@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
@@ -21,12 +21,36 @@ import { Element as DomElement, isTag } from 'domhandler';
21
21
  import type { Element } from 'domhandler';
22
22
 
23
23
  import { attr, tagOf } from '../dom.js';
24
- import { mediaMatches } from './parse.js';
24
+ import { svgSizeHint } from '../svg.js';
25
+ import {
26
+ asciiLower,
27
+ escapeEnd,
28
+ mediaMatches,
29
+ readIdent,
30
+ startsIdent,
31
+ } from './parse.js';
25
32
  import type { Declaration, StyleRule, Stylesheet } from './parse.js';
26
- import { applyDeclaration, blockify, inherit, initialStyle } from './style.js';
33
+ import {
34
+ applyDeclaration,
35
+ blockify,
36
+ settleContentVisibility,
37
+ settleOverflow,
38
+ settleClamp,
39
+ copyStyle,
40
+ decorate,
41
+ inherit,
42
+ initialOne,
43
+ initialStyle,
44
+ isInherited,
45
+ } from './style.js';
27
46
  import type { ComputedStyle, RootLook } from './style.js';
47
+ import type { FontFamilies } from '../fonts.js';
28
48
  import { parseDeclarations } from './parse.js';
49
+ import { CounterStyles, counterStyleRule } from './counter-styles.js';
50
+ import type { CounterStyleRule } from './counter-styles.js';
29
51
  import type { UnitContext } from './values.js';
52
+ import { customProperties, substituteIn } from './vars.js';
53
+ import type { CustomProps } from './vars.js';
30
54
 
31
55
  /** Where a declaration came from. Higher wins before specificity is asked. */
