@react-x11/components 0.9.0 → 0.11.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 (579) hide show
  1. package/README.md +10 -4
  2. package/dist/charts/node.d.ts +11 -1
  3. package/dist/charts/node.d.ts.map +1 -1
  4. package/dist/charts/node.js +44 -14
  5. package/dist/charts/node.js.map +1 -1
  6. package/dist/charts/render.d.ts +12 -3
  7. package/dist/charts/render.d.ts.map +1 -1
  8. package/dist/charts/render.js +94 -25
  9. package/dist/charts/render.js.map +1 -1
  10. package/dist/code/index.d.ts.map +1 -1
  11. package/dist/code/index.js +27 -24
  12. package/dist/code/index.js.map +1 -1
  13. package/dist/code-editor/completion.d.ts.map +1 -1
  14. package/dist/code-editor/completion.js +3 -2
  15. package/dist/code-editor/completion.js.map +1 -1
  16. package/dist/code-editor/index.d.ts.map +1 -1
  17. package/dist/code-editor/index.js +19 -0
  18. package/dist/code-editor/index.js.map +1 -1
  19. package/dist/code-editor/node.d.ts +177 -7
  20. package/dist/code-editor/node.d.ts.map +1 -1
  21. package/dist/code-editor/node.js +1329 -164
  22. package/dist/code-editor/node.js.map +1 -1
  23. package/dist/code-language/index.d.ts +1 -1
  24. package/dist/code-language/index.d.ts.map +1 -1
  25. package/dist/code-language/index.js +1 -1
  26. package/dist/code-language/index.js.map +1 -1
  27. package/dist/code-language/languages/glsl.d.ts.map +1 -1
  28. package/dist/code-language/languages/glsl.js +10 -2
  29. package/dist/code-language/languages/glsl.js.map +1 -1
  30. package/dist/code-language/languages/javascript.d.ts.map +1 -1
  31. package/dist/code-language/languages/javascript.js +18 -3
  32. package/dist/code-language/languages/javascript.js.map +1 -1
  33. package/dist/code-language/languages/json.d.ts.map +1 -1
  34. package/dist/code-language/languages/json.js +10 -2
  35. package/dist/code-language/languages/json.js.map +1 -1
  36. package/dist/code-language/languages/shell.d.ts.map +1 -1
  37. package/dist/code-language/languages/shell.js +10 -2
  38. package/dist/code-language/languages/shell.js.map +1 -1
  39. package/dist/code-language/languages/sql.d.ts.map +1 -1
  40. package/dist/code-language/languages/sql.js +20 -2
  41. package/dist/code-language/languages/sql.js.map +1 -1
  42. package/dist/code-language/runs.d.ts +31 -0
  43. package/dist/code-language/runs.d.ts.map +1 -1
  44. package/dist/code-language/runs.js +105 -0
  45. package/dist/code-language/runs.js.map +1 -1
  46. package/dist/code-language/stream.d.ts +22 -0
  47. package/dist/code-language/stream.d.ts.map +1 -1
  48. package/dist/code-language/stream.js +259 -9
  49. package/dist/code-language/stream.js.map +1 -1
  50. package/dist/code-language/types.d.ts +5 -4
  51. package/dist/code-language/types.d.ts.map +1 -1
  52. package/dist/codeblock/index.d.ts +26 -2
  53. package/dist/codeblock/index.d.ts.map +1 -1
  54. package/dist/codeblock/index.js +57 -4
  55. package/dist/codeblock/index.js.map +1 -1
  56. package/dist/flow/draw.d.ts +50 -0
  57. package/dist/flow/draw.d.ts.map +1 -1
  58. package/dist/flow/draw.js +132 -33
  59. package/dist/flow/draw.js.map +1 -1
  60. package/dist/flow/gl/color.d.ts +13 -0
  61. package/dist/flow/gl/color.d.ts.map +1 -0
  62. package/dist/flow/gl/color.js +82 -0
  63. package/dist/flow/gl/color.js.map +1 -0
  64. package/dist/flow/gl/index.d.ts +60 -0
  65. package/dist/flow/gl/index.d.ts.map +1 -0
  66. package/dist/flow/gl/index.js +283 -0
  67. package/dist/flow/gl/index.js.map +1 -0
  68. package/dist/flow/gl/pack.d.ts +133 -0
  69. package/dist/flow/gl/pack.d.ts.map +1 -0
  70. package/dist/flow/gl/pack.js +408 -0
  71. package/dist/flow/gl/pack.js.map +1 -0
  72. package/dist/flow/gl/renderer.d.ts +95 -0
  73. package/dist/flow/gl/renderer.d.ts.map +1 -0
  74. package/dist/flow/gl/renderer.js +459 -0
  75. package/dist/flow/gl/renderer.js.map +1 -0
  76. package/dist/flow/gl/shaders.d.ts +79 -0
  77. package/dist/flow/gl/shaders.d.ts.map +1 -0
  78. package/dist/flow/gl/shaders.js +346 -0
  79. package/dist/flow/gl/shaders.js.map +1 -0
  80. package/dist/flow/gl/text.d.ts +318 -0
  81. package/dist/flow/gl/text.d.ts.map +1 -0
  82. package/dist/flow/gl/text.js +1103 -0
  83. package/dist/flow/gl/text.js.map +1 -0
  84. package/dist/flow/index.d.ts +1 -1
  85. package/dist/flow/index.d.ts.map +1 -1
  86. package/dist/flow/index.js +619 -64
  87. package/dist/flow/index.js.map +1 -1
  88. package/dist/flow/node.d.ts +621 -72
  89. package/dist/flow/node.d.ts.map +1 -1
  90. package/dist/flow/node.js +2253 -1018
  91. package/dist/flow/node.js.map +1 -1
  92. package/dist/flow/paint.d.ts +35 -0
  93. package/dist/flow/paint.d.ts.map +1 -0
  94. package/dist/flow/paint.js +393 -0
  95. package/dist/flow/paint.js.map +1 -0
  96. package/dist/flow/paths.d.ts +14 -0
  97. package/dist/flow/paths.d.ts.map +1 -1
  98. package/dist/flow/paths.js +45 -19
  99. package/dist/flow/paths.js.map +1 -1
  100. package/dist/flow/scene.d.ts +584 -0
  101. package/dist/flow/scene.d.ts.map +1 -0
  102. package/dist/flow/scene.js +1453 -0
  103. package/dist/flow/scene.js.map +1 -0
  104. package/dist/flow/types.d.ts +111 -1
  105. package/dist/flow/types.d.ts.map +1 -1
  106. package/dist/html/controls.d.ts +28 -0
  107. package/dist/html/controls.d.ts.map +1 -1
  108. package/dist/html/controls.js +52 -2
  109. package/dist/html/controls.js.map +1 -1
  110. package/dist/html/css/calc.d.ts +14 -0
  111. package/dist/html/css/calc.d.ts.map +1 -0
  112. package/dist/html/css/calc.js +344 -0
  113. package/dist/html/css/calc.js.map +1 -0
  114. package/dist/html/css/cascade.d.ts +262 -6
  115. package/dist/html/css/cascade.d.ts.map +1 -1
  116. package/dist/html/css/cascade.js +1373 -67
  117. package/dist/html/css/cascade.js.map +1 -1
  118. package/dist/html/css/color.d.ts +23 -0
  119. package/dist/html/css/color.d.ts.map +1 -0
  120. package/dist/html/css/color.js +740 -0
  121. package/dist/html/css/color.js.map +1 -0
  122. package/dist/html/css/content.d.ts +92 -0
  123. package/dist/html/css/content.d.ts.map +1 -0
  124. package/dist/html/css/content.js +388 -0
  125. package/dist/html/css/content.js.map +1 -0
  126. package/dist/html/css/counter-styles.d.ts +76 -0
  127. package/dist/html/css/counter-styles.d.ts.map +1 -0
  128. package/dist/html/css/counter-styles.js +893 -0
  129. package/dist/html/css/counter-styles.js.map +1 -0
  130. package/dist/html/css/decode.d.ts +12 -0
  131. package/dist/html/css/decode.d.ts.map +1 -0
  132. package/dist/html/css/decode.js +116 -0
  133. package/dist/html/css/decode.js.map +1 -0
  134. package/dist/html/css/parse.d.ts +110 -10
  135. package/dist/html/css/parse.d.ts.map +1 -1
  136. package/dist/html/css/parse.js +1473 -180
  137. package/dist/html/css/parse.js.map +1 -1
  138. package/dist/html/css/style.d.ts +501 -15
  139. package/dist/html/css/style.d.ts.map +1 -1
  140. package/dist/html/css/style.js +3729 -206
  141. package/dist/html/css/style.js.map +1 -1
  142. package/dist/html/css/ua.d.ts.map +1 -1
  143. package/dist/html/css/ua.js +65 -16
  144. package/dist/html/css/ua.js.map +1 -1
  145. package/dist/html/css/values.d.ts +37 -3
  146. package/dist/html/css/values.d.ts.map +1 -1
  147. package/dist/html/css/values.js +122 -48
  148. package/dist/html/css/values.js.map +1 -1
  149. package/dist/html/css/vars.d.ts +52 -0
  150. package/dist/html/css/vars.d.ts.map +1 -0
  151. package/dist/html/css/vars.js +358 -0
  152. package/dist/html/css/vars.js.map +1 -0
  153. package/dist/html/dom.d.ts +30 -4
  154. package/dist/html/dom.d.ts.map +1 -1
  155. package/dist/html/dom.js +278 -17
  156. package/dist/html/dom.js.map +1 -1
  157. package/dist/html/fonts.d.ts +106 -0
  158. package/dist/html/fonts.d.ts.map +1 -0
  159. package/dist/html/fonts.js +565 -0
  160. package/dist/html/fonts.js.map +1 -0
  161. package/dist/html/gif.d.ts +15 -0
  162. package/dist/html/gif.d.ts.map +1 -0
  163. package/dist/html/gif.js +229 -0
  164. package/dist/html/gif.js.map +1 -0
  165. package/dist/html/index.d.ts +50 -11
  166. package/dist/html/index.d.ts.map +1 -1
  167. package/dist/html/index.js +115 -9
  168. package/dist/html/index.js.map +1 -1
  169. package/dist/html/layout/block.d.ts +226 -3
  170. package/dist/html/layout/block.d.ts.map +1 -1
  171. package/dist/html/layout/block.js +3077 -219
  172. package/dist/html/layout/block.js.map +1 -1
  173. package/dist/html/layout/boxes.d.ts +367 -17
  174. package/dist/html/layout/boxes.d.ts.map +1 -1
  175. package/dist/html/layout/boxes.js +1742 -182
  176. package/dist/html/layout/boxes.js.map +1 -1
  177. package/dist/html/layout/cache.d.ts +58 -0
  178. package/dist/html/layout/cache.d.ts.map +1 -0
  179. package/dist/html/layout/cache.js +224 -0
  180. package/dist/html/layout/cache.js.map +1 -0
  181. package/dist/html/layout/collapse.d.ts +45 -0
  182. package/dist/html/layout/collapse.d.ts.map +1 -0
  183. package/dist/html/layout/collapse.js +246 -0
  184. package/dist/html/layout/collapse.js.map +1 -0
  185. package/dist/html/layout/css-grid.d.ts +7 -0
  186. package/dist/html/layout/css-grid.d.ts.map +1 -0
  187. package/dist/html/layout/css-grid.js +900 -0
  188. package/dist/html/layout/css-grid.js.map +1 -0
  189. package/dist/html/layout/flex.d.ts.map +1 -1
  190. package/dist/html/layout/flex.js +1025 -89
  191. package/dist/html/layout/flex.js.map +1 -1
  192. package/dist/html/layout/floats.d.ts +45 -6
  193. package/dist/html/layout/floats.d.ts.map +1 -1
  194. package/dist/html/layout/floats.js +113 -14
  195. package/dist/html/layout/floats.js.map +1 -1
  196. package/dist/html/layout/grid-lines.d.ts +39 -0
  197. package/dist/html/layout/grid-lines.d.ts.map +1 -0
  198. package/dist/html/layout/grid-lines.js +119 -0
  199. package/dist/html/layout/grid-lines.js.map +1 -0
  200. package/dist/html/layout/grid.d.ts +53 -0
  201. package/dist/html/layout/grid.d.ts.map +1 -0
  202. package/dist/html/layout/grid.js +153 -0
  203. package/dist/html/layout/grid.js.map +1 -0
  204. package/dist/html/layout/inline.d.ts +154 -1
  205. package/dist/html/layout/inline.d.ts.map +1 -1
  206. package/dist/html/layout/inline.js +3598 -175
  207. package/dist/html/layout/inline.js.map +1 -1
  208. package/dist/html/layout/shaping.d.ts +6 -0
  209. package/dist/html/layout/shaping.d.ts.map +1 -0
  210. package/dist/html/layout/shaping.js +100 -0
  211. package/dist/html/layout/shaping.js.map +1 -0
  212. package/dist/html/layout/table.d.ts +3 -0
  213. package/dist/html/layout/table.d.ts.map +1 -1
  214. package/dist/html/layout/table.js +681 -175
  215. package/dist/html/layout/table.js.map +1 -1
  216. package/dist/html/layout/tracks.d.ts +44 -0
  217. package/dist/html/layout/tracks.d.ts.map +1 -0
  218. package/dist/html/layout/tracks.js +349 -0
  219. package/dist/html/layout/tracks.js.map +1 -0
  220. package/dist/html/node.d.ts +169 -4
  221. package/dist/html/node.d.ts.map +1 -1
  222. package/dist/html/node.js +1429 -137
  223. package/dist/html/node.js.map +1 -1
  224. package/dist/html/paint.d.ts +92 -3
  225. package/dist/html/paint.d.ts.map +1 -1
  226. package/dist/html/paint.js +3635 -107
  227. package/dist/html/paint.js.map +1 -1
  228. package/dist/html/resources.d.ts +60 -14
  229. package/dist/html/resources.d.ts.map +1 -1
  230. package/dist/html/resources.js +122 -36
  231. package/dist/html/resources.js.map +1 -1
  232. package/dist/html/surfaces.d.ts +23 -0
  233. package/dist/html/surfaces.d.ts.map +1 -0
  234. package/dist/html/surfaces.js +68 -0
  235. package/dist/html/surfaces.js.map +1 -0
  236. package/dist/html/svg.d.ts +80 -0
  237. package/dist/html/svg.d.ts.map +1 -0
  238. package/dist/html/svg.js +442 -0
  239. package/dist/html/svg.js.map +1 -0
  240. package/dist/html/url.d.ts +23 -0
  241. package/dist/html/url.d.ts.map +1 -0
  242. package/dist/html/url.js +80 -0
  243. package/dist/html/url.js.map +1 -0
  244. package/dist/index.d.ts +1 -1
  245. package/dist/index.d.ts.map +1 -1
  246. package/dist/index.js.map +1 -1
  247. package/dist/internal/codelines.d.ts +42 -0
  248. package/dist/internal/codelines.d.ts.map +1 -0
  249. package/dist/internal/codelines.js +170 -0
  250. package/dist/internal/codelines.js.map +1 -0
  251. package/dist/internal/collate.d.ts +3 -0
  252. package/dist/internal/collate.d.ts.map +1 -0
  253. package/dist/internal/collate.js +11 -0
  254. package/dist/internal/collate.js.map +1 -0
  255. package/dist/internal/markdown/ast.d.ts +11 -0
  256. package/dist/internal/markdown/ast.d.ts.map +1 -1
  257. package/dist/internal/markdown/parse.d.ts.map +1 -1
  258. package/dist/internal/markdown/parse.js +109 -8
  259. package/dist/internal/markdown/parse.js.map +1 -1
  260. package/dist/internal/prewarm.d.ts +18 -0
  261. package/dist/internal/prewarm.d.ts.map +1 -0
  262. package/dist/internal/prewarm.js +42 -0
  263. package/dist/internal/prewarm.js.map +1 -0
  264. package/dist/internal/sdf.d.ts +20 -0
  265. package/dist/internal/sdf.d.ts.map +1 -0
  266. package/dist/internal/sdf.js +216 -0
  267. package/dist/internal/sdf.js.map +1 -0
  268. package/dist/internal/splice.d.ts +12 -0
  269. package/dist/internal/splice.d.ts.map +1 -0
  270. package/dist/internal/splice.js +23 -0
  271. package/dist/internal/splice.js.map +1 -0
  272. package/dist/internal/window.d.ts +12 -0
  273. package/dist/internal/window.d.ts.map +1 -1
  274. package/dist/internal/window.js +23 -6
  275. package/dist/internal/window.js.map +1 -1
  276. package/dist/maps/anchors.d.ts +6 -1
  277. package/dist/maps/anchors.d.ts.map +1 -1
  278. package/dist/maps/anchors.js +260 -1
  279. package/dist/maps/anchors.js.map +1 -1
  280. package/dist/maps/controller.d.ts +57 -0
  281. package/dist/maps/controller.d.ts.map +1 -1
  282. package/dist/maps/controller.js +97 -18
  283. package/dist/maps/controller.js.map +1 -1
  284. package/dist/maps/gl/buckets.d.ts +5 -1
  285. package/dist/maps/gl/buckets.d.ts.map +1 -1
  286. package/dist/maps/gl/buckets.js +5 -2
  287. package/dist/maps/gl/buckets.js.map +1 -1
  288. package/dist/maps/gl/build-worker.d.ts +2 -0
  289. package/dist/maps/gl/build-worker.d.ts.map +1 -1
  290. package/dist/maps/gl/build-worker.js +1 -1
  291. package/dist/maps/gl/build-worker.js.map +1 -1
  292. package/dist/maps/gl/cover.d.ts +23 -4
  293. package/dist/maps/gl/cover.d.ts.map +1 -1
  294. package/dist/maps/gl/cover.js +61 -48
  295. package/dist/maps/gl/cover.js.map +1 -1
  296. package/dist/maps/gl/placement.d.ts +21 -8
  297. package/dist/maps/gl/placement.d.ts.map +1 -1
  298. package/dist/maps/gl/placement.js +133 -58
  299. package/dist/maps/gl/placement.js.map +1 -1
  300. package/dist/maps/gl/renderer.d.ts +36 -4
  301. package/dist/maps/gl/renderer.d.ts.map +1 -1
  302. package/dist/maps/gl/renderer.js +81 -24
  303. package/dist/maps/gl/renderer.js.map +1 -1
  304. package/dist/maps/gl/shaders.d.ts +18 -17
  305. package/dist/maps/gl/shaders.d.ts.map +1 -1
  306. package/dist/maps/gl/shaders.js +34 -32
  307. package/dist/maps/gl/shaders.js.map +1 -1
  308. package/dist/maps/gl/store.d.ts +10 -0
  309. package/dist/maps/gl/store.d.ts.map +1 -1
  310. package/dist/maps/gl/store.js +47 -4
  311. package/dist/maps/gl/store.js.map +1 -1
  312. package/dist/maps/gl/text.d.ts +153 -24
  313. package/dist/maps/gl/text.d.ts.map +1 -1
  314. package/dist/maps/gl/text.js +379 -63
  315. package/dist/maps/gl/text.js.map +1 -1
  316. package/dist/maps/gl/view.d.ts +293 -2
  317. package/dist/maps/gl/view.d.ts.map +1 -1
  318. package/dist/maps/gl/view.js +317 -82
  319. package/dist/maps/gl/view.js.map +1 -1
  320. package/dist/maps/icons.d.ts +32 -0
  321. package/dist/maps/icons.d.ts.map +1 -0
  322. package/dist/maps/icons.js +151 -0
  323. package/dist/maps/icons.js.map +1 -0
  324. package/dist/maps/index.d.ts +2 -0
  325. package/dist/maps/index.d.ts.map +1 -1
  326. package/dist/maps/index.js +2 -0
  327. package/dist/maps/index.js.map +1 -1
  328. package/dist/maps/labels.d.ts +56 -4
  329. package/dist/maps/labels.d.ts.map +1 -1
  330. package/dist/maps/labels.js +213 -46
  331. package/dist/maps/labels.js.map +1 -1
  332. package/dist/maps/node.d.ts +17 -0
  333. package/dist/maps/node.d.ts.map +1 -1
  334. package/dist/maps/node.js +64 -30
  335. package/dist/maps/node.js.map +1 -1
  336. package/dist/maps/proj.d.ts +48 -0
  337. package/dist/maps/proj.d.ts.map +1 -1
  338. package/dist/maps/proj.js +73 -0
  339. package/dist/maps/proj.js.map +1 -1
  340. package/dist/maps/sources.d.ts +5 -3
  341. package/dist/maps/sources.d.ts.map +1 -1
  342. package/dist/maps/sources.js +5 -3
  343. package/dist/maps/sources.js.map +1 -1
  344. package/dist/maps/style.d.ts +35 -0
  345. package/dist/maps/style.d.ts.map +1 -1
  346. package/dist/maps/style.js.map +1 -1
  347. package/dist/maps/styles.d.ts +12 -0
  348. package/dist/maps/styles.d.ts.map +1 -1
  349. package/dist/maps/styles.js +120 -6
  350. package/dist/maps/styles.js.map +1 -1
  351. package/dist/maps/tiles.d.ts +31 -11
  352. package/dist/maps/tiles.d.ts.map +1 -1
  353. package/dist/maps/tiles.js +61 -27
  354. package/dist/maps/tiles.js.map +1 -1
  355. package/dist/maps/types.d.ts +28 -4
  356. package/dist/maps/types.d.ts.map +1 -1
  357. package/dist/markdown/index.d.ts.map +1 -1
  358. package/dist/markdown/index.js +209 -68
  359. package/dist/markdown/index.js.map +1 -1
  360. package/dist/markdown/spans.d.ts.map +1 -1
  361. package/dist/markdown/spans.js +6 -0
  362. package/dist/markdown/spans.js.map +1 -1
  363. package/dist/qml/containers.d.ts.map +1 -1
  364. package/dist/qml/containers.js +6 -1
  365. package/dist/qml/containers.js.map +1 -1
  366. package/dist/qml/interaction.d.ts.map +1 -1
  367. package/dist/qml/interaction.js +38 -5
  368. package/dist/qml/interaction.js.map +1 -1
  369. package/dist/qml/layouts.d.ts.map +1 -1
  370. package/dist/qml/layouts.js +21 -6
  371. package/dist/qml/layouts.js.map +1 -1
  372. package/dist/qml/qtquick.d.ts.map +1 -1
  373. package/dist/qml/qtquick.js +9 -2
  374. package/dist/qml/qtquick.js.map +1 -1
  375. package/dist/qml/react.d.ts.map +1 -1
  376. package/dist/qml/react.js +9 -7
  377. package/dist/qml/react.js.map +1 -1
  378. package/dist/reorder/clock.d.ts +7 -0
  379. package/dist/reorder/clock.d.ts.map +1 -0
  380. package/dist/reorder/clock.js +26 -0
  381. package/dist/reorder/clock.js.map +1 -0
  382. package/dist/reorder/index.d.ts +25 -3
  383. package/dist/reorder/index.d.ts.map +1 -1
  384. package/dist/reorder/index.js +64 -19
  385. package/dist/reorder/index.js.map +1 -1
  386. package/dist/rich-text-editor/index.d.ts +0 -11
  387. package/dist/rich-text-editor/index.d.ts.map +1 -1
  388. package/dist/rich-text-editor/index.js +38 -9
  389. package/dist/rich-text-editor/index.js.map +1 -1
  390. package/dist/rich-text-editor/keymap.d.ts.map +1 -1
  391. package/dist/rich-text-editor/keymap.js +2 -1
  392. package/dist/rich-text-editor/keymap.js.map +1 -1
  393. package/dist/rich-text-editor/keys.d.ts +17 -6
  394. package/dist/rich-text-editor/keys.d.ts.map +1 -1
  395. package/dist/rich-text-editor/keys.js +135 -26
  396. package/dist/rich-text-editor/keys.js.map +1 -1
  397. package/dist/rich-text-editor/markdown.d.ts.map +1 -1
  398. package/dist/rich-text-editor/markdown.js +30 -1
  399. package/dist/rich-text-editor/markdown.js.map +1 -1
  400. package/dist/rich-text-editor/marks.d.ts +9 -0
  401. package/dist/rich-text-editor/marks.d.ts.map +1 -0
  402. package/dist/rich-text-editor/marks.js +83 -0
  403. package/dist/rich-text-editor/marks.js.map +1 -0
  404. package/dist/rich-text-editor/render.d.ts.map +1 -1
  405. package/dist/rich-text-editor/render.js +28 -10
  406. package/dist/rich-text-editor/render.js.map +1 -1
  407. package/dist/rich-text-editor/replace.d.ts +13 -0
  408. package/dist/rich-text-editor/replace.d.ts.map +1 -0
  409. package/dist/rich-text-editor/replace.js +64 -0
  410. package/dist/rich-text-editor/replace.js.map +1 -0
  411. package/dist/rich-text-editor/toolbar.d.ts.map +1 -1
  412. package/dist/rich-text-editor/toolbar.js +1 -1
  413. package/dist/rich-text-editor/toolbar.js.map +1 -1
  414. package/dist/rich-text-editor/virtual.d.ts.map +1 -1
  415. package/dist/rich-text-editor/virtual.js +17 -0
  416. package/dist/rich-text-editor/virtual.js.map +1 -1
  417. package/dist/richtext/node.d.ts +46 -2
  418. package/dist/richtext/node.d.ts.map +1 -1
  419. package/dist/richtext/node.js +187 -4
  420. package/dist/richtext/node.js.map +1 -1
  421. package/dist/richtext/runs.d.ts +13 -8
  422. package/dist/richtext/runs.d.ts.map +1 -1
  423. package/dist/richtext/runs.js +18 -8
  424. package/dist/richtext/runs.js.map +1 -1
  425. package/dist/table/index.d.ts.map +1 -1
  426. package/dist/table/index.js +23 -0
  427. package/dist/table/index.js.map +1 -1
  428. package/dist/table/rows.d.ts +3 -2
  429. package/dist/table/rows.d.ts.map +1 -1
  430. package/dist/table/rows.js +20 -6
  431. package/dist/table/rows.js.map +1 -1
  432. package/dist/terminal/index.d.ts.map +1 -1
  433. package/dist/terminal/index.js +4 -0
  434. package/dist/terminal/index.js.map +1 -1
  435. package/dist/terminal/vt/pty.d.ts +13 -2
  436. package/dist/terminal/vt/pty.d.ts.map +1 -1
  437. package/dist/terminal/vt/pty.js +18 -3
  438. package/dist/terminal/vt/pty.js.map +1 -1
  439. package/dist/terminal-output/index.d.ts.map +1 -1
  440. package/dist/terminal-output/index.js +103 -23
  441. package/dist/terminal-output/index.js.map +1 -1
  442. package/dist/terminal-output/runs.d.ts +4 -1
  443. package/dist/terminal-output/runs.d.ts.map +1 -1
  444. package/dist/terminal-output/runs.js +5 -1
  445. package/dist/terminal-output/runs.js.map +1 -1
  446. package/dist/three/canvas.d.ts.map +1 -1
  447. package/dist/three/canvas.js +85 -7
  448. package/dist/three/canvas.js.map +1 -1
  449. package/dist/three/events.d.ts +22 -4
  450. package/dist/three/events.d.ts.map +1 -1
  451. package/dist/three/events.js +34 -4
  452. package/dist/three/events.js.map +1 -1
  453. package/dist/tree/index.d.ts.map +1 -1
  454. package/dist/tree/index.js +17 -0
  455. package/dist/tree/index.js.map +1 -1
  456. package/package.json +42 -33
  457. package/src/charts/node.ts +62 -26
  458. package/src/charts/render.ts +120 -30
  459. package/src/code/index.ts +36 -32
  460. package/src/code-editor/completion.ts +3 -2
  461. package/src/code-editor/index.ts +19 -0
  462. package/src/code-editor/node.ts +1574 -184
  463. package/src/code-language/index.ts +1 -1
  464. package/src/code-language/languages/glsl.ts +12 -2
  465. package/src/code-language/languages/javascript.ts +21 -4
  466. package/src/code-language/languages/json.ts +12 -2
  467. package/src/code-language/languages/shell.ts +12 -2
  468. package/src/code-language/languages/sql.ts +22 -2
  469. package/src/code-language/runs.ts +111 -1
  470. package/src/code-language/stream.ts +257 -17
  471. package/src/code-language/types.ts +5 -4
  472. package/src/codeblock/index.ts +68 -4
  473. package/src/flow/draw.ts +167 -41
  474. package/src/flow/gl/color.ts +85 -0
  475. package/src/flow/gl/index.ts +342 -0
  476. package/src/flow/gl/pack.ts +585 -0
  477. package/src/flow/gl/renderer.ts +679 -0
  478. package/src/flow/gl/shaders.ts +371 -0
  479. package/src/flow/gl/text.ts +1295 -0
  480. package/src/flow/index.ts +794 -67
  481. package/src/flow/node.ts +2496 -1256
  482. package/src/flow/paint.ts +511 -0
  483. package/src/flow/paths.ts +80 -15
  484. package/src/flow/scene.ts +2199 -0
  485. package/src/flow/types.ts +113 -1
  486. package/src/html/controls.ts +73 -2
  487. package/src/html/css/calc.ts +385 -0
  488. package/src/html/css/cascade.ts +1481 -62
  489. package/src/html/css/color.ts +755 -0
  490. package/src/html/css/content.ts +417 -0
  491. package/src/html/css/counter-styles.ts +973 -0
  492. package/src/html/css/decode.ts +130 -0
  493. package/src/html/css/parse.ts +1514 -160
  494. package/src/html/css/style.ts +4131 -223
  495. package/src/html/css/ua.ts +66 -17
  496. package/src/html/css/values.ts +150 -50
  497. package/src/html/css/vars.ts +348 -0
  498. package/src/html/dom.ts +284 -17
  499. package/src/html/fonts.ts +641 -0
  500. package/src/html/gif.ts +233 -0
  501. package/src/html/index.ts +180 -20
  502. package/src/html/layout/block.ts +3560 -208
  503. package/src/html/layout/boxes.ts +2141 -190
  504. package/src/html/layout/cache.ts +289 -0
  505. package/src/html/layout/collapse.ts +317 -0
  506. package/src/html/layout/css-grid.ts +1063 -0
  507. package/src/html/layout/flex.ts +1115 -106
  508. package/src/html/layout/floats.ts +128 -14
  509. package/src/html/layout/grid-lines.ts +141 -0
  510. package/src/html/layout/grid.ts +181 -0
  511. package/src/html/layout/inline.ts +4187 -192
  512. package/src/html/layout/shaping.ts +140 -0
  513. package/src/html/layout/table.ts +696 -186
  514. package/src/html/layout/tracks.ts +437 -0
  515. package/src/html/node.ts +1585 -156
  516. package/src/html/paint.ts +4582 -267
  517. package/src/html/resources.ts +161 -50
  518. package/src/html/surfaces.ts +81 -0
  519. package/src/html/svg.ts +504 -0
  520. package/src/html/url.ts +86 -0
  521. package/src/index.ts +2 -0
  522. package/src/internal/codelines.ts +200 -0
  523. package/src/internal/collate.ts +12 -0
  524. package/src/internal/markdown/ast.ts +11 -0
  525. package/src/internal/markdown/parse.ts +154 -11
  526. package/src/internal/prewarm.ts +45 -0
  527. package/src/internal/sdf.ts +227 -0
  528. package/src/internal/splice.ts +25 -0
  529. package/src/internal/window.ts +36 -5
  530. package/src/maps/anchors.ts +307 -1
  531. package/src/maps/controller.ts +116 -31
  532. package/src/maps/gl/buckets.ts +5 -1
  533. package/src/maps/gl/build-worker.ts +13 -2
  534. package/src/maps/gl/cover.ts +79 -44
  535. package/src/maps/gl/placement.ts +221 -69
  536. package/src/maps/gl/renderer.ts +104 -20
  537. package/src/maps/gl/shaders.ts +35 -32
  538. package/src/maps/gl/store.ts +66 -17
  539. package/src/maps/gl/text.ts +444 -71
  540. package/src/maps/gl/view.ts +361 -96
  541. package/src/maps/icons.ts +183 -0
  542. package/src/maps/index.ts +4 -0
  543. package/src/maps/labels.ts +269 -49
  544. package/src/maps/node.ts +82 -49
  545. package/src/maps/proj.ts +87 -0
  546. package/src/maps/sources.ts +5 -3
  547. package/src/maps/style.ts +32 -0
  548. package/src/maps/styles.ts +215 -4
  549. package/src/maps/tiles.ts +64 -31
  550. package/src/maps/types.ts +28 -4
  551. package/src/markdown/index.ts +293 -92
  552. package/src/markdown/spans.ts +5 -0
  553. package/src/qml/containers.tsx +6 -1
  554. package/src/qml/interaction.tsx +42 -5
  555. package/src/qml/layouts.tsx +21 -6
  556. package/src/qml/qtquick.tsx +9 -2
  557. package/src/qml/react.tsx +7 -5
  558. package/src/reorder/clock.ts +27 -0
  559. package/src/reorder/index.ts +100 -27
  560. package/src/rich-text-editor/index.ts +37 -12
  561. package/src/rich-text-editor/keymap.ts +1 -1
  562. package/src/rich-text-editor/keys.ts +135 -26
  563. package/src/rich-text-editor/markdown.ts +36 -1
  564. package/src/rich-text-editor/marks.ts +101 -0
  565. package/src/rich-text-editor/render.ts +49 -12
  566. package/src/rich-text-editor/replace.ts +85 -0
  567. package/src/rich-text-editor/toolbar.ts +1 -1
  568. package/src/rich-text-editor/virtual.ts +25 -0
  569. package/src/richtext/node.ts +235 -14
  570. package/src/richtext/runs.ts +23 -12
  571. package/src/table/index.ts +29 -0
  572. package/src/table/rows.ts +18 -8
  573. package/src/terminal/index.ts +4 -0
  574. package/src/terminal/vt/pty.ts +16 -2
  575. package/src/terminal-output/index.ts +129 -31
  576. package/src/terminal-output/runs.ts +16 -2
  577. package/src/three/canvas.ts +102 -8
  578. package/src/three/events.ts +49 -9
  579. package/src/tree/index.ts +19 -0
@@ -14,13 +14,19 @@
14
14
  // Selector *matching* is still `css-select`'s (see cascade.ts). Parsing a
15
15
  // stylesheet is easy; matching `li:nth-child(2n+1) > a[href^="/"]` correctly
16
16
  // and quickly is not, and that is the part worth importing.
17
+ import { resolveUrl } from '../url.js';
17
18
  import { parseLength } from './values.js';
19
+ import { hasVar, unbalanced, validVars } from './vars.js';
18
20
 
19
21
  /** One `prop: value` pair, with `!important` already taken off the value. */
20
22
  export interface Declaration {
21
23
  prop: string;
22
24
  value: string;
23
25
  important: boolean;
26
+ /** A custom property, `--name`, whose name keeps its case. */
27
+ custom?: true;
28
+ /** The value has a `var()` to replace before it is read. */
29
+ vars?: true;
24
30
  }
25
31
 
26
32
  export interface StyleRule {
@@ -38,17 +44,54 @@ export interface StyleRule {
38
44
  * `(max-width: 60em)` is not the same set as the four conditions in a
39
45
  * row, and flattening cannot tell them apart. */
40
46
  media: MediaCondition[][] | null;
47
+ /** The cascade layer this rule is in (CSS Cascade 5): the rank of each
48
+ * layer on its path, outermost first, in the order the document first
49
+ * names them. Null for a rule in no layer, which outranks every layer. */
50
+ layer: readonly number[] | null;
41
51
  }
42
52
 
43
53
  export interface Stylesheet {
44
54
  rules: StyleRule[];
45
55
  /** `@import` targets, in order — the host fetches them through the
46
- * resource seam and splices the result in ahead of this sheet. */
56
+ * resource seam and splices the result in ahead of this sheet. Absolute
57
+ * when the sheet was parsed with a base. */
47
58
  imports: string[];
48
59
  /** Every width a `@media` rule in this sheet switches on. The renderer
49
60
  * keeps these so a resize can tell "the layout changed" from "the
50
61
  * *cascade* changed", and restyle only when it crossed one. */
51
62
  breakpoints: number[];
63
+ /** The `@font-face` rules, in order (see `fonts.ts`). */
64
+ fontFaces: FontFaceRule[];
65
+ /** `@counter-style` rules, in order: a name and its descriptors, which
66
+ * the cascade reads into counter styles (CSS Counter Styles 3, 3). */
67
+ counterStyles?: { prelude: string; declarations: Declaration[] }[];
68
+ }
69
+
70
+ /**
71
+ * One `@font-face` (CSS Fonts 4, 4): a face of a family, and where to get
72
+ * it. The descriptors are kept to what choosing and registering a face
73
+ * reads — `font-stretch`, `font-display` and the feature descriptors are
74
+ * not.
75
+ */
76
+ export interface FontFaceRule {
77
+ /** The family as the document names it, unquoted. */
78
+ family: string;
79
+ /** The `url()`s of `src`, in order, each with its `format()` hint
80
+ * lowercased; `local()` entries are left out. */
81
+ sources: FontFaceSource[];
82
+ /** The weights the face covers: `[400, 400]` for a static regular, a
83
+ * range for a variable face. */
84
+ weight: [number, number];
85
+ style: 'normal' | 'italic';
86
+ /** Code point ranges, inclusive; null for every code point. */
87
+ unicodeRange: [number, number][] | null;
88
+ /** The `@media` blocks the rule sits under, as a style rule's. */
89
+ media: MediaCondition[][] | null;
90
+ }
91
+
92
+ export interface FontFaceSource {
93
+ url: string;
94
+ format: string | null;
52
95
  }
53
96
 
54
97
  /** The tests this evaluates live: a width, a colour scheme, or both.
@@ -67,112 +110,429 @@ export interface MediaCondition {
67
110
 
68
111
  const IMPORTANT_RE = /!\s*important\s*$/i;
69
112
 
113
+ /** A layer's name: identifiers joined by dots. */
114
+ const LAYER_NAME =
115
+ /^-?[_a-zA-Z\u00a0-\uffff][\w\u00a0-\uffff-]*(?:\.-?[_a-zA-Z\u00a0-\uffff][\w\u00a0-\uffff-]*)*$/;
116
+
117
+ /** The at-rules that are rules with a block — which an `@import` after them
118
+ * is too late for. */
119
+ const BLOCK_AT_RULES = new Set([
120
+ 'media',
121
+ 'supports',
122
+ 'font-face',
123
+ 'page',
124
+ 'keyframes',
125
+ '-webkit-keyframes',
126
+ 'counter-style',
127
+ 'layer',
128
+ 'container',
129
+ 'property',
130
+ 'font-feature-values',
131
+ ]);
132
+
70
133
  /**
71
134
  * Parse a stylesheet. `order` continues across sheets, so the caller passes
72
135
  * a running counter and the cascade's document-order tie-break holds across
73
136
  * the whole document rather than restarting per `<style>`.
137
+ *
138
+ * The structure is CSS Syntax's: an at-rule is an at-keyword up to its `;`
139
+ * or its block, a style rule is everything up to its block, and what lies
140
+ * between is read a component value at a time — a string, an escape, a
141
+ * bracketed block with its match — so a brace or a semicolon in a string, in
142
+ * an escape or inside brackets never ends anything. A rule whose selector is
143
+ * not one is dropped whole, and so is a group with any selector in it that
144
+ * is not one (CSS 2.1 4.1.7).
74
145
  */
75
- export function parseStylesheet(text: string, startOrder = 0): Stylesheet {
76
- const sheet: Stylesheet = { rules: [], imports: [], breakpoints: [] };
146
+ export function parseStylesheet(
147
+ text: string,
148
+ startOrder = 0,
149
+ /** The document's layers, by their full names, ranked in the order the
150
+ * document first names them: one map across all of its sheets, since a
151
+ * layer one sheet names first is the same layer in the next. */
152
+ layers: Map<string, number> = new Map(),
153
+ /** The URL the sheet's own relative URLs resolve against — its own, or
154
+ * the document's for a `<style>` — or null to leave them as written.
155
+ * Every `url()`, `@import` and `@font-face` source comes out absolute. */
156
+ base: string | null = null,
157
+ ): Stylesheet {
158
+ text = withoutComments(text);
159
+ const sheet: Stylesheet = {
160
+ rules: [],
161
+ imports: [],
162
+ breakpoints: [],
163
+ fontFaces: [],
164
+ };
77
165
  let order = startOrder;
78
166
  const breakpoints = new Set<number>();
167
+ // `@import` counts only ahead of every other rule, `@charset` aside
168
+ let importsAllowed = true;
79
169
 
80
- const walk = (source: string, media: MediaCondition[][] | null): void => {
170
+ /** A layer's rank, named for the first time if it has not been. */
171
+ const rankOf = (name: string): number => {
172
+ let rank = layers.get(name);
173
+ if (rank === undefined) {
174
+ rank = layers.size;
175
+ layers.set(name, rank);
176
+ }
177
+ return rank;
178
+ };
179
+ /** The path of ranks down to a layer, `a.b` inside `outer` being three
180
+ * levels, and the layer's full name. */
181
+ const enter = (
182
+ name: string,
183
+ layer: readonly number[] | null,
184
+ path: string,
185
+ ): [number[], string] => {
186
+ const ranks = layer ? [...layer] : [];
187
+ let full = path;
188
+ for (const part of name.split('.')) {
189
+ full = full ? `${full}.${part}` : part;
190
+ ranks.push(rankOf(full));
191
+ }
192
+ return [ranks, full];
193
+ };
194
+ let anonymous = 0;
195
+
196
+ const walk = (
197
+ source: string,
198
+ media: MediaCondition[][] | null,
199
+ layer: readonly number[] | null = null,
200
+ path = '',
201
+ ): void => {
81
202
  let i = 0;
82
203
  const n = source.length;
83
204
  while (i < n) {
84
205
  i = skipTrivia(source, i);
85
206
  if (i >= n) break;
86
207
 
87
- if (source[i] === '@') {
208
+ if (source[i] === '@' && startsIdent(source, i + 1)) {
88
209
  const at = readAtRule(source, i);
89
210
  i = at.end;
90
211
  const name = at.name.toLowerCase();
212
+ if (name === 'charset') continue;
91
213
  if (name === 'import') {
92
- const url = importUrl(at.prelude);
93
- if (url) sheet.imports.push(url);
94
- } else if (name === 'media' && at.block !== null) {
214
+ if (importsAllowed && media === null) {
215
+ const url = importUrl(at.prelude);
216
+ if (url && importApplies(at.prelude)) {
217
+ sheet.imports.push(resolveUrl(url, base));
218
+ }
219
+ }
220
+ continue;
221
+ }
222
+ // Only a rule that is one closes the imports: `@media;`, `@page;`
223
+ // and an at-rule nobody knows are dropped as though never written.
224
+ if (at.block !== null && BLOCK_AT_RULES.has(name)) {
225
+ importsAllowed = false;
226
+ }
227
+ if (name === 'media' && at.block !== null) {
95
228
  const conditions = parseMediaQuery(at.prelude);
96
229
  for (const c of conditions) {
97
230
  if (c.min !== undefined) breakpoints.add(c.min);
98
- if (c.max !== undefined) breakpoints.add(c.max + 1);
231
+ if (c.max !== undefined) breakpoints.add(c.max + MAX_EDGE);
99
232
  }
100
233
  // A nested `@media` intersects with the one above it; pushing a
101
234
  // level rather than merging keeps "all of these blocks hold" exact
102
235
  // when two of them overlap.
103
- walk(at.block, media ? [...media, conditions] : [conditions]);
236
+ walk(
237
+ at.block,
238
+ media ? [...media, conditions] : [conditions],
239
+ layer,
240
+ path,
241
+ );
104
242
  } else if (name === 'supports' && at.block !== null) {
105
243
  // Everything in a `@supports` block is markup this renderer either
106
244
  // understands or ignores per-declaration, so entering it is closer
107
245
  // to right than skipping it.
108
- walk(at.block, media);
246
+ walk(at.block, media, layer, path);
247
+ } else if (name === 'layer') {
248
+ // `@layer a, b;` names layers, and so fixes their order, and
249
+ // `@layer a { … }` puts rules in one; one with no name is a layer
250
+ // of its own. Tailwind 4 writes all of its CSS in four of them.
251
+ const names = at.prelude
252
+ .split(',')
253
+ .map((part) => part.trim())
254
+ .filter(Boolean);
255
+ if (!names.every((part) => LAYER_NAME.test(part))) continue;
256
+ if (at.block === null) {
257
+ for (const part of names) enter(part, layer, path);
258
+ } else if (names.length <= 1) {
259
+ const [ranks, full] = enter(
260
+ names[0] ?? `\u0000${anonymous++}`,
261
+ layer,
262
+ path,
263
+ );
264
+ walk(at.block, media, ranks, full);
265
+ }
266
+ } else if (name === 'font-face' && at.block !== null) {
267
+ const face = parseFontFace(at.block, base, media);
268
+ if (face) sheet.fontFaces.push(face);
269
+ } else if (name === 'counter-style' && at.block !== null) {
270
+ (sheet.counterStyles ??= []).push({
271
+ prelude: at.prelude,
272
+ declarations: parseDeclarations(at.block),
273
+ });
109
274
  }
110
- // @font-face, @keyframes, @page, @charset: nothing to do, and the
111
- // block was already consumed.
275
+ // @keyframes, @page: nothing to do, and the block was already
276
+ // consumed.
112
277
  continue;
113
278
  }
114
279
 
115
- const braceAt = findBrace(source, i);
116
- if (braceAt < 0) break;
117
- const prelude = source.slice(i, braceAt).trim();
118
- const block = readBlock(source, braceAt);
280
+ // A style rule runs to its block, whatever comes first: a stray `;`
281
+ // or an `@` that names nothing is part of its selector, which then is
282
+ // not one, and the rule goes with it.
283
+ const blockAt = scanTo(source, i, '{');
284
+ if (blockAt >= n) break;
285
+ const prelude = source.slice(i, blockAt).trim();
286
+ const block = readBlock(source, blockAt);
119
287
  i = block.end;
120
- if (!prelude) continue;
121
- const declarations = parseDeclarations(block.body);
122
- if (!declarations.length) continue;
123
- for (const selector of splitSelectors(prelude)) {
124
- if (!selector) continue;
125
- sheet.rules.push({
126
- selector,
127
- specificity: specificityOf(selector),
128
- order: order++,
129
- declarations,
130
- media,
131
- });
288
+ const selectors = rawSelectors(prelude);
289
+ if (!selectors) continue;
290
+ importsAllowed = false;
291
+ styleRule(selectors, block.body, media, layer, path, 0);
292
+ }
293
+ };
294
+
295
+ /**
296
+ * A style rule, and the rules nested in it (CSS Nesting 1): a rule inside
297
+ * a rule's block is relative to it, `&` standing for it and a selector
298
+ * without one a descendant of it, and an `@media`, `@supports` or
299
+ * `@layer` inside one holds declarations for the same selectors, under
300
+ * its condition. Tailwind 4 writes its variants so — `md:flex` is
301
+ * `.md\:flex { @media (width >= 48rem) { display: flex } }`, and
302
+ * `hover:` is `&:hover` — and they were dropped with the rule. Each
303
+ * nested rule comes after its parent's declarations in the cascade's
304
+ * order, as it comes after them in the sheet.
305
+ */
306
+ const styleRule = (
307
+ selectors: string[],
308
+ body: string,
309
+ media: MediaCondition[][] | null,
310
+ layer: readonly number[] | null,
311
+ path: string,
312
+ depth: number,
313
+ ): void => {
314
+ // the plain case, a block with nothing nested in it
315
+ if (!body.includes('{')) {
316
+ emit(selectors, parseDeclarations(body), media, layer);
317
+ return;
318
+ }
319
+ const { declarations, nested } = splitNested(body);
320
+ emit(selectors, parseDeclarations(declarations), media, layer);
321
+ if (depth >= MAX_NESTING) return;
322
+ for (const item of nested) {
323
+ if (item.kind === 'rule') {
324
+ const inner = nestedSelectors(item.prelude, selectors);
325
+ if (inner) styleRule(inner, item.body, media, layer, path, depth + 1);
326
+ continue;
327
+ }
328
+ const name = item.name.toLowerCase();
329
+ if (item.block === null) continue;
330
+ if (name === 'media') {
331
+ const conditions = parseMediaQuery(item.prelude);
332
+ for (const c of conditions) {
333
+ if (c.min !== undefined) breakpoints.add(c.min);
334
+ if (c.max !== undefined) breakpoints.add(c.max + MAX_EDGE);
335
+ }
336
+ styleRule(
337
+ selectors,
338
+ item.block,
339
+ media ? [...media, conditions] : [conditions],
340
+ layer,
341
+ path,
342
+ depth + 1,
343
+ );
344
+ } else if (name === 'supports') {
345
+ styleRule(selectors, item.block, media, layer, path, depth + 1);
346
+ } else if (name === 'layer' && LAYER_NAME.test(item.prelude.trim())) {
347
+ const [ranks, full] = enter(item.prelude.trim(), layer, path);
348
+ styleRule(selectors, item.block, media, ranks, full, depth + 1);
132
349
  }
133
350
  }
134
351
  };
135
352
 
353
+ const emit = (
354
+ selectors: string[],
355
+ declarations: Declaration[],
356
+ media: MediaCondition[][] | null,
357
+ layer: readonly number[] | null,
358
+ ): void => {
359
+ if (!declarations.length) return;
360
+ if (base !== null) {
361
+ for (const d of declarations) d.value = absoluteUrls(d.value, base);
362
+ }
363
+ for (const raw of selectors) {
364
+ const selector = raw.includes('\\') ? forMatcher(raw) : raw;
365
+ sheet.rules.push({
366
+ selector,
367
+ specificity: specificityOf(selector),
368
+ order: order++,
369
+ declarations,
370
+ media,
371
+ layer,
372
+ });
373
+ }
374
+ };
375
+
136
376
  walk(text, null);
137
377
  sheet.breakpoints = [...breakpoints].sort((a, b) => a - b);
138
378
  return sheet;
139
379
  }
140
380
 
381
+ /**
382
+ * A style sheet with its comments taken out, strings left alone. CSS drops a
383
+ * comment wherever it stands (CSS 2.1 4.1.9) — between rules, which the
384
+ * scanner already skipped, but also inside a selector: `div /* note *\/ {`
385
+ * kept the note in the selector, the matcher refused it, and the rule was
386
+ * dropped whole. In a selector a comment is replaced by nothing, as the
387
+ * tokenizer does: `.a/**\/.b` is one compound selector. In a declaration
388
+ * it is replaced by a space, since there it still ends the token before it:
389
+ * `1/**\/0px` is two values and no length, and `-/**\/10px` is no
390
+ * negative one. `decls` says the text starts inside a declaration block,
391
+ * which a `style` attribute does.
392
+ */
393
+ function withoutComments(text: string, decls = false): string {
394
+ if (!text.includes('/*')) return text;
395
+ let out = '';
396
+ let from = 0;
397
+ let quote = '';
398
+ // for each open block, whether it holds declarations rather than rules
399
+ const blocks: boolean[] = [];
400
+ let prelude = 0;
401
+ // whether the rule being read is an at-rule, once its first token says
402
+ let atRule: boolean | null = null;
403
+ for (let i = 0; i < text.length; i += 1) {
404
+ const c = text[i];
405
+ if (quote) {
406
+ if (c === '\\') i = escapeEnd(text, i) - 1;
407
+ else if (c === quote || c === '\n') quote = '';
408
+ continue;
409
+ }
410
+ if (atRule === null && !isSpace(c) && !(c === '/' && text[i + 1] === '*')) {
411
+ atRule = c === '@';
412
+ }
413
+ if (c === '\\') {
414
+ // an escape: `\/*` is a slash and an asterisk, not a comment
415
+ i += 1;
416
+ } else if (c === '"' || c === "'") {
417
+ quote = c;
418
+ } else if (
419
+ (c === 'u' || c === 'U') &&
420
+ text[i + 3] === '(' &&
421
+ text.slice(i, i + 3).toLowerCase() === 'url' &&
422
+ !isNameChar(text[i - 1])
423
+ ) {
424
+ // an unquoted url is one token, `/*` and all: `url(a/*b)` is no
425
+ // comment, and taken for one it ran on through the rest of the sheet
426
+ i = urlEnd(text, i + 3) - 1;
427
+ } else if (c === '{') {
428
+ const inDecls = blocks.length ? blocks[blocks.length - 1] : decls;
429
+ blocks.push(inDecls || !opensRuleList(text, prelude, i));
430
+ prelude = i + 1;
431
+ atRule = null;
432
+ } else if (c === '}') {
433
+ blocks.pop();
434
+ prelude = i + 1;
435
+ atRule = null;
436
+ } else if (c === ';') {
437
+ prelude = i + 1;
438
+ atRule = null;
439
+ } else if (c === '/' && text[i + 1] === '*') {
440
+ const close = text.indexOf('*/', i + 2);
441
+ const inDecls = blocks.length ? blocks[blocks.length - 1] : decls;
442
+ // `@media/**\/all` is `@media all`
443
+ out += text.slice(from, i) + (inDecls || atRule ? ' ' : '');
444
+ i = close < 0 ? text.length : close + 1;
445
+ from = i + 1;
446
+ }
447
+ }
448
+ return out + text.slice(from);
449
+ }
450
+
451
+ /** Whether the block a `{` at `end` opens holds rules rather than
452
+ * declarations: a conditional or grouping at-rule's does. */
453
+ function opensRuleList(text: string, from: number, end: number): boolean {
454
+ let j = from;
455
+ while (j < end && (isSpace(text[j]) || text[j] === '/')) {
456
+ if (text[j] === '/' && text[j + 1] === '*') {
457
+ const close = text.indexOf('*/', j + 2);
458
+ j = close < 0 ? end : close + 2;
459
+ } else if (text[j] === '/') {
460
+ break;
461
+ } else {
462
+ j += 1;
463
+ }
464
+ }
465
+ return text[j] === '@' && RULE_LIST_AT.test(text.slice(j + 1, end));
466
+ }
467
+
468
+ const RULE_LIST_AT =
469
+ /^(?:media|supports|document|-moz-document|layer|container|scope|starting-style)(?![\w-])/i;
470
+
141
471
  /**
142
472
  * Parse a declaration block — also the parser for a `style=""` attribute,
143
- * which is the same grammar with no braces around it.
473
+ * which is the same grammar with no braces around it. CSS Syntax's list of
474
+ * declarations: a name, a colon, and everything up to the next `;` that no
475
+ * string or bracket holds; an at-rule in the list is read to its `;` or its
476
+ * block and dropped; anything else is dropped to the next `;`. A value with
477
+ * a `!` left in it once `!important` is taken off is not a value.
144
478
  */
145
479
  export function parseDeclarations(text: string): Declaration[] {
480
+ // a `style` attribute comes here without the sheet's pass over comments
481
+ text = withoutComments(text, true);
146
482
  const out: Declaration[] = [];
147
483
  let i = 0;
148
484
  const n = text.length;
149
485
  while (i < n) {
150
- i = skipTrivia(text, i);
151
- if (i >= n) break;
152
- // A nested block inside a declaration list is something this does not
153
- // understand (a nested rule, an `@media` inside a rule); skip it whole
154
- // rather than reading its declarations as if they were the parent's.
155
- const colon = findChar(text, i, ':', ';{');
156
- if (colon < 0) {
157
- const semi = findChar(text, i, ';', '');
158
- if (semi < 0) break;
159
- i = semi + 1;
486
+ const c = text[i];
487
+ if (isSpace(c) || c === ';') {
488
+ i += 1;
160
489
  continue;
161
490
  }
162
- const prop = text.slice(i, colon).trim().toLowerCase();
163
- const valueEnd = findChar(text, colon + 1, ';', '');
164
- const end = valueEnd < 0 ? n : valueEnd;
165
- let value = text.slice(colon + 1, end).trim();
491
+ if (c === '@' && startsIdent(text, i + 1)) {
492
+ i = readAtRule(text, i).end;
493
+ continue;
494
+ }
495
+ const end = scanTo(text, i, ';');
496
+ if (startsIdent(text, i)) {
497
+ const name = readIdent(text, i);
498
+ let colon = name.end;
499
+ while (colon < end && isSpace(text[colon])) colon += 1;
500
+ if (text[colon] === ':') {
501
+ const raw = text.slice(colon + 1, end);
502
+ let value = raw.trim();
503
+ const important = IMPORTANT_RE.test(value);
504
+ if (important) value = value.replace(IMPORTANT_RE, '').trim();
505
+ const custom = name.value.startsWith('--');
506
+ // `--` alone is no custom property's name, and a bracket closed
507
+ // that nothing opened makes a value none
508
+ if (
509
+ (value || custom) &&
510
+ !hasBang(value) &&
511
+ name.value !== '--' &&
512
+ !(custom && unbalanced(value))
513
+ ) {
514
+ const declaration: Declaration = {
515
+ // a custom property's name is case-sensitive
516
+ prop: custom ? name.value : name.value.toLowerCase(),
517
+ value: unescapeValue(value),
518
+ important,
519
+ };
520
+ if (custom) declaration.custom = true;
521
+ if (hasVar(value)) {
522
+ // a `var()` that is none is a declaration that is none — read
523
+ // untrimmed, since a newline in a string the end left open
524
+ // makes a bad string, where the end alone closes it
525
+ if (!validVars(raw)) {
526
+ i = end + 1;
527
+ continue;
528
+ }
529
+ declaration.vars = true;
530
+ }
531
+ out.push(declaration);
532
+ }
533
+ }
534
+ }
166
535
  i = end + 1;
167
- if (!prop || !value) continue;
168
- const important = IMPORTANT_RE.test(value);
169
- if (important)
170
- value = value
171
- .replace(IMPORTANT_RE, '')
172
- .replace(/\s*!\s*$/, '')
173
- .trim();
174
- if (!value) continue;
175
- out.push({ prop, value, important });
176
536
  }
177
537
  return out;
178
538
  }
@@ -180,27 +540,354 @@ export function parseDeclarations(text: string): Declaration[] {
180
540
  /** Split `a, b > c, d` on top-level commas. */
181
541
  export function splitSelectors(prelude: string): string[] {
182
542
  const out: string[] = [];
183
- let depth = 0;
184
- let quote = '';
185
543
  let start = 0;
186
- for (let i = 0; i < prelude.length; i += 1) {
187
- const c = prelude[i];
188
- if (quote) {
189
- if (c === quote && prelude[i - 1] !== '\\') quote = '';
190
- continue;
191
- }
192
- if (c === '"' || c === "'") quote = c;
193
- else if (c === '(' || c === '[') depth += 1;
194
- else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
195
- else if (c === ',' && depth === 0) {
544
+ let i = 0;
545
+ while (i < prelude.length) {
546
+ if (prelude[i] === ',') {
196
547
  out.push(prelude.slice(start, i).trim());
197
548
  start = i + 1;
549
+ i += 1;
550
+ } else {
551
+ i = opens(prelude.charCodeAt(i)) ? componentEnd(prelude, i) : i + 1;
198
552
  }
199
553
  }
200
554
  out.push(prelude.slice(start).trim());
201
555
  return out.filter(Boolean);
202
556
  }
203
557
 
558
+ /**
559
+ * A rule's selectors, or null where any of them is not a selector — an
560
+ * empty one in the list, an identifier that starts with a digit, a
561
+ * combinator with nothing after it — and the rule is dropped whole.
562
+ */
563
+ export function selectorList(prelude: string): string[] | null {
564
+ const raw = rawSelectors(prelude);
565
+ return raw && raw.map((s) => (s.includes('\\') ? forMatcher(s) : s));
566
+ }
567
+
568
+ /** A rule's selectors as written, or null where any of them is not one:
569
+ * what `selectorList` hands the matcher before its escapes are read, and
570
+ * what a nested rule's `&` stands for. */
571
+ function rawSelectors(prelude: string): string[] | null {
572
+ if (!prelude) return null;
573
+ const out: string[] = [];
574
+ let start = 0;
575
+ let i = 0;
576
+ const push = (end: number): boolean => {
577
+ const selector = prelude.slice(start, end).trim();
578
+ if (!selector || !isSelector(selector)) return false;
579
+ out.push(selector);
580
+ return true;
581
+ };
582
+ while (i < prelude.length) {
583
+ if (prelude[i] === ',') {
584
+ if (!push(i)) return null;
585
+ start = i + 1;
586
+ i += 1;
587
+ } else {
588
+ i = opens(prelude.charCodeAt(i)) ? componentEnd(prelude, i) : i + 1;
589
+ }
590
+ }
591
+ return push(prelude.length) ? out : null;
592
+ }
593
+
594
+ /**
595
+ * A selector's escapes as css-select reads them right. Its hex escapes go
596
+ * in lower case: css-what takes the space that ends `\\6C ` for a
597
+ * descendant combinator, where after `\\6c ` it does not. And in a string
598
+ * an escaped newline goes, since it only continues the line (CSS 2.1
599
+ * 4.3.7): `[title="a\\` and a newline and `b"]` is `[title="ab"]`.
600
+ */
601
+ function forMatcher(selector: string): string {
602
+ let quote = '';
603
+ let out = '';
604
+ for (let i = 0; i < selector.length; i += 1) {
605
+ const c = selector[i];
606
+ if (c === '\\') {
607
+ const end = escapeEnd(selector, i);
608
+ const escape = selector.slice(i, end);
609
+ if (/^\\[0-9a-f]/i.test(escape)) {
610
+ // the hex digits, and the one space that may end them
611
+ out += escape.toLowerCase().replace(/\s+$/, ' ');
612
+ } else if (!(quote && /^\\[\n\r\f]/.test(escape))) {
613
+ out += escape;
614
+ }
615
+ i = end - 1;
616
+ continue;
617
+ }
618
+ if (quote) {
619
+ if (c === quote) quote = '';
620
+ } else if (c === '"' || c === "'") {
621
+ quote = c;
622
+ }
623
+ out += c;
624
+ }
625
+ return out;
626
+ }
627
+
628
+ /**
629
+ * Whether a selector parses as one: compounds of type, universal, class,
630
+ * id, attribute and pseudo selectors, each name a CSS identifier, joined by
631
+ * combinators. What a selector *means* — which pseudo-classes exist — is
632
+ * the matcher's to decide; this only refuses what no grammar would read.
633
+ */
634
+ function isSelector(s: string): boolean {
635
+ const n = s.length;
636
+ let i = 0;
637
+ let expectCompound = true;
638
+ // a pseudo-element ends its selector (CSS 2.1 5.10): `p:first-line p`
639
+ // is none, and neither is `p:first-line[id]` — but for the user action
640
+ // pseudo-classes Selectors 4 lets follow one, as in `a::before:hover`
641
+ let ended = false;
642
+ while (i < n) {
643
+ const c = s[i];
644
+ if (isSpace(c)) {
645
+ i += 1;
646
+ continue;
647
+ }
648
+ if (ended) return false;
649
+ if (c === '>' || c === '+' || c === '~') {
650
+ if (expectCompound) return false;
651
+ expectCompound = true;
652
+ i += 1;
653
+ continue;
654
+ }
655
+ // one compound: simple selectors with nothing between them
656
+ const from = i;
657
+ while (i < n && !isSpace(s[i]) && !'>+~'.includes(s[i])) {
658
+ const d = s[i];
659
+ if (
660
+ ended &&
661
+ !(
662
+ d === ':' &&
663
+ s[i + 1] !== ':' &&
664
+ USER_ACTIONS.test(readIdent(s, i + 1).value)
665
+ )
666
+ ) {
667
+ return false;
668
+ }
669
+ if (d === '*' || d === '|') {
670
+ i += 1;
671
+ } else if (d === '.' || d === '#') {
672
+ if (!startsIdent(s, i + 1)) return false;
673
+ i = identEnd(s, i + 1);
674
+ } else if (d === '[') {
675
+ let j = i + 1;
676
+ while (j < n && isSpace(s[j])) j += 1;
677
+ if (s[j] === '*' || s[j] === '|') j += 1;
678
+ if (s[j] === '|') j += 1;
679
+ if (!startsIdent(s, j)) return false;
680
+ const end = componentEnd(s, i);
681
+ if (s[end - 1] !== ']') return false;
682
+ // an operator needs a value after it: `[title~=]` is no selector,
683
+ // and takes its group with it (CSS 2.1 4.1.7)
684
+ let k = identEnd(s, j);
685
+ if (s[k] === '|' && s[k + 1] !== '=' && startsIdent(s, k + 1)) {
686
+ k = identEnd(s, k + 1);
687
+ }
688
+ while (k < end - 1 && isSpace(s[k])) k += 1;
689
+ if (k < end - 1) {
690
+ if (s[k] === '=') k += 1;
691
+ else if ('~|^$*'.includes(s[k]) && s[k + 1] === '=') k += 2;
692
+ else return false;
693
+ while (k < end - 1 && isSpace(s[k])) k += 1;
694
+ if (k >= end - 1) return false;
695
+ }
696
+ i = end;
697
+ } else if (d === ':') {
698
+ const element = s[i + 1] === ':';
699
+ const at = element ? i + 2 : i + 1;
700
+ if (!startsIdent(s, at)) return false;
701
+ const name = readIdent(s, at);
702
+ // ASCII's case only (CSS 2.1 4.1.3): Unicode's makes `:lin\u212A`,
703
+ // with a Kelvin sign, `:link`
704
+ const lower = asciiLower(name.value);
705
+ if (!knownPseudo(lower, element)) return false;
706
+ if (element || LEGACY_PSEUDO_ELEMENTS.test(lower)) ended = true;
707
+ i = name.end;
708
+ if (s[i] === '(') {
709
+ const end = componentEnd(s, i);
710
+ if (s[end - 1] !== ')') return false;
711
+ if (!element && !argumentFits(lower, s.slice(i + 1, end - 1))) {
712
+ return false;
713
+ }
714
+ i = end;
715
+ } else if (!element && NEEDS_ARGUMENT.has(lower)) return false;
716
+ } else if (startsIdent(s, i) && i === from) {
717
+ i = identEnd(s, i);
718
+ } else {
719
+ return false;
720
+ }
721
+ }
722
+ expectCompound = false;
723
+ }
724
+ return !expectCompound;
725
+ }
726
+
727
+ /**
728
+ * Whether a pseudo-class takes the argument it is given: one where it
729
+ * takes one — `:lang()` names no language, and makes its group invalid as
730
+ * an unknown name does — and none where it takes none. `:is()` and
731
+ * `:where()` forgive an empty list; a vendor's are its own affair.
732
+ */
733
+ function argumentFits(name: string, argument: string): boolean {
734
+ if (name.startsWith('-')) return true;
735
+ if (NEEDS_ARGUMENT.has(name)) {
736
+ return name === 'is' || name === 'where' || /\S/.test(argument);
737
+ }
738
+ return MAY_TAKE_ARGUMENT.has(name);
739
+ }
740
+
741
+ /** A name with its ASCII letters in lower case, and no other letter's. */
742
+ export function asciiLower(name: string): string {
743
+ return name.replace(/[A-Z]+/g, (upper) => upper.toLowerCase());
744
+ }
745
+
746
+ /** The pseudo-classes that are functions, and are nothing without it. */
747
+ const NEEDS_ARGUMENT = new Set([
748
+ 'contains',
749
+ 'dir',
750
+ 'has',
751
+ 'host-context',
752
+ 'icontains',
753
+ 'is',
754
+ 'lang',
755
+ 'matches',
756
+ 'not',
757
+ 'nth-child',
758
+ 'nth-col',
759
+ 'nth-last-child',
760
+ 'nth-last-col',
761
+ 'nth-last-of-type',
762
+ 'nth-of-type',
763
+ 'state',
764
+ 'where',
765
+ ]);
766
+
767
+ /** The ones that are a pseudo-class with an argument or without. */
768
+ const MAY_TAKE_ARGUMENT = new Set(['current', 'future', 'host', 'past']);
769
+
770
+ /**
771
+ * Whether a pseudo-class or pseudo-element is one: an unknown one makes its
772
+ * selector invalid, and with it the group (CSS 2.1 4.1.7) — which is also
773
+ * what a browser does with another engine's `:-moz-…`. A vendor prefix is
774
+ * let through: which of them a browser knows is its own affair.
775
+ */
776
+ function knownPseudo(name: string, element: boolean): boolean {
777
+ if (name.startsWith('-')) return true;
778
+ if (element) return PSEUDO_ELEMENTS.has(name);
779
+ return PSEUDO_CLASSES.has(name) || LEGACY_PSEUDO_ELEMENTS.test(name);
780
+ }
781
+
782
+ const PSEUDO_ELEMENTS = new Set([
783
+ 'before',
784
+ 'after',
785
+ 'first-line',
786
+ 'first-letter',
787
+ 'selection',
788
+ 'placeholder',
789
+ 'marker',
790
+ 'backdrop',
791
+ 'file-selector-button',
792
+ 'cue',
793
+ 'part',
794
+ 'slotted',
795
+ 'spelling-error',
796
+ 'grammar-error',
797
+ 'target-text',
798
+ 'highlight',
799
+ 'details-content',
800
+ ]);
801
+
802
+ const PSEUDO_CLASSES = new Set([
803
+ // css-select's own, which the matcher answers and the docs promise
804
+ 'button',
805
+ 'checkbox',
806
+ 'file',
807
+ 'header',
808
+ 'icontains',
809
+ 'image',
810
+ 'input',
811
+ 'parent',
812
+ 'password',
813
+ 'radio',
814
+ 'reset',
815
+ 'selected',
816
+ 'submit',
817
+ 'text',
818
+ // CSS's
819
+ 'active',
820
+ 'any-link',
821
+ 'autofill',
822
+ 'blank',
823
+ 'checked',
824
+ 'closed',
825
+ 'contains',
826
+ 'current',
827
+ 'default',
828
+ 'defined',
829
+ 'dir',
830
+ 'disabled',
831
+ 'empty',
832
+ 'enabled',
833
+ 'first',
834
+ 'first-child',
835
+ 'first-of-type',
836
+ 'focus',
837
+ 'focus-visible',
838
+ 'focus-within',
839
+ 'fullscreen',
840
+ 'future',
841
+ 'has',
842
+ 'host',
843
+ 'host-context',
844
+ 'hover',
845
+ 'in-range',
846
+ 'indeterminate',
847
+ 'invalid',
848
+ 'is',
849
+ 'lang',
850
+ 'last-child',
851
+ 'last-of-type',
852
+ 'left',
853
+ 'link',
854
+ 'local-link',
855
+ 'matches',
856
+ 'modal',
857
+ 'not',
858
+ 'nth-child',
859
+ 'nth-col',
860
+ 'nth-last-child',
861
+ 'nth-last-col',
862
+ 'nth-last-of-type',
863
+ 'nth-of-type',
864
+ 'only-child',
865
+ 'only-of-type',
866
+ 'open',
867
+ 'optional',
868
+ 'out-of-range',
869
+ 'past',
870
+ 'paused',
871
+ 'picture-in-picture',
872
+ 'placeholder-shown',
873
+ 'playing',
874
+ 'popover-open',
875
+ 'read-only',
876
+ 'read-write',
877
+ 'required',
878
+ 'right',
879
+ 'root',
880
+ 'scope',
881
+ 'state',
882
+ 'target',
883
+ 'target-within',
884
+ 'user-invalid',
885
+ 'user-valid',
886
+ 'valid',
887
+ 'visited',
888
+ 'where',
889
+ ]);
890
+
204
891
  /**
205
892
  * Specificity, packed. Counted from the selector text rather than from a
206
893
  * parse: `#a` is an id, `.a`/`[a]`/`:a` is a class, a bare name is a type,
@@ -210,6 +897,11 @@ export function splitSelectors(prelude: string): string[] {
210
897
  * specificity is their argument's. The approximation costs an author who
211
898
  * writes `:is(#id)` and expects it to beat a class; nothing else.
212
899
  */
900
+ const LEGACY_PSEUDO_ELEMENTS = /^(?:before|after|first-line|first-letter)$/i;
901
+
902
+ /** The pseudo-classes that may follow a pseudo-element. */
903
+ const USER_ACTIONS = /^(?:hover|active|focus|focus-visible|focus-within)$/i;
904
+
213
905
  export function specificityOf(selector: string): number {
214
906
  let ids = 0;
215
907
  let classes = 0;
@@ -220,25 +912,34 @@ export function specificityOf(selector: string): number {
220
912
  const c = selector[i];
221
913
  if (c === '#') {
222
914
  ids += 1;
223
- i = skipIdent(selector, i + 1);
915
+ i = identEnd(selector, i + 1);
224
916
  } else if (c === '.') {
225
917
  classes += 1;
226
- i = skipIdent(selector, i + 1);
918
+ i = identEnd(selector, i + 1);
227
919
  } else if (c === '[') {
228
920
  classes += 1;
229
- i = skipBalanced(selector, i, '[', ']');
921
+ i = componentEnd(selector, i);
230
922
  } else if (c === ':') {
231
923
  if (selector[i + 1] === ':') {
232
924
  types += 1;
233
- i = skipIdent(selector, i + 2);
925
+ i = identEnd(selector, i + 2);
234
926
  } else {
235
- classes += 1;
236
- i = skipIdent(selector, i + 1);
237
- if (selector[i] === '(') i = skipBalanced(selector, i, '(', ')');
927
+ const name = readIdent(selector, i + 1);
928
+ // CSS 2 spelled the four pseudo-elements it had with one colon
929
+ if (LEGACY_PSEUDO_ELEMENTS.test(name.value)) {
930
+ types += 1;
931
+ } else {
932
+ classes += 1;
933
+ }
934
+ i = name.end;
935
+ if (selector[i] === '(') i = componentEnd(selector, i);
238
936
  }
239
- } else if (isIdentStart(c)) {
937
+ } else if (c === '*' || c === '|') {
938
+ // the universal selector, and a namespace's bar, count nothing
939
+ i += 1;
940
+ } else if (startsIdent(selector, i)) {
240
941
  types += 1;
241
- i = skipIdent(selector, i);
942
+ i = identEnd(selector, i);
242
943
  } else {
243
944
  i += 1;
244
945
  }
@@ -274,99 +975,347 @@ function isSpace(c: string): boolean {
274
975
  return c === ' ' || c === '\t' || c === '\n' || c === '\r' || c === '\f';
275
976
  }
276
977
 
277
- function isIdentStart(c: string): boolean {
278
- return /[a-zA-Z_\-*|\\]/.test(c);
978
+ /** A character an identifier may start with, before escapes: a letter, an
979
+ * underscore, anything past ASCII. */
980
+ function isNameStart(c: string | undefined): boolean {
981
+ if (c === undefined) return false;
982
+ const code = c.charCodeAt(0);
983
+ return (
984
+ (code >= 0x61 && code <= 0x7a) ||
985
+ (code >= 0x41 && code <= 0x5a) ||
986
+ code === 0x5f ||
987
+ code >= 0x80
988
+ );
279
989
  }
280
990
 
281
- function skipIdent(text: string, from: number): number {
282
- let i = from;
283
- while (i < text.length && /[a-zA-Z0-9_\-\\]/.test(text[i])) i += 1;
991
+ function isNameChar(c: string | undefined): boolean {
992
+ if (c === undefined) return false;
993
+ const code = c.charCodeAt(0);
994
+ return isNameStart(c) || (code >= 0x30 && code <= 0x39) || code === 0x2d;
995
+ }
996
+
997
+ /** Whether a backslash at `at` escapes what follows — not a newline, and
998
+ * not the end of the text. */
999
+ function isEscape(text: string, at: number): boolean {
1000
+ return text[at] === '\\' && at + 1 < text.length && text[at + 1] !== '\n';
1001
+ }
1002
+
1003
+ /**
1004
+ * Whether an identifier starts at `at` (CSS Syntax 4.3.10): a name-start
1005
+ * character or an escape, after at most one hyphen — or two hyphens. Every
1006
+ * character this accepts, `readIdent` steps over: a scan that finds an
1007
+ * identifier and then reads none of it never moves again, which is how a
1008
+ * `*` selector once hung the application.
1009
+ */
1010
+ export function startsIdent(text: string, at: number): boolean {
1011
+ const c = text[at];
1012
+ if (c === '-') {
1013
+ const d = text[at + 1];
1014
+ return isNameStart(d) || d === '-' || isEscape(text, at + 1);
1015
+ }
1016
+ return isNameStart(c) || isEscape(text, at);
1017
+ }
1018
+
1019
+ /** Where an identifier from `at` ends, its escapes stepped over. */
1020
+ function identEnd(text: string, at: number): number {
1021
+ let i = at;
1022
+ while (i < text.length) {
1023
+ const code = text.charCodeAt(i);
1024
+ if (
1025
+ (code >= 0x61 && code <= 0x7a) ||
1026
+ (code >= 0x41 && code <= 0x5a) ||
1027
+ (code >= 0x30 && code <= 0x39) ||
1028
+ code === 0x2d ||
1029
+ code === 0x5f ||
1030
+ code >= 0x80
1031
+ ) {
1032
+ i += 1;
1033
+ } else if (code === 0x5c && isEscape(text, i)) {
1034
+ i = readEscape(text, i).end;
1035
+ } else {
1036
+ break;
1037
+ }
1038
+ }
284
1039
  return i;
285
1040
  }
286
1041
 
287
- function skipBalanced(
1042
+ /** An identifier from `at`, its escapes resolved, and where it ends. */
1043
+ export function readIdent(
288
1044
  text: string,
289
- from: number,
290
- open: string,
291
- close: string,
292
- ): number {
293
- let depth = 0;
294
- let quote = '';
295
- for (let i = from; i < text.length; i += 1) {
296
- const c = text[i];
297
- if (quote) {
298
- if (c === quote && text[i - 1] !== '\\') quote = '';
299
- continue;
300
- }
301
- if (c === '"' || c === "'") quote = c;
302
- else if (c === open) depth += 1;
303
- else if (c === close) {
304
- depth -= 1;
305
- if (depth === 0) return i + 1;
1045
+ at: number,
1046
+ ): { value: string; end: number } {
1047
+ const end = identEnd(text, at);
1048
+ const raw = text.slice(at, end);
1049
+ if (!raw.includes('\\')) return { value: raw, end };
1050
+ let value = '';
1051
+ let i = at;
1052
+ while (i < end) {
1053
+ if (text[i] === '\\') {
1054
+ const escape = readEscape(text, i);
1055
+ value += escape.char;
1056
+ i = escape.end;
1057
+ } else {
1058
+ value += text[i];
1059
+ i += 1;
306
1060
  }
307
1061
  }
1062
+ return { value, end };
1063
+ }
1064
+
1065
+ /** The character an escape at `at` stands for, and where it ends: up to six
1066
+ * hex digits and one white space after them, or the next character. */
1067
+ function readEscape(text: string, at: number): { char: string; end: number } {
1068
+ let i = at + 1;
1069
+ let hex = '';
1070
+ while (hex.length < 6 && i < text.length && /[0-9a-fA-F]/.test(text[i])) {
1071
+ hex += text[i];
1072
+ i += 1;
1073
+ }
1074
+ if (!hex) return { char: text[i] ?? '�', end: i + 1 };
1075
+ if (text[i] === '\r' && text[i + 1] === '\n') i += 2;
1076
+ else if (isSpace(text[i] ?? '')) i += 1;
1077
+ const code = parseInt(hex, 16);
1078
+ const char =
1079
+ code === 0 || code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)
1080
+ ? '�'
1081
+ : String.fromCodePoint(code);
1082
+ return { char, end: i };
1083
+ }
1084
+
1085
+ /**
1086
+ * Where the component value starting at `at` ends (CSS Syntax 5.4.7): a
1087
+ * string to its closing quote (or the line that breaks it), an escape, a
1088
+ * `url(…)` to its parenthesis, a bracketed block to its match — whatever
1089
+ * closing brackets of another kind it holds — and otherwise one character.
1090
+ * Every scan below goes through this, so a brace in a string, a bracket
1091
+ * escaped with a backslash and a semicolon in `url(a;b)` are what they are
1092
+ * everywhere at once.
1093
+ */
1094
+ function componentEnd(text: string, at: number): number {
1095
+ const c = text[at];
1096
+ if (c === '"' || c === "'") return stringEnd(text, at);
1097
+ if (c === '\\') return isEscape(text, at) ? readEscape(text, at).end : at + 1;
1098
+ if (c === '(' || c === '[' || c === '{') return blockEnd(text, at);
1099
+ if (
1100
+ (c === 'u' || c === 'U') &&
1101
+ text[at + 3] === '(' &&
1102
+ text.slice(at, at + 3).toLowerCase() === 'url' &&
1103
+ !isNameChar(text[at - 1])
1104
+ ) {
1105
+ return urlEnd(text, at + 3);
1106
+ }
1107
+ return at + 1;
1108
+ }
1109
+
1110
+ function stringEnd(text: string, at: number): number {
1111
+ const quote = text[at];
1112
+ let i = at + 1;
1113
+ while (i < text.length) {
1114
+ const c = text[i];
1115
+ if (c === quote) return i + 1;
1116
+ // a newline ends a string that has not ended: it is a bad string, and
1117
+ // the newline is not in it
1118
+ if (c === '\n' || c === '\r' || c === '\f') return i;
1119
+ i = c === '\\' ? escapeEnd(text, i) : i + 1;
1120
+ }
308
1121
  return text.length;
309
1122
  }
310
1123
 
311
- /** The next `{` that is not inside a string, comment or bracket. */
312
- function findBrace(text: string, from: number): number {
313
- let quote = '';
314
- let bracket = 0;
315
- for (let i = from; i < text.length; i += 1) {
1124
+ /** Where a backslash at `at` stops meaning something inside a string: a
1125
+ * newline after it continues the line, and an escape — six hex digits and
1126
+ * the white space after them included — is read whole. */
1127
+ /** Where an escape at `at` ends: past its hex digits and the one space
1128
+ * that may close them, or past the character it escapes. */
1129
+ export function escapeEnd(text: string, at: number): number {
1130
+ if (text[at + 1] === '\r' && text[at + 2] === '\n') return at + 3;
1131
+ if (isEscape(text, at)) return readEscape(text, at).end;
1132
+ return Math.min(text.length, at + 2);
1133
+ }
1134
+
1135
+ function blockEnd(text: string, at: number): number {
1136
+ const open = text.charCodeAt(at);
1137
+ const close = open === 0x28 ? 0x29 : open === 0x5b ? 0x5d : 0x7d;
1138
+ let i = at + 1;
1139
+ while (i < text.length) {
1140
+ const code = text.charCodeAt(i);
1141
+ if (code === close) return i + 1;
1142
+ // a closing bracket of another kind inside is only a token
1143
+ i = opens(code) ? componentEnd(text, i) : i + 1;
1144
+ }
1145
+ return text.length;
1146
+ }
1147
+
1148
+ /** Whether a character starts a component value longer than itself: a
1149
+ * quote, an escape, an opening bracket, or the `u` of a `url(`. Every other
1150
+ * character is its own, which is what keeps a scan a loop over codes. */
1151
+ function opens(code: number): boolean {
1152
+ return (
1153
+ code === 0x22 ||
1154
+ code === 0x27 ||
1155
+ code === 0x5c ||
1156
+ code === 0x28 ||
1157
+ code === 0x5b ||
1158
+ code === 0x7b ||
1159
+ code === 0x75 ||
1160
+ code === 0x55
1161
+ );
1162
+ }
1163
+
1164
+ /** `url(` at the parenthesis: a quoted argument is a function like any
1165
+ * other; an unquoted one runs to its `)` with its escapes. */
1166
+ /** Where a `url(` whose parenthesis is at `paren` ends: past its `)`. */
1167
+ export function urlEnd(text: string, paren: number): number {
1168
+ let i = paren + 1;
1169
+ while (i < text.length && isSpace(text[i])) i += 1;
1170
+ if (text[i] === '"' || text[i] === "'") return blockEnd(text, paren);
1171
+ while (i < text.length) {
316
1172
  const c = text[i];
317
- if (quote) {
318
- if (c === quote && text[i - 1] !== '\\') quote = '';
1173
+ if (c === ')') return i + 1;
1174
+ i += c === '\\' ? 2 : 1;
1175
+ }
1176
+ return text.length;
1177
+ }
1178
+
1179
+ /**
1180
+ * A `url(…)` as CSS Syntax 3 reads it (4.3.6): quoted, a string, which the
1181
+ * end of the sheet may close as it closes the parenthesis; or unquoted, to
1182
+ * its `)`, with white space only around it and its escapes resolved. `null`
1183
+ * for `none`, or a url of nothing. `undefined` for a bad url — a quote, a
1184
+ * parenthesis or white space inside an unquoted one, or anything after a
1185
+ * quoted one's string — which makes the declaration it is in invalid.
1186
+ */
1187
+ export function parseUrl(value: string): string | null | undefined {
1188
+ const v = value.trim();
1189
+ if (!v || v.toLowerCase() === 'none') return null;
1190
+ if (!/^url\(/i.test(v)) return unquote(v) || null;
1191
+ let i = 4;
1192
+ while (i < v.length && isSpace(v[i])) i += 1;
1193
+ let out = '';
1194
+ const quote = v[i];
1195
+ if (quote === '"' || quote === "'") {
1196
+ i += 1;
1197
+ while (i < v.length && v[i] !== quote) {
1198
+ if (v[i] === '\\') {
1199
+ if (v[i + 1] === '\n') {
1200
+ i += 2;
1201
+ continue;
1202
+ }
1203
+ const escape = readEscape(v, i);
1204
+ out += escape.char;
1205
+ i = escape.end;
1206
+ } else {
1207
+ out += v[i];
1208
+ i += 1;
1209
+ }
1210
+ }
1211
+ i += 1;
1212
+ while (i < v.length && isSpace(v[i])) i += 1;
1213
+ if (i < v.length && v[i] !== ')') return undefined;
1214
+ return rest(v, i) ? undefined : out || null;
1215
+ }
1216
+ while (i < v.length && v[i] !== ')') {
1217
+ const c = v[i];
1218
+ if (isSpace(c)) {
1219
+ while (i < v.length && isSpace(v[i])) i += 1;
1220
+ if (i < v.length && v[i] !== ')') return undefined;
1221
+ break;
1222
+ }
1223
+ if (c === '"' || c === "'" || c === '(') return undefined;
1224
+ if (c === '\\') {
1225
+ if (!isEscape(v, i)) return undefined;
1226
+ const escape = readEscape(v, i);
1227
+ out += escape.char;
1228
+ i = escape.end;
319
1229
  continue;
320
1230
  }
321
- if (c === '"' || c === "'") quote = c;
322
- else if (c === '/' && text[i + 1] === '*') {
323
- const close = text.indexOf('*/', i + 2);
324
- i = close < 0 ? text.length : close + 1;
325
- } else if (c === '(' || c === '[') bracket += 1;
326
- else if (c === ')' || c === ']') bracket = Math.max(0, bracket - 1);
327
- else if (c === '{' && bracket === 0) return i;
328
- else if (c === ';' && bracket === 0) return -1; // a stray declaration
1231
+ out += c;
1232
+ i += 1;
329
1233
  }
330
- return -1;
1234
+ return rest(v, i) ? undefined : out || null;
331
1235
  }
332
1236
 
333
- /** Find `target` at depth zero, stopping at any of `stopAt`. */
334
- function findChar(
335
- text: string,
336
- from: number,
337
- target: string,
338
- stopAt: string,
339
- ): number {
340
- let quote = '';
341
- let depth = 0;
342
- for (let i = from; i < text.length; i += 1) {
343
- const c = text[i];
344
- if (quote) {
345
- if (c === quote && text[i - 1] !== '\\') quote = '';
1237
+ /** Whether anything follows a url's `)` at `at`: `url(a) repeat` is no
1238
+ * image, and the declaration it is in is invalid. */
1239
+ function rest(v: string, at: number): boolean {
1240
+ return at + 1 < v.length && v.slice(at + 1).trim() !== '';
1241
+ }
1242
+
1243
+ function unquote(value: string): string {
1244
+ const v = value.trim();
1245
+ if (
1246
+ v.length >= 2 &&
1247
+ (v[0] === '"' || v[0] === "'") &&
1248
+ v[v.length - 1] === v[0]
1249
+ ) {
1250
+ return v.slice(1, -1);
1251
+ }
1252
+ return v;
1253
+ }
1254
+
1255
+ /** The first `target` from `at` that no string, escape or bracket holds, or
1256
+ * the length of the text. */
1257
+ function scanTo(text: string, at: number, target: string): number {
1258
+ const want = target.charCodeAt(0);
1259
+ let i = at;
1260
+ while (i < text.length) {
1261
+ const code = text.charCodeAt(i);
1262
+ if (code === want) return i;
1263
+ i = opens(code) ? componentEnd(text, i) : i + 1;
1264
+ }
1265
+ return text.length;
1266
+ }
1267
+
1268
+ /** Whether a `!` stands in a value outside its strings and brackets. */
1269
+ function hasBang(value: string): boolean {
1270
+ if (!value.includes('!')) return false;
1271
+ for (let at = scanTo(value, 0, '!'); at < value.length;) {
1272
+ // `<!--` is a token of its own, which a custom property may hold
1273
+ if (value[at - 1] !== '<' || !value.startsWith('--', at + 1)) return true;
1274
+ at = scanTo(value, at + 1, '!');
1275
+ }
1276
+ return false;
1277
+ }
1278
+
1279
+ /**
1280
+ * A value with its escapes resolved outside its strings and `url()`s:
1281
+ * `\67reen` is `green`. A string keeps its escapes for whoever reads its
1282
+ * text, `content` among them.
1283
+ */
1284
+ function unescapeValue(value: string): string {
1285
+ if (!value.includes('\\')) return value;
1286
+ let out = '';
1287
+ let i = 0;
1288
+ while (i < value.length) {
1289
+ if (isEscape(value, i)) {
1290
+ // Resolved where it stands for a name character; any other it is part
1291
+ // of an identifier that a value parser should see as one, not as the
1292
+ // tab or the semicolon it names — `red \9` is not `red`.
1293
+ const escape = readEscape(value, i);
1294
+ out += isNameChar(escape.char) ? escape.char : value.slice(i, escape.end);
1295
+ i = escape.end;
346
1296
  continue;
347
1297
  }
348
- if (c === '"' || c === "'") quote = c;
349
- else if (c === '/' && text[i + 1] === '*') {
350
- const close = text.indexOf('*/', i + 2);
351
- i = close < 0 ? text.length : close + 1;
352
- } else if (c === '(' || c === '[' || c === '{') depth += 1;
353
- else if (c === ')' || c === ']' || c === '}') {
354
- if (depth === 0 && stopAt.includes(c)) return -1;
355
- depth -= 1;
356
- } else if (depth === 0) {
357
- if (c === target) return i;
358
- if (stopAt.includes(c)) return -1;
359
- }
1298
+ const end = componentEnd(value, i);
1299
+ out += value.slice(i, end);
1300
+ i = end;
360
1301
  }
361
- return -1;
1302
+ return out;
362
1303
  }
363
1304
 
1305
+ /**
1306
+ * A `{…}` block: its body, and where the text goes on after it. The end of
1307
+ * a style sheet closes a block still open (CSS 2.1 4.2), so a block the end
1308
+ * cut off is everything after its brace. Cutting its last character as if
1309
+ * it were the `}` made `color: blue` `color: blu` — and left an unclosed
1310
+ * `rgb(` to reach paint whole or not, by where the sheet's whitespace fell.
1311
+ */
364
1312
  function readBlock(
365
1313
  text: string,
366
1314
  braceAt: number,
367
1315
  ): { body: string; end: number } {
368
- const end = skipBalanced(text, braceAt, '{', '}');
369
- return { body: text.slice(braceAt + 1, Math.max(braceAt + 1, end - 1)), end };
1316
+ const end = blockEnd(text, braceAt);
1317
+ const closed = text[end - 1] === '}' && end - 1 > braceAt;
1318
+ return { body: text.slice(braceAt + 1, closed ? end - 1 : end), end };
370
1319
  }
371
1320
 
372
1321
  interface AtRule {
@@ -376,29 +1325,205 @@ interface AtRule {
376
1325
  end: number;
377
1326
  }
378
1327
 
1328
+ /** An at-rule: its name, and everything up to its `;` or its block. */
379
1329
  function readAtRule(text: string, at: number): AtRule {
380
- const nameEnd = skipIdent(text, at + 1);
381
- const name = text.slice(at + 1, nameEnd);
382
- let i = nameEnd;
383
- let quote = '';
384
- let depth = 0;
385
- for (; i < text.length; i += 1) {
386
- const c = text[i];
387
- if (quote) {
388
- if (c === quote && text[i - 1] !== '\\') quote = '';
389
- continue;
390
- }
391
- if (c === '"' || c === "'") quote = c;
392
- else if (c === '(' || c === '[') depth += 1;
393
- else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
394
- else if (depth === 0 && (c === ';' || c === '{')) break;
1330
+ const name = readIdent(text, at + 1);
1331
+ let i = name.end;
1332
+ while (i < text.length && text[i] !== ';' && text[i] !== '{') {
1333
+ i = opens(text.charCodeAt(i)) ? componentEnd(text, i) : i + 1;
395
1334
  }
396
- const prelude = text.slice(nameEnd, i).trim();
1335
+ const prelude = text.slice(name.end, i).trim();
397
1336
  if (text[i] === '{') {
398
1337
  const block = readBlock(text, i);
399
- return { name, prelude, block: block.body, end: block.end };
1338
+ return { name: name.value, prelude, block: block.body, end: block.end };
1339
+ }
1340
+ return {
1341
+ name: name.value,
1342
+ prelude,
1343
+ block: null,
1344
+ end: Math.min(text.length, i + 1),
1345
+ };
1346
+ }
1347
+
1348
+ /** Where a width past a maximum starts, for the widths a restyle is due
1349
+ * at: a sixty-fourth of a pixel past it, where the widths a display scale
1350
+ * divides a device width into (`640.5` at 2x) fall on the right side. */
1351
+ const MAX_EDGE = 1 / 64;
1352
+
1353
+ /**
1354
+ * A width range in Media Queries 4's syntax — `(width >= 48rem)`, `(60rem >
1355
+ * width)`, `(40rem <= width < 60rem)` — as the widths it holds between, or
1356
+ * null for a term that is not one. A strict bound is a sixty-fourth of a
1357
+ * pixel inside the value, where a viewport's width never lands.
1358
+ */
1359
+ function widthRange(term: string): { min?: number; max?: number } | null {
1360
+ const inner = /^\(\s*(.*?)\s*\)$/.exec(term)?.[1];
1361
+ if (!inner || !/[<>=]/.test(inner)) return null;
1362
+ const parts = inner.split(/\s*(<=|>=|<|>|=)\s*/);
1363
+ const isWidth = (part: string) => part.toLowerCase() === 'width';
1364
+ const out: { min?: number; max?: number } = {};
1365
+ // `width OP value`, with the operator read from the width's side
1366
+ const bound = (op: string, value: string): boolean => {
1367
+ const len = parseLength(value, ZERO_UNITS);
1368
+ if (typeof len !== 'number') return false;
1369
+ const edge = 1 / 64;
1370
+ if (op === '>=' || op === '=') out.min = Math.max(out.min ?? 0, len);
1371
+ if (op === '>') out.min = Math.max(out.min ?? 0, len + edge);
1372
+ if (op === '<=' || op === '=') out.max = Math.min(out.max ?? Infinity, len);
1373
+ if (op === '<') out.max = Math.min(out.max ?? Infinity, len - edge);
1374
+ return true;
1375
+ };
1376
+ const flip: Record<string, string> = {
1377
+ '<': '>',
1378
+ '<=': '>=',
1379
+ '>': '<',
1380
+ '>=': '<=',
1381
+ '=': '=',
1382
+ };
1383
+ if (parts.length === 3 && isWidth(parts[0])) {
1384
+ return bound(parts[1], parts[2]) ? out : null;
1385
+ }
1386
+ if (parts.length === 3 && isWidth(parts[2])) {
1387
+ return bound(flip[parts[1]], parts[0]) ? out : null;
1388
+ }
1389
+ if (parts.length === 5 && isWidth(parts[2])) {
1390
+ return bound(flip[parts[1]], parts[0]) && bound(parts[3], parts[4])
1391
+ ? out
1392
+ : null;
1393
+ }
1394
+ return null;
1395
+ }
1396
+
1397
+ /** How deep rules may nest before the ones further in are dropped: far
1398
+ * past anything written by hand or by a framework, and short of what a
1399
+ * generated sheet could make of the stack. */
1400
+ const MAX_NESTING = 16;
1401
+
1402
+ type Nested =
1403
+ | { kind: 'rule'; prelude: string; body: string }
1404
+ | { kind: 'at'; name: string; prelude: string; block: string | null };
1405
+
1406
+ /**
1407
+ * A style rule's block, parted into its declarations and the rules nested
1408
+ * in it. A declaration runs to its `;` and a nested rule to its block,
1409
+ * whichever comes first — `color: red;` and `a:hover { … }` — and a custom
1410
+ * property is a declaration whatever its value holds.
1411
+ */
1412
+ function splitNested(body: string): {
1413
+ declarations: string;
1414
+ nested: Nested[];
1415
+ } {
1416
+ let declarations = '';
1417
+ const nested: Nested[] = [];
1418
+ let i = 0;
1419
+ while (i < body.length) {
1420
+ i = skipTrivia(body, i);
1421
+ if (i >= body.length) break;
1422
+ if (body[i] === ';') {
1423
+ i += 1;
1424
+ continue;
1425
+ }
1426
+ if (body[i] === '@' && startsIdent(body, i + 1)) {
1427
+ const at = readAtRule(body, i);
1428
+ nested.push({
1429
+ kind: 'at',
1430
+ name: at.name,
1431
+ prelude: at.prelude,
1432
+ block: at.block,
1433
+ });
1434
+ i = at.end;
1435
+ continue;
1436
+ }
1437
+ const semi = scanTo(body, i, ';');
1438
+ const brace = scanTo(body, i, '{');
1439
+ if (brace < semi && !CUSTOM_DECLARATION.test(body.slice(i, brace))) {
1440
+ const block = readBlock(body, brace);
1441
+ nested.push({
1442
+ kind: 'rule',
1443
+ prelude: body.slice(i, brace).trim(),
1444
+ body: block.body,
1445
+ });
1446
+ i = block.end;
1447
+ continue;
1448
+ }
1449
+ declarations += `${body.slice(i, semi)};`;
1450
+ i = semi + 1;
400
1451
  }
401
- return { name, prelude, block: null, end: Math.min(text.length, i + 1) };
1452
+ return { declarations, nested };
1453
+ }
1454
+
1455
+ const CUSTOM_DECLARATION = /^\s*--[\w-]*\s*:/;
1456
+
1457
+ /**
1458
+ * A nested rule's selectors against its parent's, as written (CSS Nesting
1459
+ * 1, 3): a selector that starts with `&` is the parent's with the rest
1460
+ * after it, `&:hover` the parent hovered; any other `&` is `:is()` of the
1461
+ * parent; one with none is relative to the parent, a descendant unless it
1462
+ * starts with a combinator. Null where one of them comes to no selector.
1463
+ */
1464
+ function nestedSelectors(prelude: string, parents: string[]): string[] | null {
1465
+ const out: string[] = [];
1466
+ for (const part of splitTopLevel(prelude, ',')) {
1467
+ const written = part.trim();
1468
+ if (!written) return null;
1469
+ const own = hasNesting(written) ? written : `& ${written}`;
1470
+ for (const parent of parents) {
1471
+ const resolved =
1472
+ own[0] === '&' &&
1473
+ !hasNesting(own.slice(1)) &&
1474
+ !/^[\w-]/.test(own.slice(1))
1475
+ ? parent + own.slice(1)
1476
+ : replaceNesting(own, `:is(${parent})`);
1477
+ if (!isSelector(resolved)) return null;
1478
+ out.push(resolved);
1479
+ }
1480
+ }
1481
+ return out;
1482
+ }
1483
+
1484
+ /** Whether a selector has a `&` outside its strings and escapes. */
1485
+ function hasNesting(selector: string): boolean {
1486
+ return replaceNesting(selector, '') !== selector;
1487
+ }
1488
+
1489
+ /** A selector with every `&` outside its strings and escapes replaced. */
1490
+ function replaceNesting(selector: string, by: string): string {
1491
+ if (!selector.includes('&')) return selector;
1492
+ let out = '';
1493
+ let quote = '';
1494
+ for (let i = 0; i < selector.length; i += 1) {
1495
+ const c = selector[i];
1496
+ if (c === '\\') {
1497
+ out += selector.slice(i, i + 2);
1498
+ i += 1;
1499
+ } else if (quote) {
1500
+ if (c === quote) quote = '';
1501
+ out += c;
1502
+ } else if (c === '"' || c === "'") {
1503
+ quote = c;
1504
+ out += c;
1505
+ } else out += c === '&' ? by : c;
1506
+ }
1507
+ return out;
1508
+ }
1509
+
1510
+ /** A list's items at the top level: no string, escape or bracket parts
1511
+ * them. */
1512
+ function splitTopLevel(text: string, at: string): string[] {
1513
+ const out: string[] = [];
1514
+ let start = 0;
1515
+ let i = 0;
1516
+ while (i < text.length) {
1517
+ if (text[i] === at) {
1518
+ out.push(text.slice(start, i));
1519
+ start = i + 1;
1520
+ i += 1;
1521
+ } else {
1522
+ i = opens(text.charCodeAt(i)) ? componentEnd(text, i) : i + 1;
1523
+ }
1524
+ }
1525
+ out.push(text.slice(start));
1526
+ return out;
402
1527
  }
403
1528
 
404
1529
  function importUrl(prelude: string): string | null {
@@ -406,6 +1531,218 @@ function importUrl(prelude: string): string | null {
406
1531
  return m ? m[1] : null;
407
1532
  }
408
1533
 
1534
+ /** Whether an `@import`'s media list, the rest of its prelude after the
1535
+ * URL, lets it apply here: a list that only names other media, `print` or
1536
+ * `braille`, leaves the sheet out (CSS 2.1 6.3). A list that depends on the
1537
+ * width is taken to apply; an import is fetched once, not per width. */
1538
+ function importApplies(prelude: string): boolean {
1539
+ const m =
1540
+ /^\s*(?:url\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\s*\)|"[^"]*"|'[^']*')(.*)$/s.exec(
1541
+ prelude,
1542
+ );
1543
+ const list = m?.[1].trim();
1544
+ if (!list) return true;
1545
+ return parseMediaQuery(list).some((c) => c.staticPass !== false);
1546
+ }
1547
+
1548
+ /**
1549
+ * A declaration value with every `url()` in it made absolute against `base`
1550
+ * — inside a function too, `image-set()` or a `var()` fallback, and never
1551
+ * inside a string. `url(#id)`, a reference into the document itself, is left
1552
+ * as it is, as are a `data:` URL, absolute already, and a url that is none.
1553
+ */
1554
+ export function absoluteUrls(value: string, base: string): string {
1555
+ if (!/url\(/i.test(value)) return value;
1556
+ let out = '';
1557
+ let from = 0;
1558
+ let i = 0;
1559
+ while (i < value.length) {
1560
+ const c = value[i];
1561
+ if (c === '"' || c === "'") {
1562
+ i = stringEnd(value, i);
1563
+ continue;
1564
+ }
1565
+ if (c === '\\') {
1566
+ i = escapeEnd(value, i);
1567
+ continue;
1568
+ }
1569
+ if (
1570
+ (c === 'u' || c === 'U') &&
1571
+ value[i + 3] === '(' &&
1572
+ value.slice(i, i + 3).toLowerCase() === 'url' &&
1573
+ !isNameChar(value[i - 1])
1574
+ ) {
1575
+ const end = urlEnd(value, i + 3);
1576
+ const url = parseUrl(value.slice(i, end));
1577
+ if (typeof url === 'string' && url[0] !== '#' && !/^data:/i.test(url)) {
1578
+ const resolved = resolveUrl(url, base);
1579
+ if (resolved !== url) {
1580
+ out += value.slice(from, i) + cssUrl(resolved);
1581
+ from = end;
1582
+ }
1583
+ }
1584
+ i = end;
1585
+ continue;
1586
+ }
1587
+ i += 1;
1588
+ }
1589
+ return from === 0 ? value : out + value.slice(from);
1590
+ }
1591
+
1592
+ /** A URL as a `url()` that reads back as it: quoted, with the characters a
1593
+ * string cannot hold as they are escaped. */
1594
+ function cssUrl(url: string): string {
1595
+ const escaped = url.replace(/[\\"\n]/g, (c) =>
1596
+ c === '\n' ? '\\a ' : `\\${c}`,
1597
+ );
1598
+ return `url("${escaped}")`;
1599
+ }
1600
+
1601
+ /** The generic families: a `@font-face` may not take one's name, and one
1602
+ * that did would take over every element that asks for it. */
1603
+ const GENERIC_FAMILIES = new Set([
1604
+ 'serif',
1605
+ 'sans-serif',
1606
+ 'monospace',
1607
+ 'cursive',
1608
+ 'fantasy',
1609
+ 'system-ui',
1610
+ 'ui-serif',
1611
+ 'ui-sans-serif',
1612
+ 'ui-monospace',
1613
+ 'ui-rounded',
1614
+ 'math',
1615
+ 'emoji',
1616
+ 'fangsong',
1617
+ '-apple-system',
1618
+ 'blinkmacsystemfont',
1619
+ 'inherit',
1620
+ 'initial',
1621
+ 'unset',
1622
+ 'revert',
1623
+ 'default',
1624
+ ]);
1625
+
1626
+ /**
1627
+ * A `@font-face` block, or null when it names no family, or no source this
1628
+ * can ask for: `local()` names a face the system has, which this does not
1629
+ * look up, and a family that is only `local()`s is left to the system as it
1630
+ * would be anyway.
1631
+ */
1632
+ function parseFontFace(
1633
+ block: string,
1634
+ base: string | null,
1635
+ media: MediaCondition[][] | null,
1636
+ ): FontFaceRule | null {
1637
+ let family: string | null = null;
1638
+ let src: string | null = null;
1639
+ let weight: [number, number] = [400, 400];
1640
+ let style: 'normal' | 'italic' = 'normal';
1641
+ let unicodeRange: [number, number][] | null = null;
1642
+ // a descriptor that is not one is dropped, as a declaration is, and the
1643
+ // one before it stands
1644
+ for (const d of parseDeclarations(block)) {
1645
+ if (d.prop === 'font-family') {
1646
+ family = faceFamily(d.value) ?? family;
1647
+ } else if (d.prop === 'src') {
1648
+ src = d.value;
1649
+ } else if (d.prop === 'font-weight') {
1650
+ weight = faceWeight(d.value) ?? weight;
1651
+ } else if (d.prop === 'font-style') {
1652
+ const v = d.value.trim().toLowerCase();
1653
+ if (v === 'normal') style = 'normal';
1654
+ else if (v === 'italic' || /^oblique\b/.test(v)) style = 'italic';
1655
+ } else if (d.prop === 'unicode-range') {
1656
+ unicodeRange = unicodeRanges(d.value) ?? unicodeRange;
1657
+ }
1658
+ }
1659
+ if (!family || src === null) return null;
1660
+ const sources: FontFaceSource[] = [];
1661
+ for (const part of splitTopLevel(src, ',')) {
1662
+ const item = part.trim();
1663
+ if (!/^url\(/i.test(item)) continue;
1664
+ const end = urlEnd(item, 3);
1665
+ const url = parseUrl(item.slice(0, end));
1666
+ if (typeof url !== 'string') continue;
1667
+ // `format("woff2")`, the older `format(woff2)`, or a list of them, of
1668
+ // which the first says enough
1669
+ const hint = /format\(\s*(?:"([^"]*)"|'([^']*)'|([\w-]+))/i.exec(
1670
+ item.slice(end),
1671
+ );
1672
+ const format = hint ? (hint[1] ?? hint[2] ?? hint[3]).toLowerCase() : null;
1673
+ sources.push({ url: resolveUrl(url, base), format });
1674
+ }
1675
+ if (!sources.length) return null;
1676
+ return { family, sources, weight, style, unicodeRange, media };
1677
+ }
1678
+
1679
+ /** A `@font-face`'s one family name: a string, or identifiers, which one
1680
+ * space joins. Not a list, and not a generic family's name. */
1681
+ function faceFamily(value: string): string | null {
1682
+ const v = value.trim();
1683
+ let name: string;
1684
+ if (v[0] === '"' || v[0] === "'") {
1685
+ if (stringEnd(v, 0) !== v.length) return null;
1686
+ name = unquote(v).trim();
1687
+ } else {
1688
+ const words = v.split(/\s+/);
1689
+ for (const word of words) {
1690
+ if (!startsIdent(word, 0) || readIdent(word, 0).end !== word.length) {
1691
+ return null;
1692
+ }
1693
+ }
1694
+ name = words.join(' ');
1695
+ }
1696
+ if (!name || name.includes(',')) return null;
1697
+ return GENERIC_FAMILIES.has(name.toLowerCase()) ? null : name;
1698
+ }
1699
+
1700
+ /** A `@font-face`'s `font-weight`: one weight, or the range a variable face
1701
+ * covers. `auto` — the file's own range — is taken to be every weight. */
1702
+ function faceWeight(value: string): [number, number] | null {
1703
+ const parts = value.trim().toLowerCase().split(/\s+/);
1704
+ const one = (p: string): number | null => {
1705
+ if (p === 'normal') return 400;
1706
+ if (p === 'bold') return 700;
1707
+ if (!/^\d+(?:\.\d+)?$/.test(p)) return null;
1708
+ const n = Number(p);
1709
+ return n >= 1 && n <= 1000 ? n : null;
1710
+ };
1711
+ if (parts.length === 1) {
1712
+ if (parts[0] === 'auto') return [1, 1000];
1713
+ const w = one(parts[0]);
1714
+ return w === null ? null : [w, w];
1715
+ }
1716
+ if (parts.length !== 2) return null;
1717
+ const a = one(parts[0]);
1718
+ const b = one(parts[1]);
1719
+ if (a === null || b === null) return null;
1720
+ return a <= b ? [a, b] : [b, a];
1721
+ }
1722
+
1723
+ /** `unicode-range`: `U+26`, `U+0-7F`, `U+4??` — inclusive ranges, or null
1724
+ * when any part is not one, and the descriptor is dropped. */
1725
+ function unicodeRanges(value: string): [number, number][] | null {
1726
+ const out: [number, number][] = [];
1727
+ for (const part of value.split(',')) {
1728
+ const m = /^\s*u\+([0-9a-f?]{1,6})(?:-([0-9a-f]{1,6}))?\s*$/i.exec(part);
1729
+ if (!m) return null;
1730
+ let lo: number;
1731
+ let hi: number;
1732
+ if (m[1].includes('?')) {
1733
+ if (m[2] || !/^[0-9a-f]*\?+$/i.test(m[1])) return null;
1734
+ lo = parseInt(m[1].replace(/\?/g, '0'), 16);
1735
+ hi = parseInt(m[1].replace(/\?/g, 'f'), 16);
1736
+ } else {
1737
+ lo = parseInt(m[1], 16);
1738
+ hi = m[2] ? parseInt(m[2], 16) : lo;
1739
+ }
1740
+ if (hi < lo || lo > 0x10ffff) return null;
1741
+ out.push([lo, Math.min(hi, 0x10ffff)]);
1742
+ }
1743
+ return out.length ? out : null;
1744
+ }
1745
+
409
1746
  /**
410
1747
  * A `@media` prelude, reduced to the width and colour-scheme tests this can
411
1748
  * honour. Each comma group is one condition and they are OR-ed; within a
@@ -425,6 +1762,19 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
425
1762
  .replace(/^not\s+/i, '')
426
1763
  .replace(/^only\s+/i, '');
427
1764
  if (!term) continue;
1765
+ // Media Queries 4's ranges, which Tailwind 4 writes its breakpoints
1766
+ // in: `(width >= 48rem)`, `(40rem <= width < 60rem)`
1767
+ const range = widthRange(term);
1768
+ if (range) {
1769
+ if (range.min !== undefined) {
1770
+ condition.min = Math.max(condition.min ?? 0, range.min);
1771
+ }
1772
+ if (range.max !== undefined) {
1773
+ condition.max = Math.min(condition.max ?? Infinity, range.max);
1774
+ }
1775
+ sawWidth = true;
1776
+ continue;
1777
+ }
428
1778
  const feature = /^\(\s*([a-z-]+)\s*:\s*([^)]+?)\s*\)$/i.exec(term);
429
1779
  if (feature) {
430
1780
  const key = feature[1].toLowerCase();
@@ -460,7 +1810,11 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
460
1810
  }
461
1811
  const type = term.toLowerCase();
462
1812
  if (type === 'screen' || type === 'all') continue;
463
- if (type === 'print' || type === 'speech') pass = false;
1813
+ // any other medium is not this one: `print`, `speech`, and the ones
1814
+ // CSS 2.1 named that Media Queries retired, `braille`, `embossed`,
1815
+ // `handheld`, `projection`, `tty`, `tv`. A term that is no name is
1816
+ // left as it was.
1817
+ if (/^[a-z-]+$/.test(type)) pass = false;
464
1818
  }
465
1819
  if (negated) {
466
1820
  // `not` over a scheme is the other scheme. `not` over a width range