32
56
  const enum Origin {
@@ -40,6 +64,8 @@ const enum Origin {
40
64
 
41
65
  interface Candidate {
42
66
  origin: Origin;
67
+ /** The rule's cascade layer, null for none (`StyleRule.layer`). */
68
+ layer: readonly number[] | null;
43
69
  specificity: number;
44
70
  order: number;
45
71
  declarations: Declaration[];
@@ -59,6 +85,13 @@ type CssSelectAdapter = typeof DomUtils & {
59
85
  isVisited(el: Element): boolean;
60
86
  };
61
87
 
88
+ /** A style handed to every element with the same sharing key, and the key
89
+ * that element's children share under (`Cascade.sharedStyleFor`). */
90
+ export interface SharedStyle {
91
+ style: ComputedStyle;
92
+ key: number;
93
+ }
94
+
62
95
  /** A compiled matcher, kept beside the rule it came from. */
63
96
  interface IndexedRule {
64
97
  rule: StyleRule;
@@ -66,8 +99,26 @@ interface IndexedRule {
66
99
  /** Set once compilation has been attempted, so a selector `css-select`
67
100
  * refuses is not recompiled once per element for the rest of the pass. */
68
101
  compiled: boolean;
102
+ /** Which rule this is, in a sharing key (`Cascade.sharedStyleFor`). */
103
+ id: number;
69
104
  }
70
105
 
106
+ /**
107
+ * What makes a selector's match depend on more than the element's own tag and
108
+ * attributes and its ancestors' — its siblings, where it sits among them,
109
+ * what it contains, or where the document is scrolled to. Two elements that
110
+ * look alike from the root down can still differ under a rule with one of
111
+ * these, so an element such a rule could reach does not share its style
112
+ * (`Cascade.sharedStyleFor`). css-select spells some of them under other
113
+ * names, and those count too: `:checked` and `:selected` read which option
114
+ * comes first, `:disabled` and `:enabled` which legend does, and `:parent`
115
+ * and `:contains()` the element's contents. Over-broad on purpose: a `+` or
116
+ * `~` inside an attribute value, or the `~=` operator, costs sharing and
117
+ * nothing else.
118
+ */
119
+ const UNSHAREABLE =
120
+ /[+~]|:(?:first|last|only)-(?:child|of-type)|:nth-|:empty|:blank|:has\(|:focus-within|:target|:scope|:(?:checked|selected|disabled|enabled|parent)\b|:i?contains\(/;
121
+
71
122
  /**
72
123
  * Rules bucketed by the key of their rightmost compound selector. An element
73
124
  * only ever tries `id`, its classes, its tag and the universal bucket.
@@ -80,13 +131,59 @@ class RuleIndex {
80
131
  /** Whether any rule in here is pointer-sensitive, so the renderer knows
81
132
  * whether a pointer move can change the cascade at all. */
82
133
  hoverSensitive = false;
134
+ /** The buckets holding a rule an element cannot share its style under
135
+ * (`UNSHAREABLE`): the ids, classes and tags whose elements compute
136
+ * their own, and whether the universal bucket makes every element do so. */
137
+ readonly ownStyleIds = new Set<string>();
138
+ readonly ownStyleClasses = new Set<string>();
139
+ readonly ownStyleTags = new Set<string>();
140
+ ownStyleEverywhere = false;
141
+ /** How many rules are in here, so an empty index costs one comparison. */
142
+ size = 0;
143
+ private _nextId = 0;
144
+
145
+ /**
146
+ * Whether a bucket here could hold a rule for `el`, by its tag, id and
147
+ * classes and without matching anything: an index of a rule or two is
148
+ * asked of every element — the user-agent sheet's `q::before` and
149
+ * `q::after` made every element of a document split its `class` twice —
150
+ * and almost none of them has a rule in it.
151
+ */
152
+ reaches(el: Element): boolean {
153
+ if (this.universal.length || this.byTag.has(tagOf(el))) return true;
154
+ if (this.byId.size) {
155
+ const id = attr(el, 'id');
156
+ if (id && this.byId.has(id)) return true;
157
+ }
158
+ if (this.byClass.size) {
159
+ const className = attr(el, 'class');
160
+ if (className) {
161
+ for (const name of className.split(/\s+/)) {
162
+ if (name && this.byClass.has(name)) return true;
163
+ }
164
+ }
165
+ }
166
+ return false;
167
+ }
83
168
 
84
169
  add(rule: StyleRule): void {
85
- const indexed: IndexedRule = { rule, match: null, compiled: false };
170
+ this.size += 1;
171
+ const indexed: IndexedRule = {
172
+ rule,
173
+ match: null,
174
+ compiled: false,
175
+ id: this._nextId++,
176
+ };
86
177
  if (rule.selector.includes(':hover') || rule.selector.includes(':active')) {
87
178
  this.hoverSensitive = true;
88
179
  }
89
180
  const key = rightmostKey(rule.selector);
181
+ if (UNSHAREABLE.test(rule.selector)) {
182
+ if (key.kind === 'id') this.ownStyleIds.add(key.name);
183
+ else if (key.kind === 'class') this.ownStyleClasses.add(key.name);
184
+ else if (key.kind === 'tag') this.ownStyleTags.add(key.name);
185
+ else this.ownStyleEverywhere = true;
186
+ }
90
187
  const bucket =
91
188
  key.kind === 'id'
92
189
  ? mapBucket(this.byId, key.name)
@@ -99,6 +196,204 @@ class RuleIndex {
99
196
  }
100
197
  }
101
198
 
199
+ /** The pseudo-elements a rule can style here. */
200
+ type PseudoElement =
201
+ 'before' | 'after' | 'first-letter' | 'first-line' | 'marker';
202
+
203
+ /** A selector's trailing `::before`, `::after`, `::first-letter`,
204
+ * `::first-line` or `::marker`, or CSS 2's single-colon spelling of any of
205
+ * its four. */
206
+ const PSEUDO_ELEMENT = /::?(before|after|first-letter|first-line)$|::marker$/i;
207
+
208
+ /**
209
+ * A rule for a pseudo-element, as the pseudo-element it styles and a rule
210
+ * for the element it hangs off, which is what gets matched. The specificity
211
+ * is the whole selector's, the pseudo-element counted in. `p::before`
212
+ * matches `p`; `p ::before` and `p > ::before` have nothing left of their
213
+ * last compound and match any child, `p *` and `p > *`.
214
+ */
215
+ function splitPseudoElement(
216
+ rule: StyleRule,
217
+ ): { which: PseudoElement; rule: StyleRule } | null {
218
+ const m = PSEUDO_ELEMENT.exec(rule.selector);
219
+ if (!m) return null;
220
+ const head = rule.selector.slice(0, m.index);
221
+ const trimmed = head.trimEnd();
222
+ const selector = !trimmed
223
+ ? '*'
224
+ : head !== trimmed || /[>+~]$/.test(trimmed)
225
+ ? `${trimmed} *`
226
+ : trimmed;
227
+ return {
228
+ which: (m[1] ?? 'marker').toLowerCase() as PseudoElement,
229
+ rule: { ...rule, selector },
230
+ };
231
+ }
232
+
233
+ /**
234
+ * The compounds of a selector that test `:hover`, each without it (and
235
+ * without an `:active` beside it), and whether one is followed by a sibling
236
+ * combinator, which reaches the element's later siblings. `nested` where a
237
+ * `:hover` sits inside a functional pseudo-class — `:not(:hover)`,
238
+ * `:is(:hover)` — and reaches elements no compound names.
239
+ *
240
+ * A `:hover` in a compound's `:has()` is the one exception, since what it
241
+ * reaches is known: the compound, without its `:has()`, is an ancestor of
242
+ * the element the pointer is over — or an earlier sibling of it or of an
243
+ * ancestor, where the argument starts at a sibling (Selectors 4, 4.5). So
244
+ * it is given as an anchor (`has`) rather than making the rule `nested`.
245
+ *
246
+ * `:active` is not the pointer's here: nothing sets it (`setPointer` is
247
+ * handed none), so a selector that tests only it never changes as the
248
+ * pointer moves — Wikipedia's buttons' `:focus:not(:active)` among them.
249
+ * A press that sets it would have to be counted here too.
250
+ */
251
+ export function pointerCompounds(selector: string): {
252
+ compounds: string[];
253
+ siblings: boolean;
254
+ nested: boolean;
255
+ has: { anchor: string; siblings: boolean }[];
256
+ } {
257
+ const out = {
258
+ compounds: [] as string[],
259
+ siblings: false,
260
+ nested: false,
261
+ has: [] as { anchor: string; siblings: boolean }[],
262
+ };
263
+ // the compounds at the top level, each with the combinator after it
264
+ const parts: { text: string; next: string }[] = [];
265
+ let start = 0;
266
+ let depth = 0;
267
+ let quote = '';
268
+ const split = (end: number, next: string): void => {
269
+ const text = selector.slice(start, end).trim();
270
+ if (text) parts.push({ text, next });
271
+ else if (parts.length && next !== ' ') parts[parts.length - 1].next = next;
272
+ };
273
+ for (let i = 0; i < selector.length; i += 1) {
274
+ const c = selector[i];
275
+ if (quote) {
276
+ if (c === quote && selector[i - 1] !== '\\') quote = '';
277
+ continue;
278
+ }
279
+ if (c === '\\') i = escapeEnd(selector, i) - 1;
280
+ else if (c === '"' || c === "'") quote = c;
281
+ else if (c === '(' || c === '[') depth += 1;
282
+ else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
283
+ else if (depth === 0 && /[\s>+~]/.test(c)) {
284
+ split(i, c === '+' || c === '~' || c === '>' ? c : ' ');
285
+ start = i + 1;
286
+ }
287
+ }
288
+ split(selector.length, '');
289
+ for (const { text, next } of parts) {
290
+ let bare = '';
291
+ let pointer = false;
292
+ depth = 0;
293
+ quote = '';
294
+ /** Where a top-level `:has(` starts in `bare`, and its argument in
295
+ * `text`, while one is open. */
296
+ let hasAt = -1;
297
+ let argAt = -1;
298
+ let hasPointer = false;
299
+ let anchored = false;
300
+ const args: string[] = [];
301
+ for (let i = 0; i < text.length; i += 1) {
302
+ const c = text[i];
303
+ if (quote) {
304
+ if (c === quote && text[i - 1] !== '\\') quote = '';
305
+ bare += c;
306
+ continue;
307
+ }
308
+ if (c === '\\') {
309
+ const end = escapeEnd(text, i);
310
+ bare += text.slice(i, end);
311
+ i = end - 1;
312
+ continue;
313
+ }
314
+ if (c === '"' || c === "'") quote = c;
315
+ else if (c === '(' || c === '[') {
316
+ if (depth === 0 && c === '(' && /:has$/i.test(bare)) {
317
+ hasAt = bare.length - 4;
318
+ argAt = i + 1;
319
+ hasPointer = false;
320
+ }
321
+ depth += 1;
322
+ } else if (c === ')' || c === ']') {
323
+ depth = Math.max(0, depth - 1);
324
+ if (depth === 0 && hasAt >= 0 && c === ')') {
325
+ // the compound is kept without it: its argument is what the
326
+ // pointer changes
327
+ if (hasPointer) {
328
+ anchored = true;
329
+ args.push(text.slice(argAt, i));
330
+ bare = bare.slice(0, hasAt);
331
+ } else bare += c;
332
+ hasAt = -1;
333
+ continue;
334
+ }
335
+ } else if (c === ':' && text[i + 1] !== ':') {
336
+ const m = POINTER_PSEUDO_AT.exec(text.slice(i));
337
+ if (m) {
338
+ const hover = m[0].length === 6;
339
+ if (depth > 0) {
340
+ // whatever holds it in the argument, the element it tests is
341
+ // in the anchor's subtree or a later sibling's
342
+ if (hover && hasAt >= 0) hasPointer = true;
343
+ else if (hover) out.nested = true;
344
+ } else {
345
+ if (hover) pointer = true;
346
+ i += m[0].length - 1;
347
+ continue;
348
+ }
349
+ }
350
+ }
351
+ bare += c;
352
+ }
353
+ if (anchored) {
354
+ out.has.push({
355
+ anchor: bare.trim() || '*',
356
+ siblings: args.some((arg) =>
357
+ arg.split(',').some((one) => /^\s*[+~]/.test(one)),
358
+ ),
359
+ });
360
+ }
361
+ if (!pointer) continue;
362
+ out.compounds.push(bare.trim() || '*');
363
+ if (next === '+' || next === '~') out.siblings = true;
364
+ }
365
+ return out;
366
+ }
367
+
368
+ /** `:hover` or `:active` at the start of a string, and nothing longer. */
369
+ const POINTER_PSEUDO_AT = /^:(?:hover|active)(?![\w-])/i;
370
+
371
+ /** `:hover` or `:active` anywhere in a selector. */
372
+ const POINTER_PSEUDO = /:(?:hover|active)(?![\w-])/i;
373
+
374
+ /**
375
+ * A selector compiled to a matcher over this adapter. css-select keeps the
376
+ * answers of a `:has()` above the subject, or of the ancestors a
377
+ * descendant combinator tried under one, for as long as the matcher lives
378
+ * (`cacheResults`) — which is the cascade's life, and a pointer move is no
379
+ * new cascade. So a selector that tests the pointer keeps none: with them,
380
+ * `#box:has(a:hover) .m` answered what it did before the first move, for
381
+ * good.
382
+ */
383
+ function compileSelector(
384
+ selector: string,
385
+ adapter: CssSelectAdapter,
386
+ ): (el: Element) => boolean {
387
+ return compile(noEmptyWords(selector), {
388
+ adapter,
389
+ xmlMode: false,
390
+ pseudos: PSEUDOS,
391
+ cacheResults: !POINTER_PSEUDO.test(selector),
392
+ } as unknown as Parameters<typeof compile>[1]) as unknown as (
393
+ node: Element,
394
+ ) => boolean;
395
+ }
396
+
102
397
  function mapBucket(
103
398
  map: Map<string, IndexedRule[]>,
104
399
  key: string,
@@ -132,12 +427,23 @@ function rightmostKey(selector: string): {
132
427
  if (c === quote && selector[i - 1] !== '\\') quote = '';
133
428
  continue;
134
429
  }
135
- if (c === '"' || c === "'") quote = c;
430
+ // an escaped character is part of a name, whatever it is — and a hex
431
+ // escape takes the space after it: `.c\6c ass` is the class `class`
432
+ if (c === '\\') i = escapeEnd(selector, i) - 1;
433
+ else if (c === '"' || c === "'") quote = c;
136
434
  else if (c === '(' || c === '[') depth += 1;
137
435
  else if (c === ')' || c === ']') depth = Math.max(0, depth - 1);
138
436
  else if (
139
437
  depth === 0 &&
140
- (c === ' ' || c === '>' || c === '+' || c === '~')
438
+ (c === ' ' ||
439
+ c === '>' ||
440
+ c === '+' ||
441
+ c === '~' ||
442
+ // CSS's other white space: `div\fp` is a descendant too
443
+ c === '\t' ||
444
+ c === '\n' ||
445
+ c === '\r' ||
446
+ c === '\f')
141
447
  ) {
142
448
  start = i + 1;
143
449
  }
@@ -149,24 +455,23 @@ function rightmostKey(selector: string): {
149
455
  let i = 0;
150
456
  while (i < compound.length) {
151
457
  const c = compound[i];
152
- if (c === '#') {
153
- const end = identEnd(compound, i + 1);
154
- if (!id) id = compound.slice(i + 1, end);
155
- i = end;
156
- } else if (c === '.') {
157
- const end = identEnd(compound, i + 1);
158
- if (!cls) cls = compound.slice(i + 1, end);
159
- i = end;
458
+ // Names as the matcher reads them, escapes resolved: `.md\:flex` is
459
+ // the class `md:flex`, and filed under its first half it never matched.
460
+ if (c === '#' || c === '.') {
461
+ const name = readIdent(compound, i + 1);
462
+ if (c === '#' && !id) id = name.value;
463
+ if (c === '.' && !cls) cls = name.value;
464
+ i = Math.max(name.end, i + 1);
160
465
  } else if (c === '[') {
161
466
  i = balancedEnd(compound, i, '[', ']');
162
467
  } else if (c === ':') {
163
468
  const skip = compound[i + 1] === ':' ? 2 : 1;
164
- const end = identEnd(compound, i + skip);
469
+ const end = Math.max(readIdent(compound, i + skip).end, i + skip);
165
470
  i = compound[end] === '(' ? balancedEnd(compound, end, '(', ')') : end;
166
- } else if (/[a-zA-Z]/.test(c)) {
167
- const end = identEnd(compound, i);
168
- if (!tag) tag = compound.slice(i, end).toLowerCase();
169
- i = end;
471
+ } else if (startsIdent(compound, i)) {
472
+ const name = readIdent(compound, i);
473
+ if (!tag) tag = name.value.toLowerCase();
474
+ i = name.end;
170
475
  } else {
171
476
  i += 1;
172
477
  }
@@ -177,12 +482,6 @@ function rightmostKey(selector: string): {
177
482
  return { kind: 'any', name: '' };
178
483
  }
179
484
 
180
- function identEnd(text: string, from: number): number {
181
- let i = from;
182
- while (i < text.length && /[a-zA-Z0-9_\-\\]/.test(text[i])) i += 1;
183
- return i;
184
- }
185
-
186
485
  function balancedEnd(
187
486
  text: string,
188
487
  from: number,
@@ -200,6 +499,12 @@ function balancedEnd(
200
499
  return text.length;
201
500
  }
202
501
 
502
+ /** What `Cascade.firstLetterRules` found for an element. */
503
+ export interface FirstLetterRules {
504
+ readonly el: Element;
505
+ readonly candidates: readonly unknown[];
506
+ }
507
+
203
508
  /** What the pointer is over, for `:hover`. A chain rather than one element,
204
509
  * because `li:hover a` has to light up while the pointer is on the `li`. */
205
510
  export interface PointerState {
@@ -209,13 +514,80 @@ export interface PointerState {
209
514
 
210
515
  const NO_POINTER: PointerState = { hovered: new Set(), active: new Set() };
211
516
 
517
+ /**
518
+ * The face a style's font-relative units are measured in: its family list,
519
+ * size, weight and slant, the four that pick one. A family's faces can be
520
+ * different fonts altogether — a page's `@font-face` rules may set its
521
+ * bold in another file — so an `ex` in bold text is the bold face's.
522
+ */
523
+ export interface MetricFace {
524
+ family: string;
525
+ size: number;
526
+ weight: number;
527
+ style: ComputedStyle['fontStyle'];
528
+ }
529
+
530
+ /** What a face measures, where the fonts can say. */
531
+ export type FaceMetric = (face: MetricFace) => number | null;
532
+
533
+ /** What a style's face is read from: a computed style, or the part of one
534
+ * a declaration's font-relative units are measured against. */
535
+ type FaceSource = Pick<
536
+ ComputedStyle,
537
+ 'fontFamily' | 'fontSize' | 'fontWeight' | 'fontStyle'
538
+ >;
539
+
540
+ /** …and its line height, for `lh`. */
541
+ type LineSource = FaceSource &
542
+ Pick<ComputedStyle, 'lineHeight' | 'lineHeightIsLength'>;
543
+
544
+ function faceOf(style: FaceSource): MetricFace {
545
+ return {
546
+ family: style.fontFamily,
547
+ size: style.fontSize,
548
+ weight: style.fontWeight,
549
+ style: style.fontStyle,
550
+ };
551
+ }
552
+
553
+ function faceKey(style: FaceSource): string {
554
+ return `${style.fontFamily}\u0001${style.fontSize}\u0001${style.fontWeight}\u0001${style.fontStyle}`;
555
+ }
556
+
212
557
  /**
213
558
  * A cascade over a fixed set of stylesheets. Rebuilt when the sheets change;
214
559
  * re-run when the DOM, the viewport band or the pointer state does.
215
560
  */
216
561
  export class Cascade {
217
562
  private _index = new RuleIndex();
563
+ /** The rules for pseudo-elements, kept apart: they never style the
564
+ * element itself, and a document with none of them asks nothing. */
565
+ private _pseudo: Record<PseudoElement, RuleIndex> = {
566
+ before: new RuleIndex(),
567
+ after: new RuleIndex(),
568
+ 'first-letter': new RuleIndex(),
569
+ 'first-line': new RuleIndex(),
570
+ marker: new RuleIndex(),
571
+ };
218
572
  private _adapter: CssSelectAdapter;
573
+ /** The compounds of the selectors that test the pointer, each without
574
+ * its `:hover` or `:active` (`pointerCompounds`), and their matchers,
575
+ * compiled when a pointer move first asks (`hoverTouches`). */
576
+ private _hoverCompounds = new Set<string>();
577
+ private _hoverMatchers: ((el: Element) => boolean)[] | null = null;
578
+ /** Whether a pointer move can be restyled where it happened
579
+ * (`HtmlViewNode._hoverInPlace`): false where a selector tests the
580
+ * pointer inside a functional pseudo-class, which reaches elements no
581
+ * compound names. */
582
+ hoverLocal = true;
583
+ /** Whether a compound that tests the pointer is followed by a sibling
584
+ * combinator, so an element's later siblings restyle with it. */
585
+ hoverSiblings = false;
586
+ /** The compounds whose `:has()` tests the pointer, each without it, and
587
+ * whether its argument starts at a sibling (`hoverAnchors`). */
588
+ private _hoverAnchors = new Map<string, boolean>();
589
+ private _anchorMatchers:
590
+ { match: (el: Element) => boolean; siblings: boolean }[] | null = null;
219
591
  private _pointer: PointerState = NO_POINTER;
220
592
  readonly initial: ComputedStyle;
221
593
  readonly look: RootLook;
@@ -229,6 +601,35 @@ export class Cascade {
229
601
  /** Every width at which some `@media` rule changes its mind, in CSS
230
602
  * pixels — the unit the author wrote them in. */
231
603
  readonly breakpoints: number[];
604
+ /** The counter styles the sheets define, over the predefined ones. */
605
+ readonly counterStyles: CounterStyles;
606
+
607
+ /** A face's x-height, for `ex`, where the fonts can say. */
608
+ private _xHeightOf: FaceMetric | null;
609
+ private _xHeights = new Map<string, number>();
610
+ /** The advance of a face's "0", for `ch`, likewise. */
611
+ private _zeroWidthOf: FaceMetric | null;
612
+ private _zeroWidths = new Map<string, number>();
613
+ /** A face's own line height, for `lh` where `line-height` is `normal`,
614
+ * likewise. */
615
+ private _normalLineOf: FaceMetric | null;
616
+ private _normalLines = new Map<string, number>();
617
+ /** Whether any declaration sets a custom property or reads one: without
618
+ * one, no element's style asks about them. */
619
+ private _vars = false;
620
+ /** Whether any declaration is in a unit of the viewport's width — `vw`,
621
+ * `vmin`, `vmax` — or of its height, `vh` and the same two: a style
622
+ * holds such a length as a number, so it is computed again when that
623
+ * side of the viewport moves (`HtmlViewNode._update`). */
624
+ readsViewportWidth = false;
625
+ readsViewportHeight = false;
626
+ /** The families the document loads itself (`fonts.ts`), or null for a
627
+ * document with no `@font-face`. */
628
+ private _families: FontFamilies | null;
629
+ private _mapFamilies: ((list: string) => string) | undefined;
630
+ /** Whether any declaration has a length in `lh` or `rlh`: only then is
631
+ * the line height settled ahead of the declarations that read it. */
632
+ private _lh = false;
232
633
 
233
634
  constructor(
234
635
  sheets: Stylesheet[],
@@ -236,18 +637,44 @@ export class Cascade {
236
637
  viewportWidth: number,
237
638
  viewportHeight: number,
238
639
  scale = 1,
640
+ xHeight: FaceMetric | null = null,
641
+ zeroWidth: FaceMetric | null = null,
642
+ normalLine: FaceMetric | null = null,
643
+ families: FontFamilies | null = null,
239
644
  ) {
645
+ this._xHeightOf = xHeight;
646
+ this._zeroWidthOf = zeroWidth;
647
+ this._normalLineOf = normalLine;
648
+ this._families = families;
649
+ this._mapFamilies = families
650
+ ? (list: string) => families.map(list)
651
+ : undefined;
240
652
  this.look = look;
241
- this.initial = initialStyle(look);
653
+ this.initial = initialStyle(look, scale);
242
654
  this.viewportWidth = viewportWidth;
243
655
  this.viewportHeight = viewportHeight;
244
656
  this.scale = scale;
245
657
  const breakpoints = new Set<number>();
658
+ const counterStyles = new Map<string, CounterStyleRule>();
246
659
  for (const sheet of sheets) {
247
- for (const rule of sheet.rules) this._index.add(rule);
660
+ // a counter style's rule whole over any before it of its name
661
+ for (const { prelude, declarations } of sheet.counterStyles ?? []) {
662
+ const style = counterStyleRule(prelude, declarations);
663
+ if (style) counterStyles.set(style.name, style.rule);
664
+ }
665
+ for (const rule of sheet.rules) {
666
+ if (!this._vars && usesVars(rule.declarations)) this._vars = true;
667
+ this._noteViewportUnits(rule.declarations);
668
+ if (!this._lh && usesLh(rule.declarations)) this._lh = true;
669
+ const pseudo = splitPseudoElement(rule);
670
+ if (pseudo) this._pseudo[pseudo.which].add(pseudo.rule);
671
+ else this._index.add(rule);
672
+ this._noteHover((pseudo?.rule ?? rule).selector);
673
+ }
248
674
  for (const bp of sheet.breakpoints) breakpoints.add(bp);
249
675
  }
250
676
  this.breakpoints = [...breakpoints].sort((a, b) => a - b);
677
+ this.counterStyles = new CounterStyles(counterStyles);
251
678
  // css-select's default adapter is domutils; `isHovered` and `isActive`
252
679
  // are its documented hooks for exactly this, so `:hover` costs an
253
680
  // adapter field rather than a fork of the matcher.
@@ -258,21 +685,127 @@ export class Cascade {
258
685
  this._adapter = {
259
686
  ...DomUtils,
260
687
  isTag,
688
+ // a type selector sees `tagOf`'s name — XHTML's `<svg:svg>` is an
689
+ // `svg` — asked only of a name that could be one: this runs for every
690
+ // type selector tried, and the parser has lowercased the rest
691
+ getName: (el: Element) =>
692
+ el.name.endsWith(':svg') ? tagOf(el) : el.name,
261
693
  isHovered: (el: Element) => this._pointer.hovered.has(el),
262
694
  isActive: (el: Element) => this._pointer.active.has(el),
263
695
  isVisited: () => false,
264
696
  };
265
697
  }
266
698
 
699
+ /** Whether declarations read the viewport's width or height (`vw`,
700
+ * `vh`, `vmin`, `vmax`), noted on the cascade. */
701
+ private _noteViewportUnits(declarations: readonly Declaration[]): void {
702
+ if (this.readsViewportWidth && this.readsViewportHeight) return;
703
+ for (const d of declarations) {
704
+ if (!VIEWPORT_UNIT.test(d.value)) continue;
705
+ for (const m of d.value.matchAll(VIEWPORT_UNITS)) {
706
+ const unit = m[1].toLowerCase();
707
+ if (unit !== 'vh') this.readsViewportWidth = true;
708
+ if (unit !== 'vw') this.readsViewportHeight = true;
709
+ }
710
+ }
711
+ }
712
+
267
713
  /** Whether a pointer move can change what this cascade produces. */
268
714
  get hoverSensitive(): boolean {
269
- return this._index.hoverSensitive;
715
+ return (
716
+ this._index.hoverSensitive ||
717
+ this._pseudo.before.hoverSensitive ||
718
+ this._pseudo.after.hoverSensitive ||
719
+ this._pseudo['first-letter'].hoverSensitive ||
720
+ this._pseudo['first-line'].hoverSensitive ||
721
+ this._pseudo.marker.hoverSensitive
722
+ );
270
723
  }
271
724
 
272
725
  setPointer(pointer: PointerState): void {
273
726
  this._pointer = pointer;
274
727
  }
275
728
 
729
+ /**
730
+ * Whether the pointer entering or leaving `el` can change a style: one of
731
+ * the compounds a selector tests the pointer in matches it, the pointer
732
+ * aside. A move between two paragraphs of a page whose only such rule is
733
+ * `a:hover` touches nothing, and restyles nothing.
734
+ */
735
+ hoverTouches(el: Element): boolean {
736
+ this._hoverMatchers ??= [...this._hoverCompounds].flatMap((c) => {
737
+ const match = this._compile(c);
738
+ return match ? [match] : [];
739
+ });
740
+ for (const match of this._hoverMatchers) if (match(el)) return true;
741
+ return false;
742
+ }
743
+
744
+ /**
745
+ * The elements whose `:has()` may have changed its answer as `el` was
746
+ * hovered or left: the ancestors of it a compound whose `:has()` tests
747
+ * the pointer names, and their earlier siblings and its own where the
748
+ * argument starts at a sibling — a superset, which is restyled and found
749
+ * the same where it did not. Into `into`.
750
+ */
751
+ hoverAnchors(el: Element, into: Element[]): void {
752
+ if (!this._hoverAnchors.size) return;
753
+ this._anchorMatchers ??= [...this._hoverAnchors].flatMap(
754
+ ([anchor, siblings]) => {
755
+ const match = this._compile(anchor);
756
+ return match ? [{ match, siblings }] : [];
757
+ },
758
+ );
759
+ const siblings = this._anchorMatchers.some((m) => m.siblings);
760
+ for (let at: Element | null = el; at;) {
761
+ if (at !== el) {
762
+ for (const { match } of this._anchorMatchers) {
763
+ if (match(at)) {
764
+ into.push(at);
765
+ break;
766
+ }
767
+ }
768
+ }
769
+ if (siblings) {
770
+ for (let s = at.prev; s; s = s.prev) {
771
+ if (!isTag(s as Element)) continue;
772
+ for (const { match, siblings: apart } of this._anchorMatchers) {
773
+ if (apart && match(s as Element)) {
774
+ into.push(s as Element);
775
+ break;
776
+ }
777
+ }
778
+ }
779
+ }
780
+ const parent: Element['parent'] = at.parent;
781
+ at = parent && isTag(parent as Element) ? (parent as Element) : null;
782
+ }
783
+ }
784
+
785
+ private _noteHover(selector: string): void {
786
+ if (!/:(?:hover|active)/i.test(selector)) return;
787
+ const found = pointerCompounds(selector);
788
+ if (found.nested) this.hoverLocal = false;
789
+ if (found.siblings) this.hoverSiblings = true;
790
+ for (const c of found.compounds) this._hoverCompounds.add(c);
791
+ for (const { anchor, siblings } of found.has) {
792
+ this._hoverAnchors.set(
793
+ anchor,
794
+ siblings || (this._hoverAnchors.get(anchor) ?? false),
795
+ );
796
+ }
797
+ }
798
+
799
+ /** A selector compiled as a rule's is, or null where css-select refuses
800
+ * it, as a rule it refuses drops out of the cascade. */
801
+ private _compile(selector: string): ((el: Element) => boolean) | null {
802
+ try {
803
+ return compileSelector(selector, this._adapter);
804
+ } catch {
805
+ return null;
806
+ }
807
+ }
808
+
276
809
  /** Which media band a device-pixel width falls in. Two widths in the same
277
810
  * band produce identical styles, which is what lets a resize skip
278
811
  * restyling. */
@@ -286,18 +819,361 @@ export class Cascade {
286
819
  return band;
287
820
  }
288
821
 
822
+ /** Computed styles by sharing key, for one build (`sharedStyleFor`): one
823
+ * map for the elements shared without matching, one for the elements
824
+ * shared by what they matched. */
825
+ private _shared = new Map<string, SharedStyle>();
826
+ private _sharedByMatch = new Map<string, SharedStyle>();
827
+ private _nextShareKey = 1;
828
+ /** Custom property sets by what made them, for one build (`_customFor`). */
829
+ private _customs = new Map<string, CustomProps>();
830
+ private _ids = new WeakMap<object, number>();
831
+ private _nextId = 1;
832
+
833
+ /** A box tree is about to be built: the styles shared in the last build
834
+ * were computed against a pointer and a viewport that may have moved. */
835
+ beginSharing(): void {
836
+ this._shared.clear();
837
+ this._sharedByMatch.clear();
838
+ this._customs.clear();
839
+ }
840
+
841
+ /**
842
+ * An element's custom properties: its parent's, or its own over them —
843
+ * shared by every element whose parent has the same ones and whose own
844
+ * come from the same declarations. Tailwind 4 sets thirty-five of them on
845
+ * every element (`*, ::before, ::after { --tw-shadow: 0 0 #0000; … }`),
846
+ * and a set per element was a fifth of building the boxes; it also kept
847
+ * each element's `var()`s from being replaced once for its siblings,
848
+ * which `substituteIn` remembers by the set they were read against.
849
+ */
850
+ private _customFor(
851
+ parentStyle: ComputedStyle,
852
+ candidates: Candidate[],
853
+ ): CustomProps | null {
854
+ let key = '';
855
+ for (const c of candidates) {
856
+ if (!this._hasCustom(c)) continue;
857
+ key += `${this._idOf(c.declarations)}:${c.only},`;
858
+ }
859
+ const parent = parentStyle.custom;
860
+ if (!key) return parent;
861
+ key = `${parent ? this._idOf(parent) : 0}|${key}`;
862
+ const hit = this._customs.get(key);
863
+ if (hit) return hit;
864
+ const own = new Map<string, string>();
865
+ for (const c of candidates) {
866
+ for (const d of pick(c)) if (d.custom) own.set(d.prop, d.value);
867
+ }
868
+ const made = customProperties(own, parent);
869
+ this._customs.set(key, made);
870
+ return made;
871
+ }
872
+
873
+ /** Whether a candidate sets a custom property. */
874
+ private _hasCustom(c: Candidate): boolean {
875
+ if (c.only >= 0) return !!c.declarations[c.only].custom;
876
+ let has = CUSTOM_IN.get(c.declarations);
877
+ if (has === undefined) {
878
+ has = c.declarations.some((d) => !!d.custom);
879
+ CUSTOM_IN.set(c.declarations, has);
880
+ }
881
+ return has;
882
+ }
883
+
884
+ /** A number for an object, to key a string by. */
885
+ private _idOf(of: object): number {
886
+ let id = this._ids.get(of);
887
+ if (id === undefined) {
888
+ id = this._nextId++;
889
+ this._ids.set(of, id);
890
+ }
891
+ return id;
892
+ }
893
+
894
+ /**
895
+ * `styleFor`, shared between the elements that must compute the same
896
+ * style, with the key this element's children share under.
897
+ *
898
+ * A style is a function of the parent's style, the rules that match, the
899
+ * presentational attributes, the inline style and whether the parent is a
900
+ * flex container. Outside the rules `UNSHAREABLE` names, what matches
901
+ * depends only on the element's tag and attributes and its ancestors', and
902
+ * the hints read nothing else either. So the key is the parent's key, the
903
+ * flex flag, the tag and every attribute — plus the pointer state where a
904
+ * rule asks for it — and an element whose key has been computed in this
905
+ * build takes that style object, matching nothing. A long document is a
906
+ * few kinds of element many times over: the benchmark's 600 KB report is
907
+ * 9,039 elements and 110 keys, and computing every style again was a third
908
+ * of an edit.
909
+ *
910
+ * An element a rule `UNSHAREABLE` could reach is matched, since where it
911
+ * sits decides what matches — and then shared by what matched: the same
912
+ * parent, attributes and rules make the same style. That is what keeps a
913
+ * striped table from being a style per row, and it keeps the key its
914
+ * children share under, which an element-per-key would have taken from the
915
+ * whole subtree.
916
+ *
917
+ * The style is handed out shared, so nothing may write to it — nothing
918
+ * does after the cascade, and `rootStyle` does not go through here.
919
+ */
920
+ sharedStyleFor(
921
+ el: Element,
922
+ parentStyle: ComputedStyle,
923
+ parentKey: number,
924
+ inFlexContainer: boolean,
925
+ ): SharedStyle {
926
+ let key = `${parentKey}\u0001${inFlexContainer ? 1 : 0}`;
927
+ const tag = tagOf(el);
928
+ key += `\u0001${tag.length}:${tag}`;
929
+ if (this._index.hoverSensitive) {
930
+ if (this._pointer.hovered.has(el)) key += '\u0001:hover';
931
+ if (this._pointer.active.has(el)) key += '\u0001:active';
932
+ }
933
+ const attribs = el.attribs;
934
+ for (const name in attribs) {
935
+ const value = attribs[name];
936
+ key += `\u0001${name.length}:${name}=${value.length}:${value}`;
937
+ }
938
+ if (!this._shareable(el)) {
939
+ const matched: number[] = [];
940
+ const candidates = this._candidates(el, matched);
941
+ key += `\u0001${matched.join(',')}`;
942
+ let shared = this._sharedByMatch.get(key);
943
+ if (shared === undefined) {
944
+ shared = {
945
+ style: this._computeStyle(
946
+ el,
947
+ parentStyle,
948
+ inFlexContainer,
949
+ candidates,
950
+ ),
951
+ key: this._nextShareKey++,
952
+ };
953
+ this._sharedByMatch.set(key, shared);
954
+ }
955
+ return shared;
956
+ }
957
+ let shared = this._shared.get(key);
958
+ if (shared === undefined) {
959
+ shared = {
960
+ style: this.styleFor(el, parentStyle, inFlexContainer),
961
+ key: this._nextShareKey++,
962
+ };
963
+ this._shared.set(key, shared);
964
+ }
965
+ return shared;
966
+ }
967
+
968
+ /** Whether no rule `UNSHAREABLE` names could reach this element: the same
969
+ * buckets `_candidates` looks in. */
970
+ private _shareable(el: Element): boolean {
971
+ const index = this._index;
972
+ if (index.ownStyleEverywhere) return false;
973
+ if (index.ownStyleTags.size && index.ownStyleTags.has(tagOf(el))) {
974
+ return false;
975
+ }
976
+ if (index.ownStyleIds.size) {
977
+ const id = attr(el, 'id');
978
+ if (id && index.ownStyleIds.has(id)) return false;
979
+ }
980
+ if (index.ownStyleClasses.size) {
981
+ const className = attr(el, 'class');
982
+ if (className) {
983
+ for (const name of className.split(/\s+/)) {
984
+ if (name && index.ownStyleClasses.has(name)) return false;
985
+ }
986
+ }
987
+ }
988
+ return true;
989
+ }
990
+
289
991
  /**
290
- * The computed style for one element, given its parent's. Called once per
291
- * element per style pass, in document order — the box builder drives it,
292
- * so there is no second traversal and no map of styles to allocate.
992
+ * The computed style for one element, given its parent's. The box builder
993
+ * asks for every element in document order, through `sharedStyleFor`, so
994
+ * a style is computed only for the elements with none to share.
293
995
  */
294
996
  styleFor(
295
997
  el: Element,
296
998
  parentStyle: ComputedStyle,
297
999
  inFlexContainer: boolean,
1000
+ ): ComputedStyle {
1001
+ return this._computeStyle(
1002
+ el,
1003
+ parentStyle,
1004
+ inFlexContainer,
1005
+ this._candidates(el),
1006
+ );
1007
+ }
1008
+
1009
+ /**
1010
+ * The style of an element's `::before` or `::after`, or null when it has
1011
+ * none: no rule reaches it, or the rules that do leave `content` at
1012
+ * `normal` or `none`, which make no box (CSS 2.1 12.2). It inherits from
1013
+ * the element's own style, as a pseudo-element does, and a flex
1014
+ * container's are flex items. Not shared: few elements have one, and where
1015
+ * a rule gives every element one — a clearfix — the style is the cost of
1016
+ * the box it makes.
1017
+ */
1018
+ pseudoStyleFor(
1019
+ el: Element,
1020
+ which: 'before' | 'after',
1021
+ elementStyle: ComputedStyle,
1022
+ ): ComputedStyle | null {
1023
+ const index = this._pseudo[which];
1024
+ if (!index.size || !index.reaches(el)) return null;
1025
+ const candidates: Candidate[] = [];
1026
+ this._matchInto(index, el, candidates);
1027
+ if (!candidates.length) return null;
1028
+ // One that no rule gives a `content` is none, whatever else reaches it —
1029
+ // `content` is not inherited, and its initial `normal` is nothing here.
1030
+ // Tailwind's `*, ::before, ::after` reaches both of every element's.
1031
+ if (!candidates.some(setsContent)) return null;
1032
+ candidates.sort(byCascade);
1033
+ const style = this._computeStyle(
1034
+ el,
1035
+ elementStyle,
1036
+ elementStyle.display === 'flex' || elementStyle.display === 'inline-flex',
1037
+ candidates,
1038
+ );
1039
+ return style.content === 'normal' || style.content === 'none'
1040
+ ? null
1041
+ : style;
1042
+ }
1043
+
1044
+ /**
1045
+ * The rules for an element's `::first-letter`, or null when none reaches
1046
+ * it. They are matched against the element here, and its style computed
1047
+ * later by `firstLetterStyle`, because the pseudo-element inherits from
1048
+ * the box its letter turns out to be in — a `<span>` the block opens with,
1049
+ * a `::before` — which the builder has not reached yet.
1050
+ */
1051
+ firstLetterRules(el: Element): FirstLetterRules | null {
1052
+ const index = this._pseudo['first-letter'];
1053
+ if (!index.size || !index.reaches(el)) return null;
1054
+ const candidates: Candidate[] = [];
1055
+ this._matchInto(index, el, candidates);
1056
+ if (!candidates.length) return null;
1057
+ candidates.sort(byCascade);
1058
+ return { el, candidates };
1059
+ }
1060
+
1061
+ /** Whether any rule styles a `::first-line`: a document with none asks
1062
+ * nothing more of its blocks. */
1063
+ get hasFirstLine(): boolean {
1064
+ return this._pseudo['first-line'].size > 0;
1065
+ }
1066
+
1067
+ /**
1068
+ * A list item's `::marker` style, inheriting from its own, or null when no
1069
+ * rule reaches it (CSS Lists 3, 3.1): the colour and the font a bullet or
1070
+ * a number is set in, and a `content` it is set as instead.
1071
+ */
1072
+ markerStyle(el: Element, style: ComputedStyle): ComputedStyle | null {
1073
+ const index = this._pseudo.marker;
1074
+ if (!index.size || !index.reaches(el)) return null;
1075
+ const candidates: Candidate[] = [];
1076
+ this._matchInto(index, el, candidates);
1077
+ if (!candidates.length) return null;
1078
+ candidates.sort(byCascade);
1079
+ const out = this._computeStyle(el, style, false, candidates);
1080
+ // a marker's direction is its own unless a rule says otherwise (the
1081
+ // HTML style sheet's `::marker { unicode-bidi: isolate }`)
1082
+ const set = candidates.some((c) =>
1083
+ c.declarations.some((d) => d.prop === 'unicode-bidi'),
1084
+ );
1085
+ if (!set) out.unicodeBidi = 'isolate';
1086
+ return out;
1087
+ }
1088
+
1089
+ /**
1090
+ * An element's `::first-line` style, inheriting from its own, or null
1091
+ * when no rule reaches it (CSS 2.1 5.12.1).
1092
+ */
1093
+ firstLineStyle(el: Element, style: ComputedStyle): ComputedStyle | null {
1094
+ const index = this._pseudo['first-line'];
1095
+ if (!index.size) return null;
1096
+ const candidates: Candidate[] = [];
1097
+ this._matchInto(index, el, candidates);
1098
+ if (!candidates.length) return null;
1099
+ candidates.sort(byCascade);
1100
+ return this._computeStyle(el, style, false, candidates);
1101
+ }
1102
+
1103
+ /**
1104
+ * The style of a `::first-letter` inside a box of `parentStyle`. It is an
1105
+ * inline box, or a float where it floats, whatever the rules say of its
1106
+ * `display` or its `position` (CSS 2.1 5.12.2).
1107
+ */
1108
+ firstLetterStyle(
1109
+ rules: FirstLetterRules,
1110
+ parentStyle: ComputedStyle,
1111
+ ): ComputedStyle {
1112
+ const style = this._computeStyle(
1113
+ rules.el,
1114
+ parentStyle,
1115
+ false,
1116
+ rules.candidates as Candidate[],
1117
+ );
1118
+ style.display = style.float === 'none' ? 'inline' : 'block';
1119
+ style.position = 'static';
1120
+ return style;
1121
+ }
1122
+
1123
+ /** The x-height of a style's font: the font's own, asked once per face,
1124
+ * or half an em. */
1125
+ private _exOf(style: FaceSource): number {
1126
+ const key = faceKey(style);
1127
+ let ex = this._xHeights.get(key);
1128
+ if (ex === undefined) {
1129
+ ex = this._xHeightOf?.(faceOf(style)) ?? NaN;
1130
+ if (!(ex > 0)) ex = style.fontSize * 0.5;
1131
+ this._xHeights.set(key, ex);
1132
+ }
1133
+ return ex;
1134
+ }
1135
+
1136
+ /** The advance of a style's font's "0": the font's own, asked once per
1137
+ * face, or half an em. */
1138
+ private _chOf(style: FaceSource): number {
1139
+ const key = faceKey(style);
1140
+ let ch = this._zeroWidths.get(key);
1141
+ if (ch === undefined) {
1142
+ ch = this._zeroWidthOf?.(faceOf(style)) ?? NaN;
1143
+ if (!(ch > 0)) ch = style.fontSize * 0.5;
1144
+ this._zeroWidths.set(key, ch);
1145
+ }
1146
+ return ch;
1147
+ }
1148
+
1149
+ /** A style's computed line height as a length, for `lh`: `normal` as
1150
+ * its font's own, asked once per face, or 1.2em. */
1151
+ private _lineHeightOf(style: LineSource): number {
1152
+ const set = style.lineHeight;
1153
+ if (set !== 'normal') {
1154
+ return style.lineHeightIsLength ? set : set * style.fontSize;
1155
+ }
1156
+ const key = faceKey(style);
1157
+ let line = this._normalLines.get(key);
1158
+ if (line === undefined) {
1159
+ line = this._normalLineOf?.(faceOf(style)) ?? NaN;
1160
+ if (!(line > 0)) line = style.fontSize * 1.2;
1161
+ this._normalLines.set(key, line);
1162
+ }
1163
+ return line;
1164
+ }
1165
+
1166
+ /** `styleFor`, from the rules and hints already gathered for `el`. */
1167
+ private _computeStyle(
1168
+ el: Element,
1169
+ parentStyle: ComputedStyle,
1170
+ inFlexContainer: boolean,
1171
+ candidates: Candidate[],
298
1172
  ): ComputedStyle {
299
1173
  const style = inherit(parentStyle, this.initial);
300
- const candidates = this._candidates(el);
1174
+ // custom properties first, in cascade order, so every `var()` in the
1175
+ // declarations below finds the one that wins
1176
+ if (this._vars) style.custom = this._customFor(parentStyle, candidates);
301
1177
 
302
1178
  // The unit context has to be built twice: once with the parent's font
303
1179
  // size, so a `font-size: 1.2em` in the cascade resolves against the
@@ -306,29 +1182,158 @@ export class Cascade {
306
1182
  const ctxParent: UnitContext = {
307
1183
  em: parentStyle.fontSize,
308
1184
  rem: this.initial.fontSize,
1185
+ initial: this.initial,
309
1186
  vw: this.viewportWidth,
310
1187
  vh: this.viewportHeight,
311
1188
  scale: this.scale,
1189
+ ex: () => this._exOf(parentStyle),
1190
+ ch: () => this._chOf(parentStyle),
1191
+ families: this._mapFamilies,
1192
+ lh: () => this._lineHeightOf(parentStyle),
1193
+ rlh: () => this._lineHeightOf(this.initial),
312
1194
  };
1195
+ // The family, the weight and the slant go with the size: together they
1196
+ // pick the face an `ex`, a `ch` or an `lh` in any declaration is
1197
+ // measured in, however the declarations are ordered. A `width: 10ex`
1198
+ // in a sheet under an inline `font-weight: 900` was measured in the
1199
+ // family's regular face.
1200
+ let sized = false;
1201
+ let keyword = false;
313
1202
  for (const c of candidates) {
314
1203
  for (const d of pick(c)) {
315
- if (d.prop === 'font-size' || d.prop === 'font') {
316
- applyDeclaration(style, parentStyle, d.prop, d.value, ctxParent);
1204
+ if (
1205
+ d.prop === 'font-family' ||
1206
+ d.prop === 'font-weight' ||
1207
+ d.prop === 'font-style'
1208
+ ) {
1209
+ this._apply(style, parentStyle, d, ctxParent);
1210
+ } else if (d.prop === 'font-size' || d.prop === 'font') {
1211
+ // NaN until a size is set, so one the declaration did not take
1212
+ // leaves the size where it was, and says nothing of it
1213
+ const was = style.fontSize;
1214
+ style.fontSize = NaN;
1215
+ this._apply(style, parentStyle, d, ctxParent);
1216
+ if (Number.isNaN(style.fontSize)) {
1217
+ style.fontSize = was;
1218
+ } else {
1219
+ sized = true;
1220
+ keyword = !d.vars && sizeKeyword(d.prop, d.value);
1221
+ }
1222
+ }
1223
+ }
1224
+ }
1225
+ // The generic `monospace` on its own has a smaller size than the rest,
1226
+ // 13 pixels to their 16 (the "fixed" font size every browser keeps):
1227
+ // an element whose family becomes it, or stops being it, scales the
1228
+ // size it inherits by that, and a keyword size is read from the
1229
+ // smaller scale — the size it sets itself stays its own. Blink's
1230
+ // CheckForGenericFamilyChange, and why a `<pre>` in a browser is 13px.
1231
+ const mono = monospaceOnly(style.fontFamily);
1232
+ if (
1233
+ sized ? keyword && mono : mono !== monospaceOnly(parentStyle.fontFamily)
1234
+ ) {
1235
+ style.fontSize *= mono ? FIXED_SIZE : 1 / FIXED_SIZE;
1236
+ }
1237
+ // The face as the first pass settled it. The second applies every
1238
+ // declaration again in cascade order, the font's among them, so the
1239
+ // style passes through the fonts of every rule on the way: read from
1240
+ // it, the `10ex` in `div { font-family: foo; width: 10ex }` was `foo`'s
1241
+ // under an inline `font-family: Ahem` that came after it.
1242
+ const face: FaceSource = {
1243
+ fontFamily: style.fontFamily,
1244
+ fontSize: style.fontSize,
1245
+ fontWeight: style.fontWeight,
1246
+ fontStyle: style.fontStyle,
1247
+ };
1248
+ let line: LineSource | null = null;
1249
+ const ctx: UnitContext = {
1250
+ ...ctxParent,
1251
+ em: style.fontSize,
1252
+ ex: () => this._exOf(face),
1253
+ ch: () => this._chOf(face),
1254
+ lh: () => this._lineHeightOf(line ?? style),
1255
+ };
1256
+ // the line height, which an `lh` in any other declaration reads, ahead
1257
+ // of them — a `font` sets one too — and read as it is then, for the
1258
+ // same reason as the face: set again with the rest, in its place among
1259
+ // them. A `font` sets the size as well, which the first pass settled.
1260
+ if (this._lh) {
1261
+ for (const c of candidates) {
1262
+ for (const d of pick(c)) {
1263
+ if (d.prop === 'line-height' || d.prop === 'font') {
1264
+ this._apply(style, parentStyle, d, ctx);
1265
+ }
317
1266
  }
318
1267
  }
1268
+ style.fontSize = face.fontSize;
1269
+ line = {
1270
+ ...face,
1271
+ lineHeight: style.lineHeight,
1272
+ lineHeightIsLength: style.lineHeightIsLength,
1273
+ };
319
1274
  }
320
- const ctx: UnitContext = { ...ctxParent, em: style.fontSize };
1275
+ const settled = style.fontSize;
321
1276
  for (const c of candidates) {
322
1277
  for (const d of pick(c)) {
323
- if (d.prop === 'font-size') continue;
324
- applyDeclaration(style, parentStyle, d.prop, d.value, ctx);
1278
+ if (d.prop === 'font-size' || d.custom) continue;
1279
+ this._apply(style, parentStyle, d, ctx);
325
1280
  }
326
1281
  }
1282
+ // A `font` is applied again, to keep its other longhands in cascade
1283
+ // order, and it sets the size it names as well: the size is the first
1284
+ // pass's, which the declarations that outrank the `font` had their say
1285
+ // in, and the scale after them. `span { font: 15px/1 Ahem }` under
1286
+ // `.b > span { font-size: 3.75em }` was 15px, its `em`s 3.75 of its
1287
+ // parent's.
1288
+ style.fontSize = settled;
327
1289
 
1290
+ // A table never keeps HTML's alignment, `-webkit-center` and its kin:
1291
+ // the `<td align="center">` every mail centres its body table in
1292
+ // centres the table, and the text in its cells stays at their start —
1293
+ // as Blink resets it ("tables never support the -webkit-* values for
1294
+ // text-align"). An author's own `text-align: center` is not HTML's, and
1295
+ // is inherited into the table as it always was.
1296
+ if (style.alignBlocks !== null && tagOf(el) === 'table') {
1297
+ style.textAlign = 'start';
1298
+ style.alignBlocks = null;
1299
+ }
1300
+ settleClamp(style, parentStyle);
1301
+ settleOverflow(style);
1302
+ settleContentVisibility(style);
328
1303
  blockify(style, inFlexContainer);
1304
+ decorate(style);
1305
+ // which faces of the document's own families this family, weight and
1306
+ // slant ask for — known only now, with all three computed
1307
+ this._families?.note(style);
329
1308
  return style;
330
1309
  }
331
1310
 
1311
+ /** One declaration, its `var()`s replaced first. One that names a
1312
+ * custom property with no value, and has no fallback, is invalid at
1313
+ * computed-value time, and the property is as though `unset`. */
1314
+ private _apply(
1315
+ style: ComputedStyle,
1316
+ parentStyle: ComputedStyle,
1317
+ d: Declaration,
1318
+ ctx: UnitContext,
1319
+ ): void {
1320
+ if (!d.vars) {
1321
+ applyDeclaration(style, parentStyle, d.prop, d.value, ctx);
1322
+ return;
1323
+ }
1324
+ // unset first: a value that does not parse once it is substituted is
1325
+ // invalid at computed-value time, and does not leave the property what
1326
+ // an earlier declaration gave it
1327
+ if (isInherited(d.prop)) {
1328
+ applyDeclaration(style, parentStyle, d.prop, 'inherit', ctx);
1329
+ } else {
1330
+ initialOne(style, this.initial, d.prop);
1331
+ }
1332
+ const value = substituteIn(d.value, style.custom);
1333
+ if (value !== null)
1334
+ applyDeclaration(style, parentStyle, d.prop, value, ctx);
1335
+ }
1336
+
332
1337
  /**
333
1338
  * The root's style — nothing above it to inherit from.
334
1339
  *
@@ -337,24 +1342,51 @@ export class Cascade {
337
1342
  * a `<body>` would have had: the UA sheet's margin and font, and any
338
1343
  * author `body { … }` rule. Without it a fragment renders hard against the
339
1344
  * left edge while the same markup inside `<html><body>` does not, which
340
- * reads as a bug in the renderer rather than as a missing element.
1345
+ * reads as a bug in the renderer rather than as a missing element. With
1346
+ * no `<html>` either, the body inherits from an `<html>` that author
1347
+ * `html { … }` rules have styled, as the one a browser implies would be.
1348
+ *
1349
+ * A document with a `<body>` and no `<html>` — the usual shape of a page
1350
+ * that starts `<!DOCTYPE html><title>` — has the root box standing in for
1351
+ * that implied `<html>` instead, so an `html { font-size }` still reaches
1352
+ * the body. Taken as the initial style, the rule matched nothing and the
1353
+ * whole page came out at the theme's size.
1354
+ *
1355
+ * `html` is the implied `<html>`'s own style where the root box stands in
1356
+ * for the body inside it, and null otherwise: what an `html { … }` or a
1357
+ * `:root { … }` rule gives it that is not inherited has no box to be
1358
+ * drawn on, and its background is the canvas's (`paintDocument`).
341
1359
  */
342
- rootStyle(hasBody: boolean): ComputedStyle {
343
- const style = { ...this.initial };
1360
+ rootStyle(
1361
+ hasBody: boolean,
1362
+ hasHtml = true,
1363
+ ): { style: ComputedStyle; html: ComputedStyle | null } {
1364
+ const style = copyStyle(this.initial);
344
1365
  style.display = 'block';
345
- if (hasBody) return style;
1366
+ if (hasBody && hasHtml) return { style, html: null };
346
1367
  const synthetic = new DomElement('body', {}, []);
347
- const bodyStyle = this.styleFor(synthetic, style, false);
1368
+ let parent = style;
1369
+ let html: ComputedStyle | null = null;
1370
+ if (!hasHtml) {
1371
+ const element = new DomElement('html', {}, hasBody ? [] : [synthetic]);
1372
+ synthetic.parent = element;
1373
+ parent = this.styleFor(element, style, false);
1374
+ if (hasBody) return { style: asRoot(parent), html: null };
1375
+ html = parent;
1376
+ }
348
1377
  // Only the box the body would have drawn is taken, not its layout role:
349
1378
  // the root is still the initial containing block.
350
- bodyStyle.display = 'block';
351
- bodyStyle.position = 'static';
352
- bodyStyle.float = 'none';
353
- return bodyStyle;
1379
+ return { style: asRoot(this.styleFor(synthetic, parent, false)), html };
354
1380
  }
355
1381
 
356
- private _candidates(el: Element): Candidate[] {
357
- const out: Candidate[] = [];
1382
+ /** The rules of `index` that match `el`, pushed onto `out` as candidates,
1383
+ * and their ids onto `matched` in the order they were tried. */
1384
+ private _matchInto(
1385
+ index: RuleIndex,
1386
+ el: Element,
1387
+ out: Candidate[],
1388
+ matched?: number[],
1389
+ ): void {
358
1390
  // A media query's width is CSS pixels; the viewport is kept in device.
359
1391
  const width = this.viewportWidth / this.scale;
360
1392
 
@@ -366,12 +1398,7 @@ export class Cascade {
366
1398
  if (!indexed.compiled) {
367
1399
  indexed.compiled = true;
368
1400
  try {
369
- indexed.match = compile(rule.selector, {
370
- adapter: this._adapter,
371
- xmlMode: false,
372
- } as unknown as Parameters<typeof compile>[1]) as unknown as (
373
- node: Element,
374
- ) => boolean;
1401
+ indexed.match = compileSelector(rule.selector, this._adapter);
375
1402
  } catch {
376
1403
  // A selector this matcher does not know (`::-moz-…`, a CSS4 form
377
1404
  // it has not learnt) drops out of the cascade rather than out of
@@ -380,26 +1407,36 @@ export class Cascade {
380
1407
  }
381
1408
  }
382
1409
  if (!indexed.match || !indexed.match(el)) continue;
1410
+ matched?.push(indexed.id);
383
1411
  const origin = rule.order < 0 ? Origin.UserAgent : Origin.Author;
384
1412
  pushRule(out, rule, origin);
385
1413
  }
386
1414
  };
387
1415
 
388
1416
  const id = attr(el, 'id');
389
- if (id) consider(this._index.byId.get(id));
1417
+ if (id) consider(index.byId.get(id));
390
1418
  const className = attr(el, 'class');
391
1419
  if (className) {
392
1420
  for (const name of className.split(/\s+/)) {
393
- if (name) consider(this._index.byClass.get(name));
1421
+ if (name) consider(index.byClass.get(name));
394
1422
  }
395
1423
  }
396
- consider(this._index.byTag.get(tagOf(el)));
397
- consider(this._index.universal);
1424
+ consider(index.byTag.get(tagOf(el)));
1425
+ consider(index.universal);
1426
+ }
1427
+
1428
+ /** Every rule and hint that applies to `el`, in cascade order — and, into
1429
+ * `matched` when it is passed, the ids of the rules, in the order they
1430
+ * were tried. */
1431
+ private _candidates(el: Element, matched?: number[]): Candidate[] {
1432
+ const out: Candidate[] = [];
1433
+ this._matchInto(this._index, el, out, matched);
398
1434
 
399
1435
  const hints = presentationHints(el);
400
1436
  if (hints.length) {
401
1437
  out.push({
402
1438
  origin: Origin.Presentation,
1439
+ layer: null,
403
1440
  specificity: 0,
404
1441
  order: 0,
405
1442
  declarations: hints,
@@ -410,11 +1447,15 @@ export class Cascade {
410
1447
  const inline = attr(el, 'style');
411
1448
  if (inline) {
412
1449
  const declarations = parseDeclarations(inline);
1450
+ if (!this._vars && usesVars(declarations)) this._vars = true;
1451
+ this._noteViewportUnits(declarations);
1452
+ if (!this._lh && usesLh(declarations)) this._lh = true;
413
1453
  const normal = declarations.filter((d) => !d.important);
414
1454
  const important = declarations.filter((d) => d.important);
415
1455
  if (normal.length) {
416
1456
  out.push({
417
1457
  origin: Origin.Inline,
1458
+ layer: null,
418
1459
  specificity: 0,
419
1460
  order: 0,
420
1461
  declarations: normal,
@@ -424,6 +1465,7 @@ export class Cascade {
424
1465
  if (important.length) {
425
1466
  out.push({
426
1467
  origin: Origin.InlineImportant,
1468
+ layer: null,
427
1469
  specificity: 0,
428
1470
  order: 0,
429
1471
  declarations: important,
@@ -437,6 +1479,145 @@ export class Cascade {
437
1479
  }
438
1480
  }
439
1481
 
1482
+ /** An implied element's style as the root box's: its look, not its role —
1483
+ * the root is still a block, in flow, and the initial containing block. */
1484
+ function asRoot(style: ComputedStyle): ComputedStyle {
1485
+ style.display = 'block';
1486
+ style.position = 'static';
1487
+ style.float = 'none';
1488
+ return style;
1489
+ }
1490
+
1491
+ /** `:root` is the `<html>` element: the one a browser implies around a
1492
+ * fragment, whose style the root box takes (`rootStyle`), and never a
1493
+ * fragment's top-level elements, which css-select would take for it. */
1494
+ const PSEUDOS = {
1495
+ root: (el: Element) =>
1496
+ el.name === 'html' && !(el.parent && isTag(el.parent as Element)),
1497
+ // css-select's, but its ranges and tags lower-cased as ASCII has it, and
1498
+ // no other script (CSS 2.1 4.1.3): Unicode's took `:lang(\u212Al)`, a
1499
+ // Kelvin sign for the K, for `:lang(kl)`
1500
+ lang: (el: Element, code: string | null): boolean => {
1501
+ const ranges = (code ?? '')
1502
+ .split(',')
1503
+ .map((range) => range.trim().replace(/^['"]|['"]$/g, ''))
1504
+ .filter((range) => range.length > 0)
1505
+ .map((range) => asciiLower(range).split('-'));
1506
+ let root = el;
1507
+ for (let node: Element | null = el; node;) {
1508
+ const value = node.attribs['xml:lang'] ?? node.attribs.lang;
1509
+ if (value != null) {
1510
+ if (!value) return ranges.some((range) => range[0] === '');
1511
+ const tag = asciiLower(value).split('-');
1512
+ return ranges.some((range) => langRangeMatches(tag, range));
1513
+ }
1514
+ root = node;
1515
+ const parent: Element['parent'] = node.parent;
1516
+ node = parent && isTag(parent as Element) ? (parent as Element) : null;
1517
+ }
1518
+ // with no element saying, the document's, as its `<meta>` sets it —
1519
+ // searched from the document, where a fragment's `<meta>` is a
1520
+ // sibling of what it covers
1521
+ const pragma = pragmaLanguage(root.parent ?? root);
1522
+ if (!pragma) return ranges.some((range) => range[0] === '');
1523
+ const tag = asciiLower(pragma).split('-');
1524
+ return ranges.some((range) => langRangeMatches(tag, range));
1525
+ },
1526
+ };
1527
+
1528
+ /** A length in a unit of the viewport: a number, then the unit, and no
1529
+ * more of a name after it. */
1530
+ const VIEWPORT_UNIT = /\d(?:vw|vh|vmin|vmax)(?![\w-])/i;
1531
+ const VIEWPORT_UNITS = /\d(vw|vh|vmin|vmax)(?![\w-])/gi;
1532
+
1533
+ /**
1534
+ * A selector with its `[attr~=""]` made one that matches nothing: an empty
1535
+ * string is no word of a list, and such a selector represents nothing
1536
+ * (Selectors 3, 6.3.1) — which the matcher's `~=` is not told, taking the
1537
+ * empty word for one between two spaces, or at either end of none.
1538
+ */
1539
+ function noEmptyWords(selector: string): string {
1540
+ if (!selector.includes('~=')) return selector;
1541
+ return selector.replace(
1542
+ /\[[^\]"']*~=\s*(?:""|'')\s*(?:[is]\s*)?\]/gi,
1543
+ ':not(*)',
1544
+ );
1545
+ }
1546
+
1547
+ /**
1548
+ * A document's language as a `<meta http-equiv="content-language">` sets
1549
+ * it — HTML's pragma-set default language, the last such `<meta>`'s
1550
+ * `content` up to its first white space, and none where it lists more
1551
+ * than one — by the document's root element, found once.
1552
+ */
1553
+ function pragmaLanguage(root: { children: unknown[] }): string {
1554
+ let lang = PRAGMA_LANGUAGE.get(root);
1555
+ if (lang !== undefined) return lang;
1556
+ lang = '';
1557
+ const stack: { children: unknown[] }[] = [root];
1558
+ while (stack.length) {
1559
+ const node = stack.pop()! as Element;
1560
+ if (
1561
+ isTag(node) &&
1562
+ node.name === 'meta' &&
1563
+ node.attribs['http-equiv']?.trim().toLowerCase() === 'content-language'
1564
+ ) {
1565
+ const content = node.attribs.content;
1566
+ if (content !== undefined && !content.includes(',')) {
1567
+ const candidate = content.trim().split(/[\t\n\f\r ]/)[0];
1568
+ if (candidate) lang = candidate;
1569
+ }
1570
+ }
1571
+ // in document order: the last one pushed is taken first
1572
+ for (let i = node.children.length - 1; i >= 0; i -= 1) {
1573
+ const child = node.children[i];
1574
+ if (isTag(child as Element)) stack.push(child as Element);
1575
+ }
1576
+ }
1577
+ PRAGMA_LANGUAGE.set(root, lang);
1578
+ return lang;
1579
+ }
1580
+
1581
+ const PRAGMA_LANGUAGE = new WeakMap<object, string>();
1582
+
1583
+ /** RFC 4647's extended filtering, of a tag's subtags by a range's, as
1584
+ * css-select does it. */
1585
+ function langRangeMatches(tag: string[], range: string[]): boolean {
1586
+ if (range[0] !== '*' && range[0] !== tag[0]) return false;
1587
+ let at = 1;
1588
+ for (let r = 1; r < range.length; r += 1) {
1589
+ if (range[r] === '*') continue;
1590
+ while (at < tag.length && tag[at] !== range[r]) {
1591
+ if (tag[at++].length <= 1) return false;
1592
+ }
1593
+ if (at >= tag.length) return false;
1594
+ at += 1;
1595
+ }
1596
+ return true;
1597
+ }
1598
+
1599
+ /** Whether any of these declarations sets a custom property or reads one. */
1600
+ function usesVars(declarations: readonly Declaration[]): boolean {
1601
+ for (const d of declarations) if (d.custom || d.vars) return true;
1602
+ return false;
1603
+ }
1604
+
1605
+ /** Whether any of the declarations has a length in `lh` or `rlh`. */
1606
+ function usesLh(declarations: readonly Declaration[]): boolean {
1607
+ for (const d of declarations) if (LH.test(d.value)) return true;
1608
+ return false;
1609
+ }
1610
+ const LH = /\d\.?r?lh\b/i;
1611
+
1612
+ /** Whether a candidate declares `content`. */
1613
+ function setsContent(c: Candidate): boolean {
1614
+ for (const d of pick(c)) if (d.prop === 'content') return true;
1615
+ return false;
1616
+ }
1617
+
1618
+ /** Whether a rule's declarations set a custom property, by the array. */
1619
+ const CUSTOM_IN = new WeakMap<Declaration[], boolean>();
1620
+
440
1621
  function pick(c: Candidate): Declaration[] {
441
1622
  return c.only < 0 ? c.declarations : [c.declarations[c.only]];
442
1623
  }
@@ -452,6 +1633,7 @@ function pushRule(out: Candidate[], rule: StyleRule, origin: Origin): void {
452
1633
  if (!hasImportant) {
453
1634
  out.push({
454
1635
  origin,
1636
+ layer: rule.layer,
455
1637
  specificity: rule.specificity,
456
1638
  order: rule.order,
457
1639
  declarations: rule.declarations,
@@ -469,6 +1651,7 @@ function pushRule(out: Candidate[], rule: StyleRule, origin: Origin): void {
469
1651
  ? Origin.UserAgent
470
1652
  : Origin.AuthorImportant
471
1653
  : origin,
1654
+ layer: rule.layer,
472
1655
  specificity: rule.specificity,
473
1656
  order: rule.order,
474
1657
  declarations: rule.declarations,
@@ -479,10 +1662,34 @@ function pushRule(out: Candidate[], rule: StyleRule, origin: Origin): void {
479
1662
 
480
1663
  function byCascade(a: Candidate, b: Candidate): number {
481
1664
  if (a.origin !== b.origin) return a.origin - b.origin;
1665
+ if (a.layer !== b.layer) {
1666
+ // a later layer wins over an earlier one whatever the specificity, and
1667
+ // for `!important` an earlier one does (CSS Cascade 5, 6.4)
1668
+ const by = compareLayers(a.layer, b.layer);
1669
+ if (by !== 0) return a.origin >= Origin.AuthorImportant ? -by : by;
1670
+ }
482
1671
  if (a.specificity !== b.specificity) return a.specificity - b.specificity;
483
1672
  return a.order - b.order;
484
1673
  }
485
1674
 
1675
+ /**
1676
+ * Which of two layers wins for a normal declaration: a later one over an
1677
+ * earlier one, and a rule in no layer over any, as a rule directly in a
1678
+ * layer is over the layers inside it.
1679
+ */
1680
+ function compareLayers(
1681
+ a: readonly number[] | null,
1682
+ b: readonly number[] | null,
1683
+ ): number {
1684
+ if (a === null) return b === null ? 0 : 1;
1685
+ if (b === null) return -1;
1686
+ const n = Math.min(a.length, b.length);
1687
+ for (let i = 0; i < n; i += 1) {
1688
+ if (a[i] !== b[i]) return a[i] - b[i];
1689
+ }
1690
+ return b.length - a.length;
1691
+ }
1692
+
486
1693
  /**
487
1694
  * The presentational attributes, as declarations. They sit above the UA
488
1695
  * sheet and below every author rule, which is where HTML says they go — and
@@ -500,16 +1707,79 @@ function presentationHints(el: Element): Declaration[] {
500
1707
  const align = attr(el, 'align');
501
1708
  if (align) {
502
1709
  const v = align.toLowerCase();
503
- if (tag === 'img' && (v === 'left' || v === 'right')) push('float', v);
504
- else if (v === 'center' || v === 'middle') push('text-align', 'center');
1710
+ const side = v === 'middle' ? 'center' : v;
1711
+ if ((tag === 'img' || tag === 'table') && (v === 'left' || v === 'right')) {
1712
+ push('float', v);
1713
+ } else if (tag === 'img') {
1714
+ // an image's other alignments are its line's
1715
+ const va = IMAGE_ALIGN[v];
1716
+ if (va) push('vertical-align', va);
1717
+ } else if (tag === 'hr') {
1718
+ // a rule narrower than its line stands where it is aligned
1719
+ if (v === 'left') {
1720
+ push('margin-left', '0');
1721
+ push('margin-right', 'auto');
1722
+ } else if (v === 'right') {
1723
+ push('margin-left', 'auto');
1724
+ push('margin-right', '0');
1725
+ }
1726
+ } else if (tag === 'table') {
1727
+ // a table's `align` places the table and leaves its text alone: set
1728
+ // as its text's, `<table align="center">` — the frame of nearly
1729
+ // every mail — stood at the left with its cells' text centred
1730
+ if (side === 'center') {
1731
+ push('margin-left', 'auto');
1732
+ push('margin-right', 'auto');
1733
+ }
1734
+ } else if (
1735
+ ALIGNS_BLOCKS.has(tag) &&
1736
+ (side === 'center' || side === 'left' || side === 'right')
1737
+ ) {
1738
+ // these align the blocks in them as well as their text (HTML's
1739
+ // rendering, "align descendants"): the table a `<td align="center">`
1740
+ // or a `<div align="center">` holds is centred in it
1741
+ push('text-align', `-webkit-${side}`);
1742
+ } else if (side === 'center') push('text-align', 'center');
505
1743
  else if (v === 'left' || v === 'right' || v === 'justify')
506
1744
  push('text-align', v);
507
1745
  }
508
1746
  const valign = attr(el, 'valign');
509
1747
  if (valign) push('vertical-align', valign.toLowerCase());
1748
+ // HTML's `dir` is its element's `direction`: `<table dir="rtl">` starts
1749
+ // at the right, and a Hebrew or an Arabic message is written this way
1750
+ const dir = attr(el, 'dir')?.toLowerCase();
1751
+ if (dir === 'rtl' || dir === 'ltr') push('direction', dir);
1752
+ // and it isolates the element from the text around it, or with `auto`
1753
+ // takes its first strong letter's direction (HTML's UA sheet: `[dir]`,
1754
+ // `[dir=auto i]`, `bdo[dir]`)
1755
+ if (dir !== undefined) {
1756
+ push(
1757
+ 'unicode-bidi',
1758
+ tag === 'bdo'
1759
+ ? 'isolate-override'
1760
+ : dir === 'auto'
1761
+ ? 'plaintext'
1762
+ : 'isolate',
1763
+ );
1764
+ }
510
1765
 
511
1766
  const bgcolor = attr(el, 'bgcolor');
512
1767
  if (bgcolor) push('background-color', bgcolor);
1768
+ if (BACKGROUNDS.has(tag)) {
1769
+ const background = attr(el, 'background')?.trim();
1770
+ if (background) {
1771
+ push('background-image', `url("${background.replace(/["\\\n]/g, '')}")`);
1772
+ }
1773
+ }
1774
+ if (tag === 'body') {
1775
+ // the text's colour, and the links'
1776
+ const text = attr(el, 'text');
1777
+ if (text) push('color', text);
1778
+ } else if (tag === 'a' && attr(el, 'href') !== undefined) {
1779
+ const body = closestBody(el);
1780
+ const link = body ? attr(body, 'link') : undefined;
1781
+ if (link) push('color', link);
1782
+ }
513
1783
  const color = attr(el, 'color');
514
1784
  if (color && (tag === 'font' || tag === 'basefont')) push('color', color);
515
1785
  const face = attr(el, 'face');
@@ -529,6 +1799,24 @@ function presentationHints(el: Element): Declaration[] {
529
1799
  if (width) push('width', lengthAttr(width));
530
1800
  const height = attr(el, 'height');
531
1801
  if (height) push('height', lengthAttr(height));
1802
+ // and, both numbers, an image's the ratio it has before it loads, or
1803
+ // where it has none of its own (HTML 15.4.3, "map to the aspect-ratio
1804
+ // property")
1805
+ if (width && height && RATIO_SIZED.has(tag)) {
1806
+ const w = parseFloat(width);
1807
+ const h = parseFloat(height);
1808
+ if (w > 0 && h > 0 && !/%/.test(width + height)) {
1809
+ push('aspect-ratio', `auto ${w} / ${h}`);
1810
+ }
1811
+ }
1812
+ } else if (tag === 'svg') {
1813
+ // an SVG's are CSS lengths of their own, units and all (SVG 2, 5.1.1)
1814
+ const width = attr(el, 'width');
1815
+ const w = width ? svgSizeHint(width) : null;
1816
+ if (w) push('width', w);
1817
+ const height = attr(el, 'height');
1818
+ const h = height ? svgSizeHint(height) : null;
1819
+ if (h) push('height', h);
532
1820
  }
533
1821
 
534
1822
  if (tag === 'table') {
@@ -550,13 +1838,50 @@ function presentationHints(el: Element): Declaration[] {
550
1838
  if (tag === 'hr') {
551
1839
  const noshade = attr(el, 'noshade');
552
1840
  if (noshade !== undefined) push('border-top-width', '2px');
1841
+ // the rule's colour and its thickness
1842
+ const color = attr(el, 'color');
1843
+ if (color) push('border-top-color', color);
1844
+ const size = attr(el, 'size');
1845
+ if (size && Number(size) > 0) push('border-top-width', `${Number(size)}px`);
1846
+ }
1847
+ if ((tag === 'td' || tag === 'th') && attr(el, 'nowrap') !== undefined) {
1848
+ push('white-space', 'nowrap');
1849
+ }
1850
+ if (tag === 'br') {
1851
+ // below the floats: `<br clear="all">` after a floated image
1852
+ const clear = attr(el, 'clear')?.toLowerCase();
1853
+ if (clear === 'all' || clear === 'both') push('clear', 'both');
1854
+ else if (clear === 'left' || clear === 'right') push('clear', clear);
553
1855
  }
554
1856
  if (tag === 'ol') {
555
1857
  const type = attr(el, 'type');
556
1858
  const mapped = OL_TYPES[type ?? ''];
557
1859
  if (mapped) push('list-style-type', mapped);
558
- const start = attr(el, 'start');
559
- if (start) push('counter-reset', start);
1860
+ // the number a list starts at is its `list-item` counter's, one before
1861
+ // it, or counting down one after it (HTML's rendering, "Lists")
1862
+ const start = htmlInteger(attr(el, 'start'));
1863
+ if (start !== null) {
1864
+ push(
1865
+ 'counter-reset',
1866
+ attr(el, 'reversed') !== undefined
1867
+ ? `reversed(list-item) ${start + 1}`
1868
+ : `list-item ${start - 1}`,
1869
+ );
1870
+ }
1871
+ }
1872
+ if (tag === 'ul') {
1873
+ const type = attr(el, 'type')?.toLowerCase();
1874
+ if (type && BULLETS.has(type)) push('list-style-type', type);
1875
+ }
1876
+ if (tag === 'li') {
1877
+ // an item's `value` is the number it goes on from
1878
+ const value = htmlInteger(attr(el, 'value'));
1879
+ if (value !== null) push('counter-set', `list-item ${value}`);
1880
+ const type = attr(el, 'type');
1881
+ const mapped =
1882
+ OL_TYPES[type ?? ''] ??
1883
+ (type && BULLETS.has(type.toLowerCase()) ? type.toLowerCase() : null);
1884
+ if (mapped) push('list-style-type', mapped);
560
1885
  }
561
1886
  if (tag === 'img' || tag === 'object') {
562
1887
  const hspace = attr(el, 'hspace');
@@ -573,6 +1898,56 @@ function presentationHints(el: Element): Declaration[] {
573
1898
  return out;
574
1899
  }
575
1900
 
1901
+ /** An image's `align` other than a side, as its `vertical-align`. */
1902
+ const IMAGE_ALIGN: Record<string, string> = {
1903
+ top: 'top',
1904
+ texttop: 'text-top',
1905
+ middle: 'middle',
1906
+ absmiddle: 'middle',
1907
+ center: 'middle',
1908
+ bottom: 'baseline',
1909
+ baseline: 'baseline',
1910
+ absbottom: 'bottom',
1911
+ };
1912
+
1913
+ /** The elements whose `align` aligns the blocks in them too. */
1914
+ const ALIGNS_BLOCKS = new Set([
1915
+ 'div',
1916
+ 'caption',
1917
+ 'thead',
1918
+ 'tbody',
1919
+ 'tfoot',
1920
+ 'tr',
1921
+ 'td',
1922
+ 'th',
1923
+ ]);
1924
+
1925
+ /** The elements a `background` attribute gives a background image. */
1926
+ const BACKGROUNDS = new Set([
1927
+ 'body',
1928
+ 'table',
1929
+ 'thead',
1930
+ 'tbody',
1931
+ 'tfoot',
1932
+ 'tr',
1933
+ 'td',
1934
+ 'th',
1935
+ ]);
1936
+
1937
+ function closestBody(el: Element): Element | null {
1938
+ let node = el.parent;
1939
+ while (node) {
1940
+ if (node.type === 'tag' && (node as Element).name === 'body') {
1941
+ return node as Element;
1942
+ }
1943
+ node = node.parent;
1944
+ }
1945
+ return null;
1946
+ }
1947
+
1948
+ /** What the `width` and `height` attributes give a ratio as well. */
1949
+ const RATIO_SIZED = new Set(['img', 'video']);
1950
+
576
1951
  const SIZED = new Set([
577
1952
  'img',
578
1953
  'table',
@@ -582,7 +1957,6 @@ const SIZED = new Set([
582
1957
  'colgroup',
583
1958
  'iframe',
584
1959
  'video',
585
- 'canvas',
586
1960
  'object',
587
1961
  'embed',
588
1962
  'hr',
@@ -596,6 +1970,18 @@ const OL_TYPES: Record<string, string> = {
596
1970
  I: 'upper-roman',
597
1971
  };
598
1972
 
1973
+ /** What a `<ul type>` or an `<li type>` names that is not a number. */
1974
+ const BULLETS = new Set(['disc', 'circle', 'square', 'none']);
1975
+
1976
+ /** An attribute read as HTML reads an integer: its leading digits, signed,
1977
+ * whatever follows them; null where it has none. A counter is a 32-bit
1978
+ * integer in a browser, and one past that is kept to it. */
1979
+ function htmlInteger(value: string | undefined): number | null {
1980
+ const match = value === undefined ? null : /^\s*([+-]?\d+)/.exec(value);
1981
+ if (!match) return null;
1982
+ return Math.max(-2147483648, Math.min(2147483647, Number(match[1])));
1983
+ }
1984
+
599
1985
  const FONT_SIZE_STEPS = [1, 0.63, 0.82, 1, 1.13, 1.5, 2, 3];
600
1986
 
601
1987
  /** A presentational length: bare numbers are pixels, `50%` stays a percent. */
@@ -615,3 +2001,36 @@ function closestTable(el: Element): Element | null {
615
2001
  }
616
2002
  return null;
617
2003
  }
2004
+
2005
+ /** The generic fixed-width family's size to everyone else's: 13 to 16. */
2006
+ const FIXED_SIZE = 13 / 16;
2007
+
2008
+ /** Whether a family list is the generic `monospace` and nothing else,
2009
+ * which has the smaller size; a list with a fallback after it does not,
2010
+ * so `monospace, monospace` keeps the size it had. */
2011
+ function monospaceOnly(family: string): boolean {
2012
+ return family.length === 9 && family.toLowerCase() === 'monospace';
2013
+ }
2014
+
2015
+ /** Whether a `font-size`, or the size in a `font`, is an absolute-size
2016
+ * keyword, or `initial`, which is `medium`. */
2017
+ function sizeKeyword(prop: string, value: string): boolean {
2018
+ const words = value.trim().toLowerCase();
2019
+ if (prop === 'font-size') return ABSOLUTE_SIZES.has(words);
2020
+ for (const word of words.split(/[\s/]+/)) {
2021
+ if (ABSOLUTE_SIZES.has(word)) return true;
2022
+ }
2023
+ return false;
2024
+ }
2025
+
2026
+ const ABSOLUTE_SIZES = new Set([
2027
+ 'xx-small',
2028
+ 'x-small',
2029
+ 'small',
2030
+ 'medium',
2031
+ 'large',
2032
+ 'x-large',
2033
+ 'xx-large',
2034
+ 'xxx-large',
2035
+ 'initial',
2036
+ ]);