@react-x11/components 0.8.0 → 0.10.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 (721) hide show
  1. package/README.md +105 -38
  2. package/dist/charts/node.d.ts +12 -2
  3. package/dist/charts/node.d.ts.map +1 -1
  4. package/dist/charts/node.js +50 -16
  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 +3 -1
  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/languages/glsl.d.ts.map +1 -1
  24. package/dist/code-language/languages/glsl.js +10 -2
  25. package/dist/code-language/languages/glsl.js.map +1 -1
  26. package/dist/code-language/languages/javascript.d.ts.map +1 -1
  27. package/dist/code-language/languages/javascript.js +18 -3
  28. package/dist/code-language/languages/javascript.js.map +1 -1
  29. package/dist/code-language/languages/json.d.ts.map +1 -1
  30. package/dist/code-language/languages/json.js +10 -2
  31. package/dist/code-language/languages/json.js.map +1 -1
  32. package/dist/code-language/languages/shell.d.ts.map +1 -1
  33. package/dist/code-language/languages/shell.js +10 -2
  34. package/dist/code-language/languages/shell.js.map +1 -1
  35. package/dist/code-language/languages/sql.d.ts.map +1 -1
  36. package/dist/code-language/languages/sql.js +20 -2
  37. package/dist/code-language/languages/sql.js.map +1 -1
  38. package/dist/code-language/stream.d.ts +22 -0
  39. package/dist/code-language/stream.d.ts.map +1 -1
  40. package/dist/code-language/stream.js +259 -9
  41. package/dist/code-language/stream.js.map +1 -1
  42. package/dist/code-language/types.d.ts +5 -4
  43. package/dist/code-language/types.d.ts.map +1 -1
  44. package/dist/codeblock/index.d.ts +13 -2
  45. package/dist/codeblock/index.d.ts.map +1 -1
  46. package/dist/codeblock/index.js +20 -3
  47. package/dist/codeblock/index.js.map +1 -1
  48. package/dist/embed/client.d.ts +48 -3
  49. package/dist/embed/client.d.ts.map +1 -1
  50. package/dist/embed/client.js +79 -5
  51. package/dist/embed/client.js.map +1 -1
  52. package/dist/embed/index.d.ts +1 -1
  53. package/dist/embed/index.d.ts.map +1 -1
  54. package/dist/embed/index.js +1 -1
  55. package/dist/embed/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 +240 -6
  115. package/dist/html/css/cascade.d.ts.map +1 -1
  116. package/dist/html/css/cascade.js +1327 -66
  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 +498 -15
  139. package/dist/html/css/style.d.ts.map +1 -1
  140. package/dist/html/css/style.js +3698 -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 +57 -11
  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 +2937 -197
  172. package/dist/html/layout/block.js.map +1 -1
  173. package/dist/html/layout/boxes.d.ts +340 -17
  174. package/dist/html/layout/boxes.d.ts.map +1 -1
  175. package/dist/html/layout/boxes.js +1714 -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 +151 -1
  205. package/dist/html/layout/inline.d.ts.map +1 -1
  206. package/dist/html/layout/inline.js +3375 -174
  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 +172 -5
  221. package/dist/html/node.d.ts.map +1 -1
  222. package/dist/html/node.js +1336 -103
  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 +3503 -141
  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 +435 -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 +2 -2
  245. package/dist/index.d.ts.map +1 -1
  246. package/dist/index.js +1 -1
  247. package/dist/index.js.map +1 -1
  248. package/dist/internal/collate.d.ts +3 -0
  249. package/dist/internal/collate.d.ts.map +1 -0
  250. package/dist/internal/collate.js +11 -0
  251. package/dist/internal/collate.js.map +1 -0
  252. package/dist/{markdown → internal/markdown}/ast.d.ts +23 -0
  253. package/dist/internal/markdown/ast.d.ts.map +1 -0
  254. package/dist/internal/markdown/ast.js +22 -0
  255. package/dist/internal/markdown/ast.js.map +1 -0
  256. package/dist/internal/markdown/parse.d.ts.map +1 -0
  257. package/dist/{markdown → internal/markdown}/parse.js +221 -30
  258. package/dist/internal/markdown/parse.js.map +1 -0
  259. package/dist/internal/markdown/stringify.d.ts +4 -0
  260. package/dist/internal/markdown/stringify.d.ts.map +1 -0
  261. package/dist/internal/markdown/stringify.js +684 -0
  262. package/dist/internal/markdown/stringify.js.map +1 -0
  263. package/dist/internal/markdown/tags.d.ts.map +1 -0
  264. package/dist/internal/markdown/tags.js.map +1 -0
  265. package/dist/internal/prewarm.d.ts +18 -0
  266. package/dist/internal/prewarm.d.ts.map +1 -0
  267. package/dist/internal/prewarm.js +42 -0
  268. package/dist/internal/prewarm.js.map +1 -0
  269. package/dist/internal/scroll.d.ts +3 -2
  270. package/dist/internal/scroll.d.ts.map +1 -1
  271. package/dist/internal/scroll.js +1 -0
  272. package/dist/internal/scroll.js.map +1 -1
  273. package/dist/internal/sdf.d.ts +20 -0
  274. package/dist/internal/sdf.d.ts.map +1 -0
  275. package/dist/internal/sdf.js +216 -0
  276. package/dist/internal/sdf.js.map +1 -0
  277. package/dist/internal/splice.d.ts +12 -0
  278. package/dist/internal/splice.d.ts.map +1 -0
  279. package/dist/internal/splice.js +23 -0
  280. package/dist/internal/splice.js.map +1 -0
  281. package/dist/internal/window.d.ts +12 -0
  282. package/dist/internal/window.d.ts.map +1 -1
  283. package/dist/internal/window.js +23 -6
  284. package/dist/internal/window.js.map +1 -1
  285. package/dist/maps/anchors.d.ts +103 -0
  286. package/dist/maps/anchors.d.ts.map +1 -0
  287. package/dist/maps/anchors.js +790 -0
  288. package/dist/maps/anchors.js.map +1 -0
  289. package/dist/maps/controller.d.ts +340 -0
  290. package/dist/maps/controller.d.ts.map +1 -0
  291. package/dist/maps/controller.js +822 -0
  292. package/dist/maps/controller.js.map +1 -0
  293. package/dist/maps/gl/buckets.d.ts +99 -0
  294. package/dist/maps/gl/buckets.d.ts.map +1 -0
  295. package/dist/maps/gl/buckets.js +365 -0
  296. package/dist/maps/gl/buckets.js.map +1 -0
  297. package/dist/maps/gl/build-worker.d.ts +15 -0
  298. package/dist/maps/gl/build-worker.d.ts.map +1 -0
  299. package/dist/maps/gl/build-worker.js +44 -0
  300. package/dist/maps/gl/build-worker.js.map +1 -0
  301. package/dist/maps/gl/color.d.ts +8 -0
  302. package/dist/maps/gl/color.d.ts.map +1 -0
  303. package/dist/maps/gl/color.js +57 -0
  304. package/dist/maps/gl/color.js.map +1 -0
  305. package/dist/maps/gl/cover.d.ts +70 -0
  306. package/dist/maps/gl/cover.d.ts.map +1 -0
  307. package/dist/maps/gl/cover.js +137 -0
  308. package/dist/maps/gl/cover.js.map +1 -0
  309. package/dist/maps/gl/index.d.ts +3 -0
  310. package/dist/maps/gl/index.d.ts.map +1 -0
  311. package/dist/maps/gl/index.js +20 -0
  312. package/dist/maps/gl/index.js.map +1 -0
  313. package/dist/maps/gl/markers.d.ts +28 -0
  314. package/dist/maps/gl/markers.d.ts.map +1 -0
  315. package/dist/maps/gl/markers.js +60 -0
  316. package/dist/maps/gl/markers.js.map +1 -0
  317. package/dist/maps/gl/overlays.d.ts +68 -0
  318. package/dist/maps/gl/overlays.d.ts.map +1 -0
  319. package/dist/maps/gl/overlays.js +300 -0
  320. package/dist/maps/gl/overlays.js.map +1 -0
  321. package/dist/maps/gl/pane.d.ts +12 -0
  322. package/dist/maps/gl/pane.d.ts.map +1 -0
  323. package/dist/maps/gl/pane.js +37 -0
  324. package/dist/maps/gl/pane.js.map +1 -0
  325. package/dist/maps/gl/placement.d.ts +124 -0
  326. package/dist/maps/gl/placement.d.ts.map +1 -0
  327. package/dist/maps/gl/placement.js +642 -0
  328. package/dist/maps/gl/placement.js.map +1 -0
  329. package/dist/maps/gl/renderer.d.ts +384 -0
  330. package/dist/maps/gl/renderer.d.ts.map +1 -0
  331. package/dist/maps/gl/renderer.js +1184 -0
  332. package/dist/maps/gl/renderer.js.map +1 -0
  333. package/dist/maps/gl/shaders.d.ts +121 -0
  334. package/dist/maps/gl/shaders.d.ts.map +1 -0
  335. package/dist/maps/gl/shaders.js +453 -0
  336. package/dist/maps/gl/shaders.js.map +1 -0
  337. package/dist/maps/gl/store.d.ts +125 -0
  338. package/dist/maps/gl/store.d.ts.map +1 -0
  339. package/dist/maps/gl/store.js +512 -0
  340. package/dist/maps/gl/store.js.map +1 -0
  341. package/dist/maps/gl/text.d.ts +283 -0
  342. package/dist/maps/gl/text.d.ts.map +1 -0
  343. package/dist/maps/gl/text.js +698 -0
  344. package/dist/maps/gl/text.js.map +1 -0
  345. package/dist/maps/gl/view.d.ts +345 -0
  346. package/dist/maps/gl/view.d.ts.map +1 -0
  347. package/dist/maps/gl/view.js +1209 -0
  348. package/dist/maps/gl/view.js.map +1 -0
  349. package/dist/maps/icons.d.ts +32 -0
  350. package/dist/maps/icons.d.ts.map +1 -0
  351. package/dist/maps/icons.js +151 -0
  352. package/dist/maps/icons.js.map +1 -0
  353. package/dist/maps/index.d.ts +3 -1
  354. package/dist/maps/index.d.ts.map +1 -1
  355. package/dist/maps/index.js +218 -52
  356. package/dist/maps/index.js.map +1 -1
  357. package/dist/maps/labels.d.ts +91 -18
  358. package/dist/maps/labels.d.ts.map +1 -1
  359. package/dist/maps/labels.js +376 -140
  360. package/dist/maps/labels.js.map +1 -1
  361. package/dist/maps/node.d.ts +179 -56
  362. package/dist/maps/node.d.ts.map +1 -1
  363. package/dist/maps/node.js +663 -537
  364. package/dist/maps/node.js.map +1 -1
  365. package/dist/maps/overlay.d.ts +72 -9
  366. package/dist/maps/overlay.d.ts.map +1 -1
  367. package/dist/maps/overlay.js +108 -28
  368. package/dist/maps/overlay.js.map +1 -1
  369. package/dist/maps/paint.d.ts +3 -0
  370. package/dist/maps/paint.d.ts.map +1 -1
  371. package/dist/maps/paint.js.map +1 -1
  372. package/dist/maps/proj.d.ts +62 -0
  373. package/dist/maps/proj.d.ts.map +1 -1
  374. package/dist/maps/proj.js +101 -0
  375. package/dist/maps/proj.js.map +1 -1
  376. package/dist/maps/renderer.d.ts +69 -0
  377. package/dist/maps/renderer.d.ts.map +1 -0
  378. package/dist/maps/renderer.js +66 -0
  379. package/dist/maps/renderer.js.map +1 -0
  380. package/dist/maps/sources.d.ts +30 -6
  381. package/dist/maps/sources.d.ts.map +1 -1
  382. package/dist/maps/sources.js +22 -3
  383. package/dist/maps/sources.js.map +1 -1
  384. package/dist/maps/style.d.ts +35 -0
  385. package/dist/maps/style.d.ts.map +1 -1
  386. package/dist/maps/style.js.map +1 -1
  387. package/dist/maps/styles.d.ts +12 -0
  388. package/dist/maps/styles.d.ts.map +1 -1
  389. package/dist/maps/styles.js +120 -6
  390. package/dist/maps/styles.js.map +1 -1
  391. package/dist/maps/theme.d.ts +19 -0
  392. package/dist/maps/theme.d.ts.map +1 -0
  393. package/dist/maps/theme.js +44 -0
  394. package/dist/maps/theme.js.map +1 -0
  395. package/dist/maps/tiles.d.ts +147 -27
  396. package/dist/maps/tiles.d.ts.map +1 -1
  397. package/dist/maps/tiles.js +221 -54
  398. package/dist/maps/tiles.js.map +1 -1
  399. package/dist/maps/types.d.ts +213 -21
  400. package/dist/maps/types.d.ts.map +1 -1
  401. package/dist/markdown/expressions.d.ts +1 -1
  402. package/dist/markdown/expressions.d.ts.map +1 -1
  403. package/dist/markdown/expressions.js.map +1 -1
  404. package/dist/markdown/index.d.ts +7 -6
  405. package/dist/markdown/index.d.ts.map +1 -1
  406. package/dist/markdown/index.js +121 -50
  407. package/dist/markdown/index.js.map +1 -1
  408. package/dist/markdown/spans.d.ts +1 -1
  409. package/dist/markdown/spans.d.ts.map +1 -1
  410. package/dist/markdown/spans.js +6 -0
  411. package/dist/markdown/spans.js.map +1 -1
  412. package/dist/media-player/index.d.ts +11 -3
  413. package/dist/media-player/index.d.ts.map +1 -1
  414. package/dist/media-player/index.js +13 -2
  415. package/dist/media-player/index.js.map +1 -1
  416. package/dist/qml/containers.d.ts.map +1 -1
  417. package/dist/qml/containers.js +6 -1
  418. package/dist/qml/containers.js.map +1 -1
  419. package/dist/qml/interaction.d.ts.map +1 -1
  420. package/dist/qml/interaction.js +38 -5
  421. package/dist/qml/interaction.js.map +1 -1
  422. package/dist/qml/layouts.d.ts.map +1 -1
  423. package/dist/qml/layouts.js +21 -6
  424. package/dist/qml/layouts.js.map +1 -1
  425. package/dist/qml/qtquick.d.ts.map +1 -1
  426. package/dist/qml/qtquick.js +9 -2
  427. package/dist/qml/qtquick.js.map +1 -1
  428. package/dist/qml/react.d.ts.map +1 -1
  429. package/dist/qml/react.js +9 -7
  430. package/dist/qml/react.js.map +1 -1
  431. package/dist/reorder/clock.d.ts +7 -0
  432. package/dist/reorder/clock.d.ts.map +1 -0
  433. package/dist/reorder/clock.js +26 -0
  434. package/dist/reorder/clock.js.map +1 -0
  435. package/dist/reorder/index.d.ts +25 -3
  436. package/dist/reorder/index.d.ts.map +1 -1
  437. package/dist/reorder/index.js +64 -19
  438. package/dist/reorder/index.js.map +1 -1
  439. package/dist/rich-text-editor/clipboard.d.ts +23 -0
  440. package/dist/rich-text-editor/clipboard.d.ts.map +1 -0
  441. package/dist/rich-text-editor/clipboard.js +307 -0
  442. package/dist/rich-text-editor/clipboard.js.map +1 -0
  443. package/dist/rich-text-editor/collab.d.ts +32 -0
  444. package/dist/rich-text-editor/collab.d.ts.map +1 -0
  445. package/dist/rich-text-editor/collab.js +55 -0
  446. package/dist/rich-text-editor/collab.js.map +1 -0
  447. package/dist/rich-text-editor/commands.d.ts +50 -0
  448. package/dist/rich-text-editor/commands.d.ts.map +1 -0
  449. package/dist/rich-text-editor/commands.js +376 -0
  450. package/dist/rich-text-editor/commands.js.map +1 -0
  451. package/dist/rich-text-editor/drag.d.ts +68 -0
  452. package/dist/rich-text-editor/drag.d.ts.map +1 -0
  453. package/dist/rich-text-editor/drag.js +148 -0
  454. package/dist/rich-text-editor/drag.js.map +1 -0
  455. package/dist/rich-text-editor/html.d.ts +145 -0
  456. package/dist/rich-text-editor/html.d.ts.map +1 -0
  457. package/dist/rich-text-editor/html.js +516 -0
  458. package/dist/rich-text-editor/html.js.map +1 -0
  459. package/dist/rich-text-editor/index.d.ts +252 -0
  460. package/dist/rich-text-editor/index.d.ts.map +1 -0
  461. package/dist/rich-text-editor/index.js +856 -0
  462. package/dist/rich-text-editor/index.js.map +1 -0
  463. package/dist/rich-text-editor/inline.d.ts +94 -0
  464. package/dist/rich-text-editor/inline.d.ts.map +1 -0
  465. package/dist/rich-text-editor/inline.js +252 -0
  466. package/dist/rich-text-editor/inline.js.map +1 -0
  467. package/dist/rich-text-editor/keymap.d.ts +71 -0
  468. package/dist/rich-text-editor/keymap.d.ts.map +1 -0
  469. package/dist/rich-text-editor/keymap.js +394 -0
  470. package/dist/rich-text-editor/keymap.js.map +1 -0
  471. package/dist/rich-text-editor/keys.d.ts +40 -0
  472. package/dist/rich-text-editor/keys.d.ts.map +1 -0
  473. package/dist/rich-text-editor/keys.js +202 -0
  474. package/dist/rich-text-editor/keys.js.map +1 -0
  475. package/dist/rich-text-editor/look.d.ts +71 -0
  476. package/dist/rich-text-editor/look.d.ts.map +1 -0
  477. package/dist/rich-text-editor/look.js +342 -0
  478. package/dist/rich-text-editor/look.js.map +1 -0
  479. package/dist/rich-text-editor/markdown.d.ts +18 -0
  480. package/dist/rich-text-editor/markdown.d.ts.map +1 -0
  481. package/dist/rich-text-editor/markdown.js +496 -0
  482. package/dist/rich-text-editor/markdown.js.map +1 -0
  483. package/dist/rich-text-editor/marks.d.ts +9 -0
  484. package/dist/rich-text-editor/marks.d.ts.map +1 -0
  485. package/dist/rich-text-editor/marks.js +83 -0
  486. package/dist/rich-text-editor/marks.js.map +1 -0
  487. package/dist/rich-text-editor/nodes.d.ts +135 -0
  488. package/dist/rich-text-editor/nodes.d.ts.map +1 -0
  489. package/dist/rich-text-editor/nodes.js +397 -0
  490. package/dist/rich-text-editor/nodes.js.map +1 -0
  491. package/dist/rich-text-editor/render.d.ts +68 -0
  492. package/dist/rich-text-editor/render.d.ts.map +1 -0
  493. package/dist/rich-text-editor/render.js +518 -0
  494. package/dist/rich-text-editor/render.js.map +1 -0
  495. package/dist/rich-text-editor/schema.d.ts +16 -0
  496. package/dist/rich-text-editor/schema.d.ts.map +1 -0
  497. package/dist/rich-text-editor/schema.js +386 -0
  498. package/dist/rich-text-editor/schema.js.map +1 -0
  499. package/dist/rich-text-editor/store.d.ts +11 -0
  500. package/dist/rich-text-editor/store.d.ts.map +1 -0
  501. package/dist/rich-text-editor/store.js +43 -0
  502. package/dist/rich-text-editor/store.js.map +1 -0
  503. package/dist/rich-text-editor/suggest.d.ts +127 -0
  504. package/dist/rich-text-editor/suggest.d.ts.map +1 -0
  505. package/dist/rich-text-editor/suggest.js +0 -0
  506. package/dist/rich-text-editor/suggest.js.map +1 -0
  507. package/dist/rich-text-editor/tables.d.ts +41 -0
  508. package/dist/rich-text-editor/tables.d.ts.map +1 -0
  509. package/dist/rich-text-editor/tables.js +204 -0
  510. package/dist/rich-text-editor/tables.js.map +1 -0
  511. package/dist/rich-text-editor/toolbar.d.ts +58 -0
  512. package/dist/rich-text-editor/toolbar.d.ts.map +1 -0
  513. package/dist/rich-text-editor/toolbar.js +548 -0
  514. package/dist/rich-text-editor/toolbar.js.map +1 -0
  515. package/dist/rich-text-editor/view.d.ts +368 -0
  516. package/dist/rich-text-editor/view.d.ts.map +1 -0
  517. package/dist/rich-text-editor/view.js +1968 -0
  518. package/dist/rich-text-editor/view.js.map +1 -0
  519. package/dist/rich-text-editor/virtual.d.ts +54 -0
  520. package/dist/rich-text-editor/virtual.d.ts.map +1 -0
  521. package/dist/rich-text-editor/virtual.js +279 -0
  522. package/dist/rich-text-editor/virtual.js.map +1 -0
  523. package/dist/richtext/node.d.ts +71 -4
  524. package/dist/richtext/node.d.ts.map +1 -1
  525. package/dist/richtext/node.js +233 -28
  526. package/dist/richtext/node.js.map +1 -1
  527. package/dist/richtext/runs.d.ts +13 -8
  528. package/dist/richtext/runs.d.ts.map +1 -1
  529. package/dist/richtext/runs.js +18 -8
  530. package/dist/richtext/runs.js.map +1 -1
  531. package/dist/table/index.d.ts.map +1 -1
  532. package/dist/table/index.js +23 -0
  533. package/dist/table/index.js.map +1 -1
  534. package/dist/table/rows.d.ts +3 -2
  535. package/dist/table/rows.d.ts.map +1 -1
  536. package/dist/table/rows.js +20 -6
  537. package/dist/table/rows.js.map +1 -1
  538. package/dist/terminal/backends.d.ts +4 -1
  539. package/dist/terminal/backends.d.ts.map +1 -1
  540. package/dist/terminal/backends.js.map +1 -1
  541. package/dist/terminal/index.d.ts +18 -7
  542. package/dist/terminal/index.d.ts.map +1 -1
  543. package/dist/terminal/index.js +32 -11
  544. package/dist/terminal/index.js.map +1 -1
  545. package/dist/terminal/vt/pty.d.ts +13 -2
  546. package/dist/terminal/vt/pty.d.ts.map +1 -1
  547. package/dist/terminal/vt/pty.js +18 -3
  548. package/dist/terminal/vt/pty.js.map +1 -1
  549. package/dist/terminal-output/index.d.ts.map +1 -1
  550. package/dist/terminal-output/index.js +6 -1
  551. package/dist/terminal-output/index.js.map +1 -1
  552. package/dist/three/canvas.d.ts.map +1 -1
  553. package/dist/three/canvas.js +85 -7
  554. package/dist/three/canvas.js.map +1 -1
  555. package/dist/three/events.d.ts +22 -4
  556. package/dist/three/events.d.ts.map +1 -1
  557. package/dist/three/events.js +34 -4
  558. package/dist/three/events.js.map +1 -1
  559. package/dist/tree/index.d.ts.map +1 -1
  560. package/dist/tree/index.js +17 -0
  561. package/dist/tree/index.js.map +1 -1
  562. package/package.json +59 -31
  563. package/src/charts/node.ts +68 -28
  564. package/src/charts/render.ts +120 -30
  565. package/src/code/index.ts +3 -1
  566. package/src/code-editor/completion.ts +3 -2
  567. package/src/code-editor/index.ts +19 -0
  568. package/src/code-editor/node.ts +1574 -184
  569. package/src/code-language/languages/glsl.ts +12 -2
  570. package/src/code-language/languages/javascript.ts +21 -4
  571. package/src/code-language/languages/json.ts +12 -2
  572. package/src/code-language/languages/shell.ts +12 -2
  573. package/src/code-language/languages/sql.ts +22 -2
  574. package/src/code-language/stream.ts +257 -17
  575. package/src/code-language/types.ts +5 -4
  576. package/src/codeblock/index.ts +20 -3
  577. package/src/embed/client.ts +102 -8
  578. package/src/embed/index.ts +2 -0
  579. package/src/flow/draw.ts +167 -41
  580. package/src/flow/gl/color.ts +85 -0
  581. package/src/flow/gl/index.ts +342 -0
  582. package/src/flow/gl/pack.ts +585 -0
  583. package/src/flow/gl/renderer.ts +679 -0
  584. package/src/flow/gl/shaders.ts +371 -0
  585. package/src/flow/gl/text.ts +1295 -0
  586. package/src/flow/index.ts +794 -67
  587. package/src/flow/node.ts +2496 -1256
  588. package/src/flow/paint.ts +511 -0
  589. package/src/flow/paths.ts +80 -15
  590. package/src/flow/scene.ts +2199 -0
  591. package/src/flow/types.ts +113 -1
  592. package/src/html/controls.ts +73 -2
  593. package/src/html/css/calc.ts +385 -0
  594. package/src/html/css/cascade.ts +1403 -62
  595. package/src/html/css/color.ts +755 -0
  596. package/src/html/css/content.ts +417 -0
  597. package/src/html/css/counter-styles.ts +973 -0
  598. package/src/html/css/decode.ts +130 -0
  599. package/src/html/css/parse.ts +1514 -160
  600. package/src/html/css/style.ts +4102 -222
  601. package/src/html/css/ua.ts +58 -12
  602. package/src/html/css/values.ts +150 -50
  603. package/src/html/css/vars.ts +348 -0
  604. package/src/html/dom.ts +284 -17
  605. package/src/html/fonts.ts +641 -0
  606. package/src/html/gif.ts +233 -0
  607. package/src/html/index.ts +180 -20
  608. package/src/html/layout/block.ts +3456 -230
  609. package/src/html/layout/boxes.ts +2100 -190
  610. package/src/html/layout/cache.ts +289 -0
  611. package/src/html/layout/collapse.ts +317 -0
  612. package/src/html/layout/css-grid.ts +1063 -0
  613. package/src/html/layout/flex.ts +1115 -106
  614. package/src/html/layout/floats.ts +128 -14
  615. package/src/html/layout/grid-lines.ts +141 -0
  616. package/src/html/layout/grid.ts +181 -0
  617. package/src/html/layout/inline.ts +3943 -192
  618. package/src/html/layout/shaping.ts +140 -0
  619. package/src/html/layout/table.ts +696 -186
  620. package/src/html/layout/tracks.ts +437 -0
  621. package/src/html/node.ts +1486 -121
  622. package/src/html/paint.ts +4293 -169
  623. package/src/html/resources.ts +161 -50
  624. package/src/html/surfaces.ts +81 -0
  625. package/src/html/svg.ts +493 -0
  626. package/src/html/url.ts +86 -0
  627. package/src/index.ts +4 -0
  628. package/src/internal/collate.ts +12 -0
  629. package/src/{markdown → internal/markdown}/ast.ts +35 -7
  630. package/src/{markdown → internal/markdown}/parse.ts +288 -35
  631. package/src/internal/markdown/stringify.ts +772 -0
  632. package/src/internal/prewarm.ts +45 -0
  633. package/src/internal/scroll.ts +5 -3
  634. package/src/internal/sdf.ts +227 -0
  635. package/src/internal/splice.ts +25 -0
  636. package/src/internal/window.ts +36 -5
  637. package/src/maps/anchors.ts +913 -0
  638. package/src/maps/controller.ts +1084 -0
  639. package/src/maps/gl/buckets.ts +461 -0
  640. package/src/maps/gl/build-worker.ts +66 -0
  641. package/src/maps/gl/color.ts +62 -0
  642. package/src/maps/gl/cover.ts +205 -0
  643. package/src/maps/gl/index.ts +23 -0
  644. package/src/maps/gl/markers.ts +83 -0
  645. package/src/maps/gl/overlays.ts +379 -0
  646. package/src/maps/gl/pane.ts +42 -0
  647. package/src/maps/gl/placement.ts +968 -0
  648. package/src/maps/gl/renderer.ts +1660 -0
  649. package/src/maps/gl/shaders.ts +489 -0
  650. package/src/maps/gl/store.ts +615 -0
  651. package/src/maps/gl/text.ts +868 -0
  652. package/src/maps/gl/view.ts +1489 -0
  653. package/src/maps/icons.ts +183 -0
  654. package/src/maps/index.ts +274 -81
  655. package/src/maps/labels.ts +457 -144
  656. package/src/maps/node.ts +832 -665
  657. package/src/maps/overlay.ts +148 -43
  658. package/src/maps/paint.ts +3 -0
  659. package/src/maps/proj.ts +118 -0
  660. package/src/maps/renderer.ts +155 -0
  661. package/src/maps/sources.ts +43 -6
  662. package/src/maps/style.ts +32 -0
  663. package/src/maps/styles.ts +215 -4
  664. package/src/maps/theme.ts +51 -0
  665. package/src/maps/tiles.ts +253 -62
  666. package/src/maps/types.ts +213 -21
  667. package/src/markdown/expressions.ts +5 -1
  668. package/src/markdown/index.ts +163 -78
  669. package/src/markdown/spans.ts +6 -1
  670. package/src/media-player/index.ts +18 -4
  671. package/src/qml/containers.tsx +6 -1
  672. package/src/qml/interaction.tsx +42 -5
  673. package/src/qml/layouts.tsx +21 -6
  674. package/src/qml/qtquick.tsx +9 -2
  675. package/src/qml/react.tsx +7 -5
  676. package/src/reorder/clock.ts +27 -0
  677. package/src/reorder/index.ts +100 -27
  678. package/src/rich-text-editor/clipboard.ts +439 -0
  679. package/src/rich-text-editor/collab.ts +91 -0
  680. package/src/rich-text-editor/commands.ts +448 -0
  681. package/src/rich-text-editor/dom-globals.d.ts +23 -0
  682. package/src/rich-text-editor/drag.ts +211 -0
  683. package/src/rich-text-editor/html.ts +619 -0
  684. package/src/rich-text-editor/index.ts +1364 -0
  685. package/src/rich-text-editor/inline.ts +345 -0
  686. package/src/rich-text-editor/keymap.ts +510 -0
  687. package/src/rich-text-editor/keys.ts +228 -0
  688. package/src/rich-text-editor/look.ts +427 -0
  689. package/src/rich-text-editor/markdown.ts +653 -0
  690. package/src/rich-text-editor/marks.ts +101 -0
  691. package/src/rich-text-editor/nodes.ts +497 -0
  692. package/src/rich-text-editor/render.ts +837 -0
  693. package/src/rich-text-editor/schema.ts +431 -0
  694. package/src/rich-text-editor/store.ts +39 -0
  695. package/src/rich-text-editor/suggest.ts +597 -0
  696. package/src/rich-text-editor/tables.ts +272 -0
  697. package/src/rich-text-editor/toolbar.ts +724 -0
  698. package/src/rich-text-editor/view.ts +2287 -0
  699. package/src/rich-text-editor/virtual.ts +372 -0
  700. package/src/richtext/node.ts +294 -44
  701. package/src/richtext/runs.ts +23 -12
  702. package/src/table/index.ts +29 -0
  703. package/src/table/rows.ts +18 -8
  704. package/src/terminal/backends.ts +4 -1
  705. package/src/terminal/index.ts +47 -15
  706. package/src/terminal/vt/pty.ts +16 -2
  707. package/src/terminal-output/index.ts +6 -1
  708. package/src/three/canvas.ts +102 -8
  709. package/src/three/events.ts +49 -9
  710. package/src/tree/index.ts +19 -0
  711. package/dist/markdown/ast.d.ts.map +0 -1
  712. package/dist/markdown/ast.js +0 -17
  713. package/dist/markdown/ast.js.map +0 -1
  714. package/dist/markdown/parse.d.ts.map +0 -1
  715. package/dist/markdown/parse.js.map +0 -1
  716. package/dist/markdown/tags.d.ts.map +0 -1
  717. package/dist/markdown/tags.js.map +0 -1
  718. /package/dist/{markdown → internal/markdown}/parse.d.ts +0 -0
  719. /package/dist/{markdown → internal/markdown}/tags.d.ts +0 -0
  720. /package/dist/{markdown → internal/markdown}/tags.js +0 -0
  721. /package/src/{markdown → internal/markdown}/tags.ts +0 -0
@@ -12,11 +12,59 @@
12
12
  // - **Nothing here depends on the containing block.** Percentages and `auto`
13
13
  // survive as `Len`s (see values.ts). A style computed at one width is
14
14
  // correct at every width, which is the whole of the resize story.
15
- import { AUTO, fourSides, keywordFontSize, parseAlpha, parseColor, parseLength, parseNumber, parseUrl, parseWeight, splitCommas, splitValue, } from './values.js';
15
+ import { AUTO, alphaOf, fourSides, inkColor, isTransparent, keywordFontSize, parseAlpha, parseColor, isPct, parseLength, parseNumber, parseWeight, resolve, splitCommas, splitValue, } from './values.js';
16
+ import { parseUrl, readIdent, startsIdent } from './parse.js';
17
+ import { DEFAULT_QUOTES, parseContent, parseCounterList, parseListStyleType, parseQuotes, } from './content.js';
18
+ /** Whether a length is one `padding` takes: not `auto`, not negative. A
19
+ * `calc()` with a percentage in it has no sign until layout, which clamps
20
+ * it at zero (CSS Values 4 10.12). */
21
+ function notNegative(len) {
22
+ if (len === AUTO)
23
+ return true;
24
+ if (typeof len === 'number')
25
+ return len >= 0;
26
+ return len.px !== undefined || len.pct >= 0;
27
+ }
28
+ function validPadding(len) {
29
+ if (len === null || len === AUTO)
30
+ return false;
31
+ return notNegative(len);
32
+ }
33
+ /** `auto`, or `rect()` of four lengths or `auto`s, commas between them or
34
+ * not; undefined for anything else, which leaves the value as it was. */
35
+ function parseClip(value, ctx) {
36
+ const v = value.trim();
37
+ if (/^auto$/i.test(v))
38
+ return null;
39
+ const m = /^rect\(([^)]*)\)$/i.exec(v);
40
+ if (!m)
41
+ return undefined;
42
+ const parts = m[1].includes(',')
43
+ ? m[1].split(',').map((p) => p.trim())
44
+ : m[1].trim().split(/\s+/);
45
+ if (parts.length !== 4)
46
+ return undefined;
47
+ const edges = [];
48
+ for (const part of parts) {
49
+ if (/^auto$/i.test(part)) {
50
+ edges.push(null);
51
+ continue;
52
+ }
53
+ const len = parseLength(part, ctx);
54
+ if (typeof len !== 'number')
55
+ return undefined;
56
+ edges.push(len);
57
+ }
58
+ return { top: edges[0], right: edges[1], bottom: edges[2], left: edges[3] };
59
+ }
16
60
  /** The properties that inherit. Named once, so `inherit()` and the `inherit`
17
61
  * keyword cannot disagree about the list. */
18
- const INHERITED = [
62
+ /** The fields a style takes from its parent's (CSS 2.1's "Inherited:
63
+ * yes"), which `inherit` copies one by one; the test holds the two to the
64
+ * same list. */
65
+ export const INHERITED = [
19
66
  'color',
67
+ 'textFillColor',
20
68
  'fontFamily',
21
69
  'fontSize',
22
70
  'fontWeight',
@@ -24,22 +72,49 @@ const INHERITED = [
24
72
  'lineHeight',
25
73
  'lineHeightIsLength',
26
74
  'textAlign',
75
+ 'textAlignLast',
76
+ 'textJustify',
77
+ 'blockEllipsis',
78
+ 'alignBlocks',
27
79
  'textIndent',
28
80
  'textTransform',
81
+ 'textUnderlineOffset',
29
82
  'letterSpacing',
30
83
  'wordSpacing',
84
+ 'textShadow',
85
+ 'fontVariantNumeric',
86
+ 'fontVariantCaps',
87
+ 'fontVariantLigatures',
88
+ 'fontVariantPosition',
89
+ 'fontKerning',
90
+ 'fontFeatureSettings',
91
+ 'tabSize',
92
+ 'tabSizeIsLength',
31
93
  'whiteSpace',
94
+ 'overflowWrap',
95
+ 'wordBreak',
96
+ 'lineBreakAnywhere',
97
+ 'textWrapStyle',
32
98
  'direction',
33
99
  'visibility',
34
100
  'listStyleType',
35
101
  'listStylePosition',
102
+ 'listStyleImage',
36
103
  'cursor',
37
104
  'borderCollapse',
38
105
  'borderSpacing',
106
+ 'borderSpacingY',
107
+ 'captionSide',
108
+ 'emptyCells',
39
109
  'tableTextAlignSet',
110
+ 'quotes',
111
+ 'custom',
40
112
  ];
41
- export function initialStyle(look) {
42
- return {
113
+ export function initialStyle(look, scale = 1) {
114
+ // `medium`, in device pixels
115
+ const medium = BORDER_WIDTH_KEYWORDS.medium * scale;
116
+ // a copy of the literal, not the literal: see `copyStyle`
117
+ return copyStyle({
43
118
  color: look.color,
44
119
  fontFamily: look.fontFamily,
45
120
  fontSize: look.fontSize,
@@ -48,35 +123,95 @@ export function initialStyle(look) {
48
123
  lineHeight: 'normal',
49
124
  lineHeightIsLength: false,
50
125
  textAlign: 'start',
126
+ textAlignLast: 'auto',
127
+ textJustify: 'auto',
128
+ alignBlocks: null,
51
129
  textIndent: 0,
52
130
  textTransform: 'none',
53
131
  letterSpacing: 0,
54
132
  wordSpacing: 0,
133
+ textShadow: null,
134
+ fontVariantNumeric: '',
135
+ fontVariantCaps: '',
136
+ fontVariantLigatures: '',
137
+ fontVariantPosition: '',
138
+ fontKerning: '',
139
+ fontFeatureSettings: '',
140
+ tabSize: 8,
141
+ tabSizeIsLength: false,
55
142
  whiteSpace: 'normal',
143
+ overflowWrap: 'normal',
144
+ wordBreak: 'normal',
145
+ lineBreakAnywhere: false,
146
+ textWrapStyle: 'auto',
56
147
  direction: 'ltr',
148
+ unicodeBidi: 'normal',
57
149
  visibility: 'visible',
58
150
  listStyleType: 'disc',
59
151
  listStylePosition: 'outside',
152
+ listStyleImage: null,
60
153
  cursor: null,
61
154
  borderCollapse: 'separate',
62
- borderSpacing: 2,
155
+ // CSS's initial value; a `<table>` gets its 2px from the UA sheet, and
156
+ // an anonymous table, which no sheet names, has none
157
+ borderSpacing: 0,
158
+ borderSpacingY: 0,
159
+ captionSide: 'top',
160
+ emptyCells: 'show',
63
161
  tableTextAlignSet: false,
162
+ quotes: DEFAULT_QUOTES,
163
+ custom: null,
64
164
  display: 'inline',
165
+ flowRoot: false,
166
+ contain: 0,
167
+ contentVisibility: 'visible',
168
+ overflowClipBox: 'padding-box',
169
+ overflowClipMargin: 0,
170
+ containIntrinsicWidth: null,
171
+ containIntrinsicHeight: null,
65
172
  position: 'static',
66
173
  float: 'none',
67
174
  clear: 'none',
68
175
  boxSizing: 'content-box',
176
+ lineClamp: null,
177
+ maxLines: null,
178
+ clampContinue: 'auto',
179
+ blockEllipsis: false,
180
+ webkitBox: null,
181
+ webkitBoxOrient: 'horizontal',
182
+ webkitBoxDirection: 'normal',
183
+ webkitBoxPack: null,
184
+ webkitBoxAlign: null,
185
+ webkitBoxFlex: 0,
186
+ columns: 0,
187
+ textOverflow: 'clip',
188
+ aspectRatio: null,
189
+ objectFit: 'fill',
190
+ appearance: 'auto',
191
+ objectPositionX: { pct: 50 },
192
+ objectPositionY: { pct: 50 },
69
193
  overflowX: 'visible',
70
194
  overflowY: 'visible',
195
+ clip: null,
71
196
  opacity: 1,
197
+ translate: null,
198
+ transformTranslate: null,
72
199
  zIndex: AUTO,
73
200
  verticalAlign: 'baseline',
74
201
  width: AUTO,
75
202
  height: AUTO,
76
- minWidth: 0,
203
+ // `auto`, which is 0 but for a flex item (CSS Flexbox 4.5): what
204
+ // `min-w-0` undoes has to be told from what it is set to
205
+ minWidth: AUTO,
77
206
  maxWidth: 'none',
78
- minHeight: 0,
207
+ minHeight: AUTO,
79
208
  maxHeight: 'none',
209
+ widthKeyword: null,
210
+ minWidthKeyword: null,
211
+ minHeightKeyword: null,
212
+ maxWidthKeyword: null,
213
+ maxHeightKeyword: null,
214
+ heightKeyword: null,
80
215
  marginTop: 0,
81
216
  marginRight: 0,
82
217
  marginBottom: 0,
@@ -85,10 +220,13 @@ export function initialStyle(look) {
85
220
  paddingRight: 0,
86
221
  paddingBottom: 0,
87
222
  paddingLeft: 0,
88
- borderTopWidth: 0,
89
- borderRightWidth: 0,
90
- borderBottomWidth: 0,
91
- borderLeftWidth: 0,
223
+ // `medium`, and drawn as nothing while the style is `none`: a width
224
+ // computes to zero there (CSS 2.1 8.5.1), which is the layout's to
225
+ // apply, so that `border-style: solid` alone brings a border back
226
+ borderTopWidth: medium,
227
+ borderRightWidth: medium,
228
+ borderBottomWidth: medium,
229
+ borderLeftWidth: medium,
92
230
  borderTopStyle: 'none',
93
231
  borderRightStyle: 'none',
94
232
  borderBottomStyle: 'none',
@@ -100,22 +238,49 @@ export function initialStyle(look) {
100
238
  borderBottomColor: 'currentColor',
101
239
  borderLeftColor: 'currentColor',
102
240
  borderRadius: [0, 0, 0, 0],
241
+ borderRadiusY: null,
242
+ boxShadow: null,
243
+ outlineStyle: 'none',
244
+ outlineWidth: medium,
245
+ outlineColor: 'currentColor',
246
+ outlineOffset: 0,
103
247
  top: AUTO,
104
248
  right: AUTO,
105
249
  bottom: AUTO,
106
250
  left: AUTO,
107
251
  backgroundColor: null,
108
252
  backgroundImage: null,
109
- backgroundRepeat: 'repeat',
253
+ backgroundGradient: null,
254
+ backgroundRepeat: REPEAT,
110
255
  backgroundSize: 'auto',
256
+ backgroundAttachment: 'scroll',
111
257
  backgroundPositionX: 0,
112
258
  backgroundPositionY: 0,
259
+ backgroundImages: null,
260
+ backgroundRepeats: null,
261
+ backgroundSizes: null,
262
+ backgroundAttachments: null,
263
+ backgroundPositions: null,
264
+ backgroundClipText: false,
265
+ backgroundClip: 'border-box',
266
+ backgroundOrigin: 'padding-box',
267
+ backgroundClips: null,
268
+ backgroundOrigins: null,
269
+ borderImage: NO_BORDER_IMAGE,
270
+ textFillColor: null,
113
271
  textDecorationLine: 'none',
114
272
  textDecorationColor: null,
115
273
  textDecorationStyle: 'solid',
274
+ textDecorationThickness: null,
275
+ textUnderlineOffset: null,
276
+ underline: null,
277
+ underlineStyle: 'solid',
278
+ underlineThickness: null,
279
+ underlineOffset: null,
280
+ lineThrough: null,
116
281
  flexDirection: 'row',
117
282
  flexWrap: 'nowrap',
118
- justifyContent: 'flex-start',
283
+ justifyContent: 'normal',
119
284
  alignItems: 'stretch',
120
285
  alignSelf: 'auto',
121
286
  alignContent: 'stretch',
@@ -125,8 +290,54 @@ export function initialStyle(look) {
125
290
  order: 0,
126
291
  rowGap: 0,
127
292
  columnGap: 0,
293
+ grid: false,
294
+ gridColumns: null,
295
+ gridRows: null,
296
+ gridAutoRows: AUTO_TRACK,
297
+ gridAutoColumns: AUTO_TRACK,
298
+ gridAreas: null,
299
+ gridAutoFlow: 'row',
300
+ gridColumnStart: null,
301
+ gridColumnEnd: null,
302
+ gridRowStart: null,
303
+ gridRowEnd: null,
304
+ justifyItems: 'normal',
305
+ justifySelf: 'auto',
128
306
  tableLayout: 'auto',
129
- };
307
+ content: 'normal',
308
+ counterReset: null,
309
+ counterIncrement: null,
310
+ counterSet: null,
311
+ });
312
+ }
313
+ let StyleCopy = null;
314
+ /**
315
+ * A copy of a style, made by a constructor that assigns every field.
316
+ *
317
+ * Never `{ ...style }`. V8 gives an object literal of 128 properties or
318
+ * more a dictionary map — a hash table where a shape would be — and a
319
+ * spread of one adds the copy's properties one at a time: 20 µs a copy at
320
+ * 149 fields, where a literal of 127 spreads in 0.2. A style passed 128 and
321
+ * every `inherit` paid it, and a spread of a style that is not a literal
322
+ * costs 4 µs. A constructor's fields are laid out in the object whatever
323
+ * their number: 0.13 µs a copy, and read as fast as a small literal's.
324
+ *
325
+ * The constructor is written from the fields of the first style copied,
326
+ * which is `initialStyle`'s literal and so every field there is: one added
327
+ * to the style cannot be left out of the copy.
328
+ */
329
+ export function copyStyle(from) {
330
+ StyleCopy ??= copyConstructor(Object.keys(from));
331
+ return new StyleCopy(from);
332
+ }
333
+ function copyConstructor(fields) {
334
+ const body = fields.map((field) => {
335
+ if (!/^[A-Za-z_$][\w$]*$/.test(field)) {
336
+ throw new Error(`a style field that is not a name: ${field}`);
337
+ }
338
+ return `this.${field} = from.${field};`;
339
+ });
340
+ return new Function('from', body.join('\n'));
130
341
  }
131
342
  /**
132
343
  * A child's starting style: the inherited half of `parent`, everything else
@@ -134,17 +345,112 @@ export function initialStyle(look) {
134
345
  * because it is the same object for every element in a pass.
135
346
  */
136
347
  export function inherit(parent, initial) {
137
- const out = { ...initial };
138
- for (const key of INHERITED) {
139
- // The cast is the price of one loop instead of twenty-one assignments:
140
- // both sides are the same key of the same interface, which the indexed
141
- // write cannot see.
142
- out[key] = parent[key];
143
- }
348
+ const out = copyStyle(initial);
349
+ // `INHERITED`, a field at a time: a loop over the names was three
350
+ // quarters of the cost of every element's style, a store through a
351
+ // computed name costing thirty times one through a written one
352
+ out.color = parent.color;
353
+ out.textFillColor = parent.textFillColor;
354
+ out.fontFamily = parent.fontFamily;
355
+ out.fontSize = parent.fontSize;
356
+ out.fontWeight = parent.fontWeight;
357
+ out.fontStyle = parent.fontStyle;
358
+ out.lineHeight = parent.lineHeight;
359
+ out.lineHeightIsLength = parent.lineHeightIsLength;
360
+ out.textAlign = parent.textAlign;
361
+ out.textAlignLast = parent.textAlignLast;
362
+ out.textJustify = parent.textJustify;
363
+ out.blockEllipsis = parent.blockEllipsis;
364
+ out.alignBlocks = parent.alignBlocks;
365
+ out.textIndent = parent.textIndent;
366
+ out.textTransform = parent.textTransform;
367
+ out.letterSpacing = parent.letterSpacing;
368
+ out.wordSpacing = parent.wordSpacing;
369
+ out.textShadow = parent.textShadow;
370
+ out.fontVariantNumeric = parent.fontVariantNumeric;
371
+ out.fontVariantCaps = parent.fontVariantCaps;
372
+ out.fontVariantLigatures = parent.fontVariantLigatures;
373
+ out.fontVariantPosition = parent.fontVariantPosition;
374
+ out.fontKerning = parent.fontKerning;
375
+ out.fontFeatureSettings = parent.fontFeatureSettings;
376
+ out.tabSize = parent.tabSize;
377
+ out.tabSizeIsLength = parent.tabSizeIsLength;
378
+ out.whiteSpace = parent.whiteSpace;
379
+ out.overflowWrap = parent.overflowWrap;
380
+ out.wordBreak = parent.wordBreak;
381
+ out.lineBreakAnywhere = parent.lineBreakAnywhere;
382
+ out.textWrapStyle = parent.textWrapStyle;
383
+ out.direction = parent.direction;
384
+ out.visibility = parent.visibility;
385
+ out.listStyleType = parent.listStyleType;
386
+ out.listStylePosition = parent.listStylePosition;
387
+ out.listStyleImage = parent.listStyleImage;
388
+ out.cursor = parent.cursor;
389
+ out.borderCollapse = parent.borderCollapse;
390
+ out.borderSpacing = parent.borderSpacing;
391
+ out.borderSpacingY = parent.borderSpacingY;
392
+ out.captionSide = parent.captionSide;
393
+ out.emptyCells = parent.emptyCells;
394
+ out.tableTextAlignSet = parent.tableTextAlignSet;
395
+ out.quotes = parent.quotes;
396
+ out.custom = parent.custom;
144
397
  out.borderTopColor = 'currentColor';
145
398
  out.borderRightColor = 'currentColor';
146
399
  out.borderBottomColor = 'currentColor';
147
400
  out.borderLeftColor = 'currentColor';
401
+ out.textUnderlineOffset = parent.textUnderlineOffset;
402
+ // propagated, not inherited: `decorate` drops them where CSS stops them
403
+ out.underline = parent.underline;
404
+ out.underlineStyle = parent.underlineStyle;
405
+ out.underlineThickness = parent.underlineThickness;
406
+ out.underlineOffset = parent.underlineOffset;
407
+ out.lineThrough = parent.lineThrough;
408
+ return out;
409
+ }
410
+ /**
411
+ * The properties that apply to `::first-line` and inherit (CSS Pseudo 4,
412
+ * 2.1.1): its fonts, colour, spacing, transform and decorations. Only
413
+ * these are inherited through it by what is on the line; the rest are the
414
+ * parent's own (2.1.2).
415
+ */
416
+ export const FIRST_LINE_INHERITED = [
417
+ 'color',
418
+ 'textFillColor',
419
+ 'fontFamily',
420
+ 'fontSize',
421
+ 'fontWeight',
422
+ 'fontStyle',
423
+ 'lineHeight',
424
+ 'lineHeightIsLength',
425
+ 'textTransform',
426
+ 'textUnderlineOffset',
427
+ 'letterSpacing',
428
+ 'wordSpacing',
429
+ 'textShadow',
430
+ 'fontVariantNumeric',
431
+ 'fontVariantCaps',
432
+ 'fontVariantLigatures',
433
+ 'fontVariantPosition',
434
+ 'fontKerning',
435
+ 'fontFeatureSettings',
436
+ 'underline',
437
+ 'underlineStyle',
438
+ 'underlineThickness',
439
+ 'underlineOffset',
440
+ 'lineThrough',
441
+ ];
442
+ /**
443
+ * What a box on its block's first line inherits from: its parent's style,
444
+ * with the properties `::first-line` takes from the parent's style on that
445
+ * line (CSS Pseudo 4, 2.1.2) — the pseudo-element's own, for the block's
446
+ * children.
447
+ */
448
+ export function firstLineParent(parent, onLine) {
449
+ const out = copyStyle(parent);
450
+ const to = out;
451
+ const from = onLine;
452
+ for (const name of FIRST_LINE_INHERITED)
453
+ to[name] = from[name];
148
454
  return out;
149
455
  }
150
456
  // --- applying a declaration -------------------------------------------------
@@ -157,11 +463,19 @@ const COLOR_PROPS = {
157
463
  'border-bottom-color': 'borderBottomColor',
158
464
  'border-left-color': 'borderLeftColor',
159
465
  'text-decoration-color': 'textDecorationColor',
466
+ '-webkit-text-fill-color': 'textFillColor',
160
467
  };
161
468
  const SIDE_PROPS = {
162
469
  margin: ['marginTop', 'marginRight', 'marginBottom', 'marginLeft'],
163
470
  padding: ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
471
+ inset: ['top', 'right', 'bottom', 'left'],
164
472
  };
473
+ /** An `outline-style`: a border style but `hidden`, or `auto`. */
474
+ function outlineStyleOf(v) {
475
+ if (v === 'auto')
476
+ return 'auto';
477
+ return BORDER_STYLES.has(v) && v !== 'hidden' ? v : null;
478
+ }
165
479
  const BORDER_STYLES = new Set([
166
480
  'none',
167
481
  'hidden',
@@ -174,6 +488,8 @@ const BORDER_STYLES = new Set([
174
488
  'inset',
175
489
  'outset',
176
490
  ]);
491
+ /** The largest font size, in CSS pixels (`font-size`). */
492
+ const MAX_FONT_SIZE = 10000;
177
493
  const BORDER_WIDTH_KEYWORDS = {
178
494
  thin: 1,
179
495
  medium: 3,
@@ -192,6 +508,13 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
192
508
  let value = rawValue.trim();
193
509
  if (!value)
194
510
  return;
511
+ // a logical property is the physical one it stands for, the CSS-wide
512
+ // keywords and a stray `!important` included, so it is read first
513
+ const logical = LOGICAL[name];
514
+ if (logical) {
515
+ applyLogical(style, parent, logical, value.replace(/\s*!\s*important$/i, ''), ctx);
516
+ return;
517
+ }
195
518
  // CSS-wide keywords, before anything else parses the value.
196
519
  const lower = value.toLowerCase();
197
520
  if (lower === 'inherit') {
@@ -199,11 +522,18 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
199
522
  return;
200
523
  }
201
524
  if (lower === 'initial' || lower === 'unset' || lower === 'revert') {
202
- // `unset` is `inherit` for an inherited property and `initial` otherwise;
203
- // `revert` would be the UA sheet's value, and treating it as initial is
204
- // the closest this gets without keeping a third cascade level.
525
+ // `unset` is `inherit` for an inherited property and `initial` otherwise.
526
+ // `initial` is the value the property starts at, which an inherited one
527
+ // does not keep: left as it was, `line-height: initial` in a box inside
528
+ // a 200px line height was that line height still, and one after an
529
+ // earlier declaration of the property was that declaration. `revert`
530
+ // would be the UA sheet's value, and leaving the one the cascade has
531
+ // reached is the closest this gets without a third cascade level.
205
532
  if (lower === 'unset' && isInherited(name))
206
533
  inheritOne(style, parent, name);
534
+ else if (lower !== 'revert' && ctx.initial) {
535
+ inheritOne(style, ctx.initial, name);
536
+ }
207
537
  return;
208
538
  }
209
539
  // `!important` is stripped by the parser; a stray one here is an author
@@ -216,10 +546,12 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
216
546
  const parsed = parseColor(value);
217
547
  if (parsed === null)
218
548
  return;
219
- // `color: currentColor` is the one place the token refers to itself; it
220
- // means "keep the inherited colour", which the style already holds.
221
- if (name === 'color' && parsed === 'currentColor')
549
+ // `currentColor` in `color` is the one place the token refers to itself:
550
+ // it means the inherited colour, and so does one in a `color-mix()`
551
+ if (name === 'color') {
552
+ style.color = inkColor(parsed, parent.color);
222
553
  return;
554
+ }
223
555
  style[color] = parsed;
224
556
  return;
225
557
  }
@@ -229,12 +561,89 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
229
561
  const v = value.toLowerCase();
230
562
  if (v === 'inline-block' || v === 'inline-flex')
231
563
  style.display = v;
564
+ // a grid container is a flex container to the box tree — its
565
+ // children are blockified items, and it holds its floats — and its
566
+ // own layout (`css-grid.ts`) to the engine
232
567
  else if (v === 'grid')
233
- style.display = 'block'; // graceful, per the PRD
568
+ style.display = 'flex';
234
569
  else if (v === 'inline-grid')
235
- style.display = 'inline-block';
570
+ style.display = 'inline-flex';
571
+ else if (v === 'flow-root')
572
+ style.display = 'block';
573
+ // the legacy flexible box, a flex box until `settleClamp` has read
574
+ // its orient, and the prefixed spelling of the modern one
575
+ else if (v === '-webkit-box' || v === '-webkit-flex') {
576
+ style.display = 'flex';
577
+ }
578
+ else if (v === '-webkit-inline-box' || v === '-webkit-inline-flex') {
579
+ style.display = 'inline-flex';
580
+ }
236
581
  else if (DISPLAYS.has(v))
237
582
  style.display = v;
583
+ else
584
+ return;
585
+ style.flowRoot = v === 'flow-root';
586
+ style.grid = v === 'grid' || v === 'inline-grid';
587
+ style.webkitBox =
588
+ v === '-webkit-box'
589
+ ? 'block'
590
+ : v === '-webkit-inline-box'
591
+ ? 'inline'
592
+ : null;
593
+ return;
594
+ }
595
+ case '-webkit-box-orient': {
596
+ const v = value.toLowerCase();
597
+ if (v === 'horizontal' || v === 'inline-axis') {
598
+ style.webkitBoxOrient = 'horizontal';
599
+ }
600
+ else if (v === 'vertical' || v === 'block-axis') {
601
+ style.webkitBoxOrient = 'vertical';
602
+ }
603
+ return;
604
+ }
605
+ case '-webkit-box-direction': {
606
+ const v = value.toLowerCase();
607
+ if (v === 'normal' || v === 'reverse')
608
+ style.webkitBoxDirection = v;
609
+ return;
610
+ }
611
+ case '-webkit-box-pack': {
612
+ const v = BOX_PACK.get(value.trim().toLowerCase());
613
+ if (v)
614
+ style.webkitBoxPack = v;
615
+ return;
616
+ }
617
+ case '-webkit-box-align': {
618
+ const v = BOX_ALIGN.get(value.trim().toLowerCase());
619
+ if (v)
620
+ style.webkitBoxAlign = v;
621
+ return;
622
+ }
623
+ case 'column-count':
624
+ case 'column-width':
625
+ case 'columns': {
626
+ // `auto`, or a count, a width or both of them
627
+ const bit = name === 'column-count' ? 1 : name === 'column-width' ? 2 : 3;
628
+ let set = 0;
629
+ for (const part of splitValue(value)) {
630
+ const v = part.toLowerCase();
631
+ if (v === 'auto')
632
+ continue;
633
+ if (/^\d+$/.test(v) && Number(v) >= 1)
634
+ set |= 1;
635
+ else if (parseLength(part, ctx) !== null)
636
+ set |= 2;
637
+ else
638
+ return;
639
+ }
640
+ style.columns = (style.columns & ~bit) | (set & bit);
641
+ return;
642
+ }
643
+ case '-webkit-box-flex': {
644
+ const n = parseNumber(value);
645
+ if (n !== null && n >= 0)
646
+ style.webkitBoxFlex = n;
238
647
  return;
239
648
  }
240
649
  case 'position': {
@@ -264,6 +673,100 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
264
673
  const v = value.toLowerCase();
265
674
  if (v === 'left' || v === 'right' || v === 'both' || v === 'none')
266
675
  style.clear = v;
676
+ else if (v === 'inline-start')
677
+ style.clear = style.direction === 'rtl' ? 'right' : 'left';
678
+ else if (v === 'inline-end')
679
+ style.clear = style.direction === 'rtl' ? 'left' : 'right';
680
+ return;
681
+ }
682
+ case 'line-clamp': {
683
+ // `none`, or a count of lines, an ellipsis or both, and
684
+ // `-webkit-legacy` (CSS Overflow 4, 5.1): the ellipsis a string
685
+ // names is drawn as the one `auto` draws
686
+ const parts = splitValue(value);
687
+ if (parts.length === 1 && parts[0].toLowerCase() === 'none') {
688
+ style.maxLines = null;
689
+ style.clampContinue = 'auto';
690
+ style.blockEllipsis = false;
691
+ return;
692
+ }
693
+ let lines = null;
694
+ let ellipsis = null;
695
+ let legacy = false;
696
+ for (const part of parts) {
697
+ const v = part.toLowerCase();
698
+ if (/^\d+$/.test(v) && lines === null)
699
+ lines = Number(v);
700
+ else if (v === '-webkit-legacy' && !legacy)
701
+ legacy = true;
702
+ else if (ellipsis === null && (v === 'auto' || v === 'no-ellipsis')) {
703
+ ellipsis = v === 'auto';
704
+ }
705
+ else if (ellipsis === null && /^(["']).*\1$/s.test(part)) {
706
+ ellipsis = part.length > 2;
707
+ }
708
+ else
709
+ return;
710
+ }
711
+ if (lines === 0 || (lines === null && ellipsis === null))
712
+ return;
713
+ style.maxLines = lines;
714
+ style.clampContinue = legacy ? 'legacy' : 'collapse';
715
+ style.blockEllipsis = ellipsis ?? true;
716
+ return;
717
+ }
718
+ case '-webkit-line-clamp': {
719
+ // `line-clamp` as it was, which clamps only a vertical `-webkit-box`
720
+ // (5.1.1) and always with an ellipsis
721
+ const v = value.trim().toLowerCase();
722
+ if (v === 'none') {
723
+ style.maxLines = null;
724
+ style.clampContinue = 'auto';
725
+ style.blockEllipsis = true;
726
+ }
727
+ else if (/^\d+$/.test(v) && Number(v) >= 1) {
728
+ style.maxLines = Number(v);
729
+ style.clampContinue = 'legacy';
730
+ style.blockEllipsis = true;
731
+ }
732
+ return;
733
+ }
734
+ case 'max-lines': {
735
+ const v = value.trim().toLowerCase();
736
+ if (v === 'none')
737
+ style.maxLines = null;
738
+ else if (/^\d+$/.test(v) && Number(v) >= 1)
739
+ style.maxLines = Number(v);
740
+ return;
741
+ }
742
+ case 'continue': {
743
+ const v = value.trim().toLowerCase();
744
+ if (v === 'auto')
745
+ style.clampContinue = 'auto';
746
+ else if (v === 'collapse' || v === 'discard') {
747
+ style.clampContinue = 'collapse';
748
+ }
749
+ else if (v === '-webkit-legacy')
750
+ style.clampContinue = 'legacy';
751
+ return;
752
+ }
753
+ case 'block-ellipsis': {
754
+ const v = value.trim();
755
+ const lower = v.toLowerCase();
756
+ if (lower === 'auto' || lower === 'no-ellipsis') {
757
+ style.blockEllipsis = lower === 'auto';
758
+ }
759
+ else if (/^(["']).*\1$/s.test(v))
760
+ style.blockEllipsis = v.length > 2;
761
+ return;
762
+ }
763
+ case 'text-overflow': {
764
+ // one value for the end a line is cut at, or two, start and end
765
+ const parts = splitValue(value).map((p) => p.toLowerCase());
766
+ const end = parts[parts.length - 1];
767
+ if (parts.length <= 2 && (end === 'clip' || end === 'ellipsis')) {
768
+ style.textOverflow = end;
769
+ }
267
770
  return;
268
771
  }
269
772
  case 'box-sizing': {
@@ -272,6 +775,111 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
272
775
  style.boxSizing = v;
273
776
  return;
274
777
  }
778
+ case 'aspect-ratio': {
779
+ const parsed = parseAspectRatio(value);
780
+ if (parsed !== undefined)
781
+ style.aspectRatio = parsed;
782
+ return;
783
+ }
784
+ case 'object-fit': {
785
+ const v = value.trim().toLowerCase();
786
+ if (OBJECT_FITS.has(v))
787
+ style.objectFit = v;
788
+ return;
789
+ }
790
+ case 'appearance':
791
+ case '-webkit-appearance':
792
+ case '-moz-appearance': {
793
+ // every keyword but `none` is some control's own look, which is the
794
+ // only other thing this engine has
795
+ const v = value.trim().toLowerCase();
796
+ if (/^[a-z-]+$/.test(v))
797
+ style.appearance = v === 'none' ? 'none' : 'auto';
798
+ return;
799
+ }
800
+ case 'contain': {
801
+ const bits = containOf(value);
802
+ if (bits !== null)
803
+ style.contain = bits;
804
+ return;
805
+ }
806
+ case 'overflow-clip-margin': {
807
+ let box = null;
808
+ let margin = null;
809
+ // a length that shrinks the box where it is negative (CSS Overflow 4)
810
+ for (const part of splitValue(value)) {
811
+ const word = part.toLowerCase();
812
+ if (box === null && isVisualBox(word)) {
813
+ box = word;
814
+ continue;
815
+ }
816
+ const len = parseLength(part, ctx);
817
+ if (margin !== null || typeof len !== 'number')
818
+ return;
819
+ margin = len;
820
+ }
821
+ if (box === null && margin === null)
822
+ return;
823
+ style.overflowClipBox = box ?? 'padding-box';
824
+ style.overflowClipMargin = margin ?? 0;
825
+ return;
826
+ }
827
+ case 'content-visibility': {
828
+ const v = value.trim().toLowerCase();
829
+ if (v === 'visible' || v === 'auto' || v === 'hidden') {
830
+ style.contentVisibility = v;
831
+ }
832
+ return;
833
+ }
834
+ case 'contain-intrinsic-size':
835
+ case 'contain-intrinsic-width':
836
+ case 'contain-intrinsic-height':
837
+ case 'contain-intrinsic-inline-size':
838
+ case 'contain-intrinsic-block-size': {
839
+ const parts = splitValue(value);
840
+ const sizes = [];
841
+ for (let i = 0; i < parts.length; i += 1) {
842
+ const word = parts[i].toLowerCase();
843
+ // `auto` beside a length remembers a size last laid out, which a
844
+ // document that does not change has not got: the length
845
+ if (word === 'auto' && i + 1 < parts.length)
846
+ continue;
847
+ if (word === 'none') {
848
+ sizes.push(null);
849
+ continue;
850
+ }
851
+ const len = parseLength(parts[i], ctx);
852
+ if (typeof len !== 'number' || len < 0)
853
+ return;
854
+ sizes.push(len);
855
+ }
856
+ if (!sizes.length || sizes.length > 2)
857
+ return;
858
+ const across = name === 'contain-intrinsic-size' ||
859
+ name === 'contain-intrinsic-width' ||
860
+ name === 'contain-intrinsic-inline-size';
861
+ const down = name !== 'contain-intrinsic-width' &&
862
+ name !== 'contain-intrinsic-inline-size';
863
+ if (name === 'contain-intrinsic-size') {
864
+ style.containIntrinsicWidth = sizes[0];
865
+ style.containIntrinsicHeight = sizes.length > 1 ? sizes[1] : sizes[0];
866
+ }
867
+ else if (sizes.length === 1) {
868
+ if (across)
869
+ style.containIntrinsicWidth = sizes[0];
870
+ else if (down)
871
+ style.containIntrinsicHeight = sizes[0];
872
+ }
873
+ return;
874
+ }
875
+ case 'object-position': {
876
+ const pair = positionPair(splitValue(value), ctx);
877
+ if (!pair)
878
+ return;
879
+ style.objectPositionX = pair[0];
880
+ style.objectPositionY = pair[1];
881
+ return;
882
+ }
275
883
  case 'overflow':
276
884
  case 'overflow-x':
277
885
  case 'overflow-y': {
@@ -284,27 +892,60 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
284
892
  style.overflowY = y;
285
893
  return;
286
894
  }
895
+ case 'clip': {
896
+ const clip = parseClip(value, ctx);
897
+ if (clip !== undefined)
898
+ style.clip = clip;
899
+ return;
900
+ }
287
901
  case 'opacity': {
288
902
  const a = parseAlpha(value);
289
903
  if (a !== null)
290
904
  style.opacity = a;
291
905
  return;
292
906
  }
907
+ case 'translate': {
908
+ // `none`, or across, down, and a depth nothing here has
909
+ if (value.trim().toLowerCase() === 'none') {
910
+ style.translate = null;
911
+ return;
912
+ }
913
+ const parts = splitValue(value);
914
+ if (parts.length > 3)
915
+ return;
916
+ const [x, y, z] = parts.map((p) => parseLength(p, ctx));
917
+ if (x == null || x === AUTO || y === null || y === AUTO)
918
+ return;
919
+ if (z !== undefined && typeof z !== 'number')
920
+ return;
921
+ style.translate = [x, y ?? 0];
922
+ return;
923
+ }
924
+ case 'transform': {
925
+ const moved = transformTranslation(value, ctx);
926
+ if (moved !== undefined)
927
+ style.transformTranslate = moved;
928
+ return;
929
+ }
293
930
  case 'visibility': {
294
931
  const v = value.toLowerCase();
295
- if (v === 'hidden' || v === 'collapse')
296
- style.visibility = 'hidden';
297
- else if (v === 'visible')
298
- style.visibility = 'visible';
932
+ if (v === 'hidden' || v === 'collapse' || v === 'visible') {
933
+ style.visibility = v;
934
+ }
299
935
  return;
300
936
  }
301
937
  case 'z-index': {
302
- if (value.toLowerCase() === 'auto')
938
+ // an integer: `1.5` is none, and a `calc()` is rounded to one, half
939
+ // up (CSS Values 4 10.9)
940
+ const v = value.trim().toLowerCase();
941
+ if (v === 'auto')
303
942
  style.zIndex = AUTO;
304
- else {
305
- const n = parseNumber(value);
943
+ else if (/^[+-]?\d+$/.test(v))
944
+ style.zIndex = Number(v);
945
+ else if (/^[a-z-]+\(/.test(v)) {
946
+ const n = parseNumber(v);
306
947
  if (n !== null)
307
- style.zIndex = Math.trunc(n);
948
+ style.zIndex = Math.floor(n + 0.5);
308
949
  }
309
950
  return;
310
951
  }
@@ -315,12 +956,16 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
315
956
  v === 'middle' ||
316
957
  v === 'bottom' ||
317
958
  v === 'sub' ||
318
- v === 'super') {
959
+ v === 'super' ||
960
+ v === 'text-top' ||
961
+ v === 'text-bottom') {
319
962
  style.verticalAlign = v;
320
963
  }
321
964
  else {
965
+ // a percentage is of the element's own line height, which layout
966
+ // knows
322
967
  const len = parseLength(value, ctx);
323
- if (typeof len === 'number')
968
+ if (len !== null && len !== AUTO)
324
969
  style.verticalAlign = len;
325
970
  }
326
971
  return;
@@ -330,20 +975,55 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
330
975
  case 'height':
331
976
  case 'min-width':
332
977
  case 'min-height': {
978
+ const keyword = contentSizeOf(value, ctx, name.endsWith('width'));
979
+ if (keyword) {
980
+ // `auto` beside it, which is a height's content height already
981
+ style[camel(name)] = AUTO;
982
+ if (name === 'width')
983
+ style.widthKeyword = keyword;
984
+ else if (name === 'min-width')
985
+ style.minWidthKeyword = keyword;
986
+ else if (name === 'min-height')
987
+ style.minHeightKeyword = keyword;
988
+ else
989
+ style.heightKeyword = keyword;
990
+ return;
991
+ }
992
+ // a negative size is no value, and the declaration goes (CSS 2.1
993
+ // 10.2, 10.4, 10.5, 10.7)
333
994
  const len = parseLength(value, ctx);
334
- if (len !== null)
995
+ if (len !== null && notNegative(len)) {
335
996
  style[camel(name)] = len;
997
+ if (name === 'width')
998
+ style.widthKeyword = null;
999
+ else if (name === 'min-width')
1000
+ style.minWidthKeyword = null;
1001
+ else if (name === 'min-height')
1002
+ style.minHeightKeyword = null;
1003
+ else
1004
+ style.heightKeyword = null;
1005
+ }
336
1006
  return;
337
1007
  }
338
1008
  case 'max-width':
339
1009
  case 'max-height': {
340
- if (value.toLowerCase() === 'none') {
1010
+ const keyword = contentSizeOf(value, ctx, name === 'max-width');
1011
+ if (keyword || value.toLowerCase() === 'none') {
341
1012
  style[camel(name)] = 'none';
1013
+ if (name === 'max-width')
1014
+ style.maxWidthKeyword = keyword;
1015
+ else
1016
+ style.maxHeightKeyword = keyword;
342
1017
  return;
343
1018
  }
344
1019
  const len = parseLength(value, ctx);
345
- if (len !== null)
1020
+ if (len !== null && notNegative(len)) {
346
1021
  style[camel(name)] = len;
1022
+ if (name === 'max-width')
1023
+ style.maxWidthKeyword = null;
1024
+ else
1025
+ style.maxHeightKeyword = null;
1026
+ }
347
1027
  return;
348
1028
  }
349
1029
  case 'top':
@@ -355,18 +1035,20 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
355
1035
  style[name] = len;
356
1036
  return;
357
1037
  }
1038
+ case 'inset':
358
1039
  case 'margin':
359
1040
  case 'padding': {
360
1041
  const keys = SIDE_PROPS[name];
361
1042
  const parts = splitValue(value).map((p) => parseLength(p, ctx));
362
1043
  if (parts.some((p) => p === null))
363
1044
  return;
1045
+ // padding is never `auto` and never negative: a value that is either
1046
+ // is no value, and the declaration goes (CSS 2.1 8.4)
1047
+ if (name === 'padding' && !parts.every(validPadding))
1048
+ return;
364
1049
  const sides = fourSides(parts);
365
- // `padding: auto` is not a thing; a stray one computes to zero rather
366
- // than making layout branch on an impossible value.
367
1050
  for (let i = 0; i < 4; i += 1) {
368
- const v = name === 'padding' && sides[i] === AUTO ? 0 : sides[i];
369
- style[keys[i]] = v;
1051
+ style[keys[i]] = sides[i];
370
1052
  }
371
1053
  return;
372
1054
  }
@@ -381,9 +1063,9 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
381
1063
  const len = parseLength(value, ctx);
382
1064
  if (len === null)
383
1065
  return;
384
- const isPadding = name.startsWith('padding');
385
- style[camel(name)] =
386
- isPadding && len === AUTO ? 0 : len;
1066
+ if (name.startsWith('padding') && !validPadding(len))
1067
+ return;
1068
+ style[camel(name)] = len;
387
1069
  return;
388
1070
  }
389
1071
  // --- borders ------------------------------------------------------------
@@ -446,21 +1128,104 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
446
1128
  style[camel(name)] = v;
447
1129
  return;
448
1130
  }
1131
+ case 'border-top-left-radius':
1132
+ case 'border-top-right-radius':
1133
+ case 'border-bottom-right-radius':
1134
+ case 'border-bottom-left-radius': {
1135
+ // a radius, or a horizontal one and a vertical one
1136
+ const parts = splitValue(value).map((p) => radiusOf(p, ctx));
1137
+ if (!parts.length || parts.length > 2 || parts.includes(null))
1138
+ return;
1139
+ const [x, y = x] = parts;
1140
+ const corner = CORNERS.indexOf(name);
1141
+ const radii = style.borderRadius.slice();
1142
+ const vertical = (style.borderRadiusY ?? style.borderRadius).slice();
1143
+ radii[corner] = x;
1144
+ vertical[corner] = y;
1145
+ style.borderRadius = radii;
1146
+ style.borderRadiusY = sameRadii(radii, vertical) ? null : vertical;
1147
+ return;
1148
+ }
1149
+ case 'outline': {
1150
+ // width, style and colour in any order, each reset where it is left
1151
+ // out, as `border`'s are
1152
+ let width = BORDER_WIDTH_KEYWORDS.medium * ctx.scale;
1153
+ let outline = 'none';
1154
+ let color = 'currentColor';
1155
+ for (const part of splitValue(value)) {
1156
+ const v = part.toLowerCase();
1157
+ const kind = outlineStyleOf(v);
1158
+ if (kind) {
1159
+ outline = kind;
1160
+ continue;
1161
+ }
1162
+ const w = borderWidth(part, ctx);
1163
+ if (w !== null) {
1164
+ width = w;
1165
+ continue;
1166
+ }
1167
+ const c = v === 'invert' ? 'currentColor' : parseColor(part);
1168
+ if (c === null)
1169
+ return;
1170
+ color = c;
1171
+ }
1172
+ style.outlineStyle = outline;
1173
+ style.outlineWidth = width;
1174
+ style.outlineColor = color;
1175
+ return;
1176
+ }
1177
+ case 'outline-style': {
1178
+ const kind = outlineStyleOf(value.trim().toLowerCase());
1179
+ if (kind)
1180
+ style.outlineStyle = kind;
1181
+ return;
1182
+ }
1183
+ case 'outline-width': {
1184
+ const w = borderWidth(value, ctx);
1185
+ if (w !== null)
1186
+ style.outlineWidth = w;
1187
+ return;
1188
+ }
1189
+ case 'outline-color': {
1190
+ // `invert` is the text's colour where a browser cannot invert
1191
+ const v = value.trim().toLowerCase();
1192
+ const c = v === 'invert' ? 'currentColor' : parseColor(value);
1193
+ if (c !== null)
1194
+ style.outlineColor = c;
1195
+ return;
1196
+ }
1197
+ case 'outline-offset': {
1198
+ const len = parseLength(value, ctx);
1199
+ if (typeof len === 'number')
1200
+ style.outlineOffset = len;
1201
+ return;
1202
+ }
1203
+ case 'box-shadow': {
1204
+ const shadows = parseBoxShadow(value, ctx);
1205
+ if (shadows !== undefined)
1206
+ style.boxShadow = shadows;
1207
+ return;
1208
+ }
1209
+ case 'text-shadow': {
1210
+ const shadows = parseBoxShadow(value, ctx, true);
1211
+ if (shadows !== undefined)
1212
+ style.textShadow = shadows;
1213
+ return;
1214
+ }
449
1215
  case 'border-radius': {
450
- // The `/` form gives elliptical corners, which this rounds to the
451
- // horizontal radius rather than dropping the declaration.
452
- const horizontal = value.split('/')[0];
453
- const parts = splitValue(horizontal).map((p) => {
454
- const len = parseLength(p, ctx);
455
- return typeof len === 'number'
456
- ? len
457
- : len && typeof len === 'object'
458
- ? 0
459
- : null;
460
- });
461
- if (parts.some((p) => p === null))
1216
+ // one to four radii, and after a `/` one to four more for the
1217
+ // vertical radii where they differ: elliptical corners
1218
+ const halves = value.split('/');
1219
+ if (halves.length > 2)
462
1220
  return;
463
- style.borderRadius = fourSides(parts);
1221
+ const [horizontal, vertical] = halves.map((half) => splitValue(half).map((p) => radiusOf(p, ctx)));
1222
+ for (const parts of vertical ? [horizontal, vertical] : [horizontal]) {
1223
+ if (!parts.length || parts.length > 4 || parts.includes(null))
1224
+ return;
1225
+ }
1226
+ style.borderRadius = fourSides(horizontal);
1227
+ const y = vertical ? fourSides(vertical) : null;
1228
+ style.borderRadiusY = y && !sameRadii(style.borderRadius, y) ? y : null;
464
1229
  return;
465
1230
  }
466
1231
  // --- background ---------------------------------------------------------
@@ -469,33 +1234,152 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
469
1234
  return;
470
1235
  }
471
1236
  case 'background-image': {
472
- style.backgroundImage = parseUrl(splitCommas(value)[0]);
1237
+ // one a layer; a bad url makes the declaration invalid, and it is
1238
+ // dropped
1239
+ const images = [];
1240
+ for (const part of splitCommas(value)) {
1241
+ const image = backgroundImageOf(part, ctx);
1242
+ if (image === undefined)
1243
+ return;
1244
+ images.push(image);
1245
+ }
1246
+ if (!images.length)
1247
+ return;
1248
+ const [first] = images;
1249
+ style.backgroundImage = typeof first === 'string' ? first : null;
1250
+ style.backgroundGradient = typeof first === 'string' ? null : first;
1251
+ style.backgroundImages = images.length > 1 ? images : null;
1252
+ return;
1253
+ }
1254
+ case 'background-clip':
1255
+ case '-webkit-background-clip': {
1256
+ // `text` the first layer's alone, which paints through the text
1257
+ const clips = layerValues(value, (part) => {
1258
+ const v = part.toLowerCase();
1259
+ return v === 'text' || isVisualBox(v) ? v : null;
1260
+ });
1261
+ if (!clips)
1262
+ return;
1263
+ style.backgroundClipText = clips[0] === 'text';
1264
+ const boxes = clips.map((c) => (c === 'text' ? 'border-box' : c));
1265
+ style.backgroundClip = boxes[0];
1266
+ style.backgroundClips = boxes.length > 1 ? boxes : null;
1267
+ return;
1268
+ }
1269
+ case 'border-image': {
1270
+ const image = readBorderImage(value, ctx);
1271
+ if (image)
1272
+ style.borderImage = image;
1273
+ return;
1274
+ }
1275
+ case 'border-image-source': {
1276
+ const source = backgroundImageOf(value, ctx);
1277
+ if (source === undefined)
1278
+ return;
1279
+ style.borderImage = { ...style.borderImage, source };
1280
+ return;
1281
+ }
1282
+ case 'border-image-slice': {
1283
+ const slice = borderImageSlice(splitValue(value));
1284
+ if (slice)
1285
+ style.borderImage = { ...style.borderImage, ...slice };
1286
+ return;
1287
+ }
1288
+ case 'border-image-width': {
1289
+ const width = sidesOf(splitValue(value), (p) => imageWidthOf(p, ctx));
1290
+ if (width)
1291
+ style.borderImage = { ...style.borderImage, width };
1292
+ return;
1293
+ }
1294
+ case 'border-image-outset': {
1295
+ const outset = sidesOf(splitValue(value), (p) => imageOutsetOf(p, ctx));
1296
+ if (outset)
1297
+ style.borderImage = { ...style.borderImage, outset };
1298
+ return;
1299
+ }
1300
+ case 'border-image-repeat': {
1301
+ const repeat = imageRepeatOf(splitValue(value.toLowerCase()));
1302
+ if (repeat)
1303
+ style.borderImage = { ...style.borderImage, repeat };
1304
+ return;
1305
+ }
1306
+ case 'background-origin': {
1307
+ const origins = layerValues(value, (part) => {
1308
+ const v = part.toLowerCase();
1309
+ return isVisualBox(v) ? v : null;
1310
+ });
1311
+ if (!origins)
1312
+ return;
1313
+ style.backgroundOrigin = origins[0];
1314
+ style.backgroundOrigins = origins.length > 1 ? origins : null;
473
1315
  return;
474
1316
  }
475
1317
  case 'background-repeat': {
476
- const v = value.toLowerCase().trim();
477
- if (v === 'repeat' ||
478
- v === 'repeat-x' ||
479
- v === 'repeat-y' ||
480
- v === 'no-repeat') {
481
- style.backgroundRepeat = v;
482
- }
1318
+ const repeats = layerValues(value, (part) => {
1319
+ const words = splitValue(part.toLowerCase());
1320
+ return words.length <= 2 ? readRepeat(words) : null;
1321
+ });
1322
+ if (!repeats)
1323
+ return;
1324
+ style.backgroundRepeat = repeats[0];
1325
+ style.backgroundRepeats = repeats.length > 1 ? repeats : null;
1326
+ return;
1327
+ }
1328
+ case 'background-attachment': {
1329
+ const attachments = layerValues(value, (part) => {
1330
+ const v = part.toLowerCase();
1331
+ return v === 'scroll' || v === 'fixed' || v === 'local' ? v : null;
1332
+ });
1333
+ if (!attachments)
1334
+ return;
1335
+ style.backgroundAttachment = attachments[0];
1336
+ style.backgroundAttachments = attachments.length > 1 ? attachments : null;
483
1337
  return;
484
1338
  }
485
1339
  case 'background-size': {
486
- const v = value.toLowerCase().trim();
487
- if (v === 'cover' || v === 'contain' || v === 'auto')
488
- style.backgroundSize = v;
1340
+ const sizes = layerValues(value, (part) => backgroundSizeOf(splitValue(part), ctx));
1341
+ if (!sizes)
1342
+ return;
1343
+ style.backgroundSize = sizes[0];
1344
+ style.backgroundSizes = sizes.length > 1 ? sizes : null;
489
1345
  return;
490
1346
  }
491
1347
  case 'background-position': {
492
- const parts = splitValue(value);
493
- const x = backgroundPosition(parts[0], ctx);
494
- const y = backgroundPosition(parts[1] ?? 'center', ctx);
495
- if (x !== null)
496
- style.backgroundPositionX = x;
497
- if (y !== null)
498
- style.backgroundPositionY = y;
1348
+ const pairs = layerValues(value, (part) => positionPair(splitValue(part), ctx));
1349
+ if (!pairs)
1350
+ return;
1351
+ [style.backgroundPositionX, style.backgroundPositionY] = pairs[0];
1352
+ style.backgroundPositions = pairs.length > 1 ? pairs : null;
1353
+ return;
1354
+ }
1355
+ // --- generated content --------------------------------------------------
1356
+ case 'content': {
1357
+ const parsed = parseContent(value);
1358
+ if (parsed !== null)
1359
+ style.content = parsed;
1360
+ return;
1361
+ }
1362
+ case 'counter-reset':
1363
+ case 'counter-increment':
1364
+ case 'counter-set': {
1365
+ const reset = name === 'counter-reset';
1366
+ const increment = name === 'counter-increment';
1367
+ const parsed = parseCounterList(value, increment ? 1 : 0, reset);
1368
+ if (parsed === null)
1369
+ return;
1370
+ const list = parsed === 'none' ? null : parsed;
1371
+ if (reset)
1372
+ style.counterReset = list;
1373
+ else if (increment)
1374
+ style.counterIncrement = list;
1375
+ else
1376
+ style.counterSet = list;
1377
+ return;
1378
+ }
1379
+ case 'quotes': {
1380
+ const parsed = parseQuotes(value);
1381
+ if (parsed !== null)
1382
+ style.quotes = parsed;
499
1383
  return;
500
1384
  }
501
1385
  // --- text ---------------------------------------------------------------
@@ -505,26 +1389,48 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
505
1389
  }
506
1390
  case 'font-family': {
507
1391
  // ntk's font matcher takes the CSS list as written and walks it, so the
508
- // value passes through whole rather than being resolved here.
509
- style.fontFamily = splitCommas(value)
510
- .map((f) => f.replace(/^['"]|['"]$/g, ''))
1392
+ // value passes through whole rather than being resolved here — once
1393
+ // every name in it is one: a string, or identifiers (CSS 2.1 15.3).
1394
+ // `test!foo, Ahem` is no list, and set Ahem.
1395
+ const names = splitCommas(value);
1396
+ if (!names.every(isFamilyName))
1397
+ return;
1398
+ // and an unquoted name is its identifiers joined by one space each,
1399
+ // however they were spaced: `Courier New` over two lines is
1400
+ // `Courier New`, and was a name no font has
1401
+ const list = names
1402
+ .map((f) => /^['"]/.test(f)
1403
+ ? f.replace(/^['"]|['"]$/g, '')
1404
+ : f.trim().replace(/[ \t\n\r\f]+/g, ' '))
511
1405
  .filter(Boolean)
512
1406
  .join(', ');
1407
+ style.fontFamily = ctx.families ? ctx.families(list) : list;
513
1408
  return;
514
1409
  }
515
1410
  case 'font-size': {
1411
+ // No larger than 10,000 pixels, as Chrome keeps it: a text engine
1412
+ // handed a face at a size of millions shapes and caches glyphs that
1413
+ // size, and a nest of `larger`s gets there on its own
1414
+ const most = MAX_FONT_SIZE * ctx.scale;
516
1415
  const kw = keywordFontSize(value, parent.fontSize, ctx.rem);
517
1416
  if (kw !== null) {
518
- style.fontSize = kw;
1417
+ style.fontSize = Math.min(kw, most);
519
1418
  return;
520
1419
  }
521
1420
  // `em` in a `font-size` is relative to the *parent's* size, not this
522
1421
  // element's — the one place the unit context has to be overridden.
1422
+ // 0 is a size — the text takes no room, which is what a container of
1423
+ // inline-blocks sets to lose the spaces between them — and a negative
1424
+ // one is no size at all, so the declaration goes
523
1425
  const len = parseLength(value, { ...ctx, em: parent.fontSize });
524
- if (typeof len === 'number')
525
- style.fontSize = Math.max(1, len);
1426
+ if (typeof len === 'number') {
1427
+ if (len >= 0)
1428
+ style.fontSize = Math.min(len, most);
1429
+ }
526
1430
  else if (len && typeof len === 'object') {
527
- style.fontSize = Math.max(1, (len.pct / 100) * parent.fontSize);
1431
+ const size = resolve(len, parent.fontSize);
1432
+ if (size >= 0)
1433
+ style.fontSize = Math.min(size, most);
528
1434
  }
529
1435
  return;
530
1436
  }
@@ -545,19 +1451,28 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
545
1451
  style.lineHeightIsLength = false;
546
1452
  return;
547
1453
  }
1454
+ // none of it below nought, which drops the declaration (CSS 2.1
1455
+ // 10.8.1)
548
1456
  const n = parseNumber(value);
549
1457
  if (n !== null) {
1458
+ if (n < 0)
1459
+ return;
550
1460
  style.lineHeight = n;
551
1461
  style.lineHeightIsLength = false;
552
1462
  return;
553
1463
  }
554
1464
  const len = parseLength(value, ctx);
555
1465
  if (typeof len === 'number') {
1466
+ if (len < 0)
1467
+ return;
556
1468
  style.lineHeight = len;
557
1469
  style.lineHeightIsLength = true;
558
1470
  }
559
1471
  else if (len && typeof len === 'object') {
560
- style.lineHeight = (len.pct / 100) * style.fontSize;
1472
+ const px = resolve(len, style.fontSize);
1473
+ if (px < 0)
1474
+ return;
1475
+ style.lineHeight = px;
561
1476
  style.lineHeightIsLength = true;
562
1477
  }
563
1478
  return;
@@ -571,10 +1486,58 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
571
1486
  v === 'start' ||
572
1487
  v === 'end') {
573
1488
  style.textAlign = v;
1489
+ style.alignBlocks = null;
1490
+ style.tableTextAlignSet = true;
1491
+ return;
1492
+ }
1493
+ // every line justified, the last as well (CSS Text 3, 7.1)
1494
+ if (v === 'justify-all') {
1495
+ style.textAlign = 'justify';
1496
+ style.textAlignLast = 'justify';
1497
+ style.alignBlocks = null;
1498
+ style.tableTextAlignSet = true;
1499
+ return;
1500
+ }
1501
+ // the value HTML's alignment is given as, the blocks inside aligned
1502
+ // with the text — and which mail writes for itself
1503
+ const aligned = /^-(?:webkit|moz|khtml)-(left|right|center)$/.exec(v);
1504
+ if (aligned) {
1505
+ const side = aligned[1];
1506
+ style.textAlign = side;
1507
+ style.alignBlocks = side;
574
1508
  style.tableTextAlignSet = true;
575
1509
  }
576
1510
  return;
577
1511
  }
1512
+ case 'text-align-last': {
1513
+ const v = value.toLowerCase();
1514
+ if (v === 'auto' ||
1515
+ v === 'left' ||
1516
+ v === 'right' ||
1517
+ v === 'center' ||
1518
+ v === 'justify' ||
1519
+ v === 'start' ||
1520
+ v === 'end') {
1521
+ style.textAlignLast = v;
1522
+ }
1523
+ else if (v === 'match-parent') {
1524
+ style.textAlignLast = parent.textAlignLast;
1525
+ }
1526
+ return;
1527
+ }
1528
+ case 'text-justify': {
1529
+ const v = value.toLowerCase();
1530
+ if (v === 'auto' ||
1531
+ v === 'none' ||
1532
+ v === 'inter-word' ||
1533
+ v === 'inter-character') {
1534
+ style.textJustify = v;
1535
+ }
1536
+ else if (v === 'distribute') {
1537
+ style.textJustify = 'inter-character';
1538
+ }
1539
+ return;
1540
+ }
578
1541
  case 'text-indent': {
579
1542
  const len = parseLength(value, ctx);
580
1543
  if (len !== null && len !== AUTO)
@@ -607,17 +1570,159 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
607
1570
  else
608
1571
  style.wordSpacing = len;
609
1572
  }
1573
+ else if (name === 'letter-spacing' && len && isPct(len)) {
1574
+ // a percentage of the font size, as `em` is (CSS Text 4): `200%`
1575
+ // was dropped, where it is twice the letter's size apart
1576
+ style.letterSpacing = resolve(len, ctx.em);
1577
+ }
610
1578
  return;
611
1579
  }
612
- case 'white-space': {
613
- const v = value.toLowerCase();
614
- if (v === 'normal' ||
615
- v === 'nowrap' ||
616
- v === 'pre' ||
617
- v === 'pre-wrap' ||
1580
+ case 'font-variant-numeric':
1581
+ case 'font-variant-caps':
1582
+ case 'font-variant-ligatures':
1583
+ case 'font-variant-position': {
1584
+ const kind = name.slice('font-variant-'.length);
1585
+ const variant = parseFontVariant(value, kind);
1586
+ if (variant)
1587
+ Object.assign(style, variant);
1588
+ return;
1589
+ }
1590
+ case 'font-variant': {
1591
+ const variant = parseFontVariant(value, null);
1592
+ if (variant)
1593
+ Object.assign(style, variant);
1594
+ return;
1595
+ }
1596
+ case 'font-kerning': {
1597
+ const v = value.trim().toLowerCase();
1598
+ if (v === 'auto')
1599
+ style.fontKerning = '';
1600
+ else if (v === 'normal')
1601
+ style.fontKerning = 'kern=1';
1602
+ else if (v === 'none')
1603
+ style.fontKerning = 'kern=0';
1604
+ return;
1605
+ }
1606
+ case 'font-feature-settings': {
1607
+ const settings = parseFeatureSettings(value);
1608
+ if (settings !== null)
1609
+ style.fontFeatureSettings = settings;
1610
+ return;
1611
+ }
1612
+ case 'tab-size':
1613
+ case '-moz-tab-size': {
1614
+ // a number of spaces, or a length; neither below nought
1615
+ const v = value.trim();
1616
+ if (/^[+]?(\d+\.?\d*|\.\d+)$/.test(v)) {
1617
+ style.tabSize = Number(v);
1618
+ style.tabSizeIsLength = false;
1619
+ return;
1620
+ }
1621
+ const len = parseLength(v, ctx);
1622
+ if (typeof len === 'number' && len >= 0) {
1623
+ style.tabSize = len;
1624
+ style.tabSizeIsLength = true;
1625
+ }
1626
+ return;
1627
+ }
1628
+ case 'white-space': {
1629
+ const v = value.trim().toLowerCase();
1630
+ if (v === 'normal' ||
1631
+ v === 'nowrap' ||
1632
+ v === 'pre' ||
1633
+ v === 'pre-wrap' ||
618
1634
  v === 'pre-line') {
619
1635
  style.whiteSpace = v;
620
1636
  }
1637
+ else if (v === 'break-spaces') {
1638
+ // `pre-wrap` whose spaces take room at a line's end rather than
1639
+ // hanging past it, which is as near as the engines come
1640
+ style.whiteSpace = 'pre-wrap';
1641
+ }
1642
+ return;
1643
+ }
1644
+ // a word is cut inside itself only where the style says it may be:
1645
+ // `anywhere` as `break-word` is
1646
+ case 'overflow-wrap':
1647
+ case 'word-wrap': {
1648
+ const v = value.trim().toLowerCase();
1649
+ if (v === 'normal')
1650
+ style.overflowWrap = 'normal';
1651
+ else if (v === 'break-word' || v === 'anywhere') {
1652
+ style.overflowWrap = 'break-word';
1653
+ }
1654
+ return;
1655
+ }
1656
+ case 'line-break': {
1657
+ const v = value.trim().toLowerCase();
1658
+ if (v === 'auto' ||
1659
+ v === 'loose' ||
1660
+ v === 'normal' ||
1661
+ v === 'strict' ||
1662
+ v === 'anywhere') {
1663
+ style.lineBreakAnywhere = v === 'anywhere';
1664
+ }
1665
+ return;
1666
+ }
1667
+ case 'word-break': {
1668
+ const v = value.trim().toLowerCase();
1669
+ if (v === 'normal' ||
1670
+ v === 'break-all' ||
1671
+ v === 'keep-all' ||
1672
+ v === 'break-word') {
1673
+ style.wordBreak = v;
1674
+ }
1675
+ return;
1676
+ }
1677
+ // CSS Text 4 splits `white-space` into what it keeps of the white space
1678
+ // and whether its lines wrap; each longhand changes its half of it
1679
+ case 'white-space-collapse': {
1680
+ const v = value.trim().toLowerCase();
1681
+ const collapse = v === 'collapse'
1682
+ ? 'collapse'
1683
+ : v === 'preserve' || v === 'break-spaces' || v === 'preserve-spaces'
1684
+ ? 'preserve'
1685
+ : v === 'preserve-breaks'
1686
+ ? 'preserve-breaks'
1687
+ : null;
1688
+ if (collapse) {
1689
+ style.whiteSpace = whiteSpaceOf(collapse, wrapsIn(style.whiteSpace));
1690
+ }
1691
+ return;
1692
+ }
1693
+ case 'text-wrap':
1694
+ case 'text-wrap-mode':
1695
+ case 'text-wrap-style': {
1696
+ // `text-wrap` is both, and resets the one it leaves out: Tailwind 4
1697
+ // writes `text-nowrap` and `text-balance` in it
1698
+ let wrap = null;
1699
+ let balance = null;
1700
+ for (const word of splitValue(value.toLowerCase())) {
1701
+ const mode = word === 'wrap' || word === 'nowrap';
1702
+ const kind = word === 'auto' ||
1703
+ word === 'balance' ||
1704
+ word === 'pretty' ||
1705
+ word === 'stable';
1706
+ if (mode && wrap === null && name !== 'text-wrap-style') {
1707
+ wrap = word === 'wrap';
1708
+ }
1709
+ else if (kind && balance === null && name !== 'text-wrap-mode') {
1710
+ balance = word === 'balance';
1711
+ }
1712
+ else
1713
+ return;
1714
+ }
1715
+ if (wrap === null && balance === null)
1716
+ return;
1717
+ if (name === 'text-wrap') {
1718
+ wrap ??= true;
1719
+ balance ??= false;
1720
+ }
1721
+ if (wrap !== null) {
1722
+ style.whiteSpace = whiteSpaceOf(COLLAPSE_OF[style.whiteSpace], wrap);
1723
+ }
1724
+ if (balance !== null)
1725
+ style.textWrapStyle = balance ? 'balance' : 'auto';
621
1726
  return;
622
1727
  }
623
1728
  case 'direction': {
@@ -626,28 +1731,86 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
626
1731
  style.direction = v;
627
1732
  return;
628
1733
  }
1734
+ case 'unicode-bidi': {
1735
+ const v = value.trim().toLowerCase();
1736
+ if (v === 'normal' ||
1737
+ v === 'embed' ||
1738
+ v === 'isolate' ||
1739
+ v === 'bidi-override' ||
1740
+ v === 'isolate-override' ||
1741
+ v === 'plaintext') {
1742
+ style.unicodeBidi = v;
1743
+ }
1744
+ return;
1745
+ }
629
1746
  case 'text-decoration':
630
1747
  case 'text-decoration-line': {
1748
+ // Read whole before any of it is taken: a word the property does not
1749
+ // know, one given twice, or `none` beside a line makes the declaration
1750
+ // invalid, and it is ignored (CSS 2.1 4.2) — `underline overline
1751
+ // line-through diagonal` draws nothing, where it drew a line-through.
1752
+ const lines = [];
1753
+ let none = false;
1754
+ let color = null;
1755
+ let decorationStyle = null;
1756
+ let thickness;
631
1757
  for (const part of splitValue(value)) {
632
1758
  const v = part.toLowerCase();
633
- if (v === 'underline' ||
634
- v === 'line-through' ||
635
- v === 'overline' ||
636
- v === 'none') {
637
- style.textDecorationLine = v;
1759
+ if (LINE_KEYWORDS.has(v)) {
1760
+ if (none || lines.includes(v))
1761
+ return;
1762
+ lines.push(v);
1763
+ continue;
638
1764
  }
639
- else if (name === 'text-decoration') {
640
- const c = parseColor(part);
641
- if (c)
642
- style.textDecorationColor = c;
643
- else if (DECORATION_STYLES.has(v)) {
644
- style.textDecorationStyle =
645
- v;
646
- }
1765
+ if (v === 'none') {
1766
+ if (none || lines.length)
1767
+ return;
1768
+ none = true;
1769
+ continue;
647
1770
  }
1771
+ if (name !== 'text-decoration')
1772
+ return;
1773
+ const c = parseColor(part);
1774
+ if (c) {
1775
+ if (color !== null)
1776
+ return;
1777
+ color = c;
1778
+ }
1779
+ else if (DECORATION_STYLES.has(v)) {
1780
+ if (decorationStyle !== null)
1781
+ return;
1782
+ decorationStyle = v;
1783
+ }
1784
+ else {
1785
+ const t = decorationLength(part, ctx);
1786
+ if (t === undefined || thickness !== undefined)
1787
+ return;
1788
+ thickness = t;
1789
+ }
1790
+ }
1791
+ if (!none && !lines.length && name === 'text-decoration-line')
1792
+ return;
1793
+ style.textDecorationLine = lines.length ? lines.join(' ') : 'none';
1794
+ if (name === 'text-decoration') {
1795
+ // a shorthand: what it does not name goes back to its initial value
1796
+ style.textDecorationColor = color;
1797
+ style.textDecorationStyle = decorationStyle ?? 'solid';
1798
+ style.textDecorationThickness = thickness ?? null;
648
1799
  }
649
1800
  return;
650
1801
  }
1802
+ case 'text-decoration-thickness': {
1803
+ const thickness = decorationLength(value, ctx, 'from-font');
1804
+ if (thickness !== undefined)
1805
+ style.textDecorationThickness = thickness;
1806
+ return;
1807
+ }
1808
+ case 'text-underline-offset': {
1809
+ const offset = decorationLength(value, ctx);
1810
+ if (offset !== undefined)
1811
+ style.textUnderlineOffset = offset;
1812
+ return;
1813
+ }
651
1814
  case 'text-decoration-style': {
652
1815
  const v = value.toLowerCase();
653
1816
  if (DECORATION_STYLES.has(v)) {
@@ -661,17 +1824,77 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
661
1824
  }
662
1825
  // --- lists --------------------------------------------------------------
663
1826
  case 'list-style': {
1827
+ // a type, a position and an image, each at most once and in any
1828
+ // order, and each left out reset (CSS 2.1 12.5.1); a `none` is
1829
+ // whichever of the type and the image is not otherwise given, both
1830
+ // where neither is, and a third thing where both are, which is
1831
+ // nothing and makes the declaration invalid
1832
+ let type = null;
1833
+ let position = null;
1834
+ let image;
1835
+ let nones = 0;
664
1836
  for (const part of splitValue(value)) {
665
1837
  const v = part.toLowerCase();
666
- if (v === 'inside' || v === 'outside')
667
- style.listStylePosition = v;
668
- else if (v !== 'none' || style.listStyleType === 'disc')
669
- style.listStyleType = v;
1838
+ if (v === 'inside' || v === 'outside') {
1839
+ if (position)
1840
+ return;
1841
+ position = v;
1842
+ }
1843
+ else if (v === 'none')
1844
+ nones += 1;
1845
+ else if (v.startsWith('url(') || IMAGE_FUNCTION.test(v)) {
1846
+ if (image !== undefined)
1847
+ return;
1848
+ image = v.startsWith('url(') ? (parseUrl(part) ?? null) : null;
1849
+ }
1850
+ else {
1851
+ if (type !== null)
1852
+ return;
1853
+ type = parseListStyleType(part);
1854
+ if (type === null)
1855
+ return;
1856
+ }
1857
+ }
1858
+ if (nones > 2)
1859
+ return;
1860
+ if (nones === 2 && (type !== null || image !== undefined))
1861
+ return;
1862
+ if (nones === 1) {
1863
+ if (type !== null && image !== undefined)
1864
+ return;
1865
+ if (type === null)
1866
+ type = 'none';
1867
+ else
1868
+ image = null;
1869
+ }
1870
+ if (nones === 2) {
1871
+ type = 'none';
1872
+ image = null;
1873
+ }
1874
+ style.listStyleType = type ?? 'disc';
1875
+ style.listStylePosition = position ?? 'outside';
1876
+ style.listStyleImage = image ?? null;
1877
+ return;
1878
+ }
1879
+ case 'list-style-image': {
1880
+ const v = value.trim();
1881
+ if (v.toLowerCase() === 'none')
1882
+ style.listStyleImage = null;
1883
+ else if (IMAGE_FUNCTION.test(v.toLowerCase())) {
1884
+ // a gradient is an image this draws no marker as
1885
+ style.listStyleImage = null;
1886
+ }
1887
+ else if (/^url\(/i.test(v)) {
1888
+ const url = parseUrl(v);
1889
+ if (url !== undefined)
1890
+ style.listStyleImage = url;
670
1891
  }
671
1892
  return;
672
1893
  }
673
1894
  case 'list-style-type': {
674
- style.listStyleType = value.toLowerCase();
1895
+ const type = parseListStyleType(value);
1896
+ if (type !== null)
1897
+ style.listStyleType = type;
675
1898
  return;
676
1899
  }
677
1900
  case 'list-style-position': {
@@ -705,7 +1928,7 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
705
1928
  return;
706
1929
  }
707
1930
  case 'justify-content': {
708
- const v = alignKeyword(value);
1931
+ const v = justifyKeyword(value);
709
1932
  if (v)
710
1933
  style.justifyContent = v;
711
1934
  return;
@@ -759,23 +1982,239 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
759
1982
  return;
760
1983
  }
761
1984
  case 'order': {
762
- const n = parseNumber(value);
763
- if (n !== null)
764
- style.order = Math.trunc(n);
1985
+ // an integer, and nothing else (CSS Flexbox 5.4)
1986
+ const v = value.trim();
1987
+ if (/^[+-]?\d+$/.test(v))
1988
+ style.order = Number(v);
765
1989
  return;
766
1990
  }
767
1991
  case 'gap':
768
1992
  case 'row-gap':
769
- case 'column-gap': {
770
- const parts = splitValue(value).map((p) => parseLength(p, ctx));
771
- const row = typeof parts[0] === 'number' ? parts[0] : null;
772
- const col = typeof parts[1] === 'number' ? parts[1] : row;
773
- if (row === null)
1993
+ case 'column-gap':
1994
+ // the names CSS Grid 1 first gave them, which browsers still read
1995
+ case 'grid-gap':
1996
+ case 'grid-row-gap':
1997
+ case 'grid-column-gap': {
1998
+ const which = name.replace(/^grid-/, '');
1999
+ // a length or a percentage, not below nought, or `normal`, which is
2000
+ // nought outside columns of text
2001
+ const gap = (part) => {
2002
+ if (part === undefined)
2003
+ return null;
2004
+ if (part.toLowerCase() === 'normal')
2005
+ return 0;
2006
+ const len = parseLength(part, ctx);
2007
+ if (typeof len === 'number')
2008
+ return len >= 0 ? len : null;
2009
+ return len !== null && isPct(len) && !(len.pct < 0) ? len : null;
2010
+ };
2011
+ const parts = splitValue(value);
2012
+ if (!parts.length || parts.length > (which === 'gap' ? 2 : 1))
2013
+ return;
2014
+ const row = gap(parts[0]);
2015
+ const col = parts.length > 1 ? gap(parts[1]) : row;
2016
+ if (row === null || col === null)
774
2017
  return;
775
- if (name !== 'column-gap')
2018
+ if (which !== 'column-gap')
776
2019
  style.rowGap = row;
777
- if (name !== 'row-gap')
778
- style.columnGap = col ?? row;
2020
+ if (which !== 'row-gap')
2021
+ style.columnGap = col;
2022
+ return;
2023
+ }
2024
+ case 'grid-template-columns':
2025
+ case 'grid-template-rows': {
2026
+ const template = value.toLowerCase() === 'none' ? null : parseGridTemplate(value, ctx);
2027
+ if (template === undefined)
2028
+ return;
2029
+ if (name === 'grid-template-columns')
2030
+ style.gridColumns = template;
2031
+ else
2032
+ style.gridRows = template;
2033
+ return;
2034
+ }
2035
+ case 'grid-template-areas': {
2036
+ if (value.trim().toLowerCase() === 'none') {
2037
+ style.gridAreas = null;
2038
+ return;
2039
+ }
2040
+ const parts = trackParts(value);
2041
+ if (!parts?.length || !parts.every((p) => /^["']/.test(p)))
2042
+ return;
2043
+ const areas = parseAreas(parts);
2044
+ if (areas)
2045
+ style.gridAreas = areas;
2046
+ return;
2047
+ }
2048
+ case 'grid-auto-flow': {
2049
+ const words = value.trim().toLowerCase().split(/\s+/);
2050
+ const dense = words.includes('dense');
2051
+ const axis = words.filter((w) => w !== 'dense');
2052
+ if (axis.length > 1 ||
2053
+ words.length > 2 ||
2054
+ (axis.length && axis[0] !== 'row' && axis[0] !== 'column') ||
2055
+ (!dense && !axis.length)) {
2056
+ return;
2057
+ }
2058
+ const flow = axis[0] ?? 'row';
2059
+ style.gridAutoFlow = dense
2060
+ ? flow === 'row'
2061
+ ? 'row dense'
2062
+ : 'column dense'
2063
+ : flow;
2064
+ return;
2065
+ }
2066
+ case 'grid-auto-rows':
2067
+ case 'grid-auto-columns': {
2068
+ const track = parseGridTrack(splitValue(value)[0] ?? '', ctx);
2069
+ if (!track)
2070
+ return;
2071
+ if (name === 'grid-auto-rows')
2072
+ style.gridAutoRows = track;
2073
+ else
2074
+ style.gridAutoColumns = track;
2075
+ return;
2076
+ }
2077
+ case 'grid-template': {
2078
+ // rows, a slash and columns, or `none` (CSS Grid 1, 7.4)
2079
+ if (value.trim().toLowerCase() === 'none') {
2080
+ style.gridRows = null;
2081
+ style.gridColumns = null;
2082
+ style.gridAreas = null;
2083
+ return;
2084
+ }
2085
+ const template = parseTemplateShorthand(value, ctx);
2086
+ if (!template)
2087
+ return;
2088
+ style.gridRows = template.rows;
2089
+ style.gridColumns = template.columns;
2090
+ style.gridAreas = template.areas;
2091
+ return;
2092
+ }
2093
+ case 'grid': {
2094
+ // a template, or one axis's template and the other's `auto-flow`
2095
+ // tracks (7.8); every longhand it does not name back to its initial
2096
+ const parts = splitTopLevelSlash(value);
2097
+ const flows = (part) => part !== undefined && /(^|\s)auto-flow(\s|$)/i.test(part);
2098
+ const autoTrack = (part) => {
2099
+ const words = splitValue(part).filter((w) => !/^(auto-flow|dense)$/i.test(w));
2100
+ return words.length ? parseGridTrack(words[0], ctx) : AUTO_TRACK;
2101
+ };
2102
+ const dense = (part) => /(^|\s)dense(\s|$)/i.test(part);
2103
+ const axis = (part) => part.trim().toLowerCase() === 'none'
2104
+ ? null
2105
+ : parseGridTemplate(part, ctx);
2106
+ if (parts.length === 2 && flows(parts[0])) {
2107
+ const rows = autoTrack(parts[0]);
2108
+ const columns = axis(parts[1]);
2109
+ if (!rows || columns === undefined)
2110
+ return;
2111
+ style.gridRows = null;
2112
+ style.gridColumns = columns;
2113
+ style.gridAreas = null;
2114
+ style.gridAutoRows = rows;
2115
+ style.gridAutoColumns = AUTO_TRACK;
2116
+ style.gridAutoFlow = dense(parts[0]) ? 'row dense' : 'row';
2117
+ return;
2118
+ }
2119
+ if (parts.length === 2 && flows(parts[1])) {
2120
+ const rows = axis(parts[0]);
2121
+ const columns = autoTrack(parts[1]);
2122
+ if (rows === undefined || !columns)
2123
+ return;
2124
+ style.gridRows = rows;
2125
+ style.gridColumns = null;
2126
+ style.gridAreas = null;
2127
+ style.gridAutoRows = AUTO_TRACK;
2128
+ style.gridAutoColumns = columns;
2129
+ style.gridAutoFlow = dense(parts[1]) ? 'column dense' : 'column';
2130
+ return;
2131
+ }
2132
+ const none = value.trim().toLowerCase() === 'none';
2133
+ const template = none ? null : parseTemplateShorthand(value, ctx);
2134
+ if (!none && !template)
2135
+ return;
2136
+ style.gridRows = template?.rows ?? null;
2137
+ style.gridColumns = template?.columns ?? null;
2138
+ style.gridAreas = template?.areas ?? null;
2139
+ style.gridAutoRows = AUTO_TRACK;
2140
+ style.gridAutoColumns = AUTO_TRACK;
2141
+ style.gridAutoFlow = 'row';
2142
+ return;
2143
+ }
2144
+ case 'grid-column':
2145
+ case 'grid-row': {
2146
+ // an end left out is the start where the start is a name, and auto
2147
+ // where it is not (8.4)
2148
+ const parts = splitTopLevelSlash(value);
2149
+ const start = parseGridLine(parts[0]);
2150
+ const end = parts.length > 1 ? parseGridLine(parts[1]) : named(start);
2151
+ if (start === undefined || end === undefined || parts.length > 2)
2152
+ return;
2153
+ if (name === 'grid-column') {
2154
+ style.gridColumnStart = start;
2155
+ style.gridColumnEnd = end;
2156
+ }
2157
+ else {
2158
+ style.gridRowStart = start;
2159
+ style.gridRowEnd = end;
2160
+ }
2161
+ return;
2162
+ }
2163
+ case 'grid-area': {
2164
+ // the lines left out copy a name: the column start the row start's,
2165
+ // and each end its start's (8.4)
2166
+ const parts = splitTopLevelSlash(value).map(parseGridLine);
2167
+ if (parts.length > 4 || parts.some((p) => p === undefined))
2168
+ return;
2169
+ const rowStart = parts[0] ?? null;
2170
+ const columnStart = parts.length > 1 ? parts[1] : named(rowStart);
2171
+ style.gridRowStart = rowStart;
2172
+ style.gridColumnStart = columnStart;
2173
+ style.gridRowEnd = parts.length > 2 ? parts[2] : named(rowStart);
2174
+ style.gridColumnEnd = parts.length > 3 ? parts[3] : named(columnStart);
2175
+ return;
2176
+ }
2177
+ case 'grid-column-start':
2178
+ case 'grid-column-end':
2179
+ case 'grid-row-start':
2180
+ case 'grid-row-end': {
2181
+ const line = parseGridLine(value);
2182
+ if (line === undefined)
2183
+ return;
2184
+ style[camel(name)] = line;
2185
+ return;
2186
+ }
2187
+ case 'justify-items':
2188
+ case 'justify-self': {
2189
+ const v = value.trim().toLowerCase();
2190
+ const keyword = v === 'auto' && name === 'justify-self'
2191
+ ? 'auto'
2192
+ : v === 'normal' || v === 'stretch'
2193
+ ? v
2194
+ : alignKeyword(v);
2195
+ if (keyword === 'auto' ||
2196
+ keyword === 'normal' ||
2197
+ keyword === 'stretch' ||
2198
+ keyword === 'flex-start' ||
2199
+ keyword === 'flex-end' ||
2200
+ keyword === 'center') {
2201
+ if (name === 'justify-items' && keyword !== 'auto') {
2202
+ style.justifyItems = keyword;
2203
+ }
2204
+ else if (name === 'justify-self')
2205
+ style.justifySelf = keyword;
2206
+ }
2207
+ return;
2208
+ }
2209
+ case 'place-items':
2210
+ case 'place-self': {
2211
+ const parts = splitValue(value);
2212
+ if (!parts.length || parts.length > 2)
2213
+ return;
2214
+ const align = name === 'place-items' ? 'align-items' : 'align-self';
2215
+ const justify = name === 'place-items' ? 'justify-items' : 'justify-self';
2216
+ applyDeclaration(style, parent, align, parts[0], ctx);
2217
+ applyDeclaration(style, parent, justify, parts[1] ?? parts[0], ctx);
779
2218
  return;
780
2219
  }
781
2220
  // --- tables -------------------------------------------------------------
@@ -785,10 +2224,30 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
785
2224
  style.borderCollapse = v;
786
2225
  return;
787
2226
  }
2227
+ case 'caption-side': {
2228
+ const v = value.toLowerCase();
2229
+ if (v === 'top' || v === 'bottom')
2230
+ style.captionSide = v;
2231
+ return;
2232
+ }
2233
+ case 'empty-cells': {
2234
+ const v = value.trim().toLowerCase();
2235
+ if (v === 'show' || v === 'hide')
2236
+ style.emptyCells = v;
2237
+ return;
2238
+ }
788
2239
  case 'border-spacing': {
789
- const len = parseLength(splitValue(value)[0] ?? '', ctx);
790
- if (typeof len === 'number')
791
- style.borderSpacing = len;
2240
+ // one length for both, or the columns' and then the rows' (CSS 2.1
2241
+ // 17.6.1), never negative
2242
+ const parts = splitValue(value);
2243
+ if (parts.length < 1 || parts.length > 2)
2244
+ return;
2245
+ const x = parseLength(parts[0], ctx);
2246
+ const y = parts.length > 1 ? parseLength(parts[1], ctx) : x;
2247
+ if (typeof x !== 'number' || typeof y !== 'number' || x < 0 || y < 0)
2248
+ return;
2249
+ style.borderSpacing = x;
2250
+ style.borderSpacingY = y;
792
2251
  return;
793
2252
  }
794
2253
  case 'table-layout': {
@@ -801,6 +2260,21 @@ export function applyDeclaration(style, parent, prop, rawValue, ctx) {
801
2260
  return;
802
2261
  }
803
2262
  }
2263
+ /** `-webkit-box-pack` and `-webkit-box-align`, as `justify-content` and
2264
+ * `align-items` say them. */
2265
+ const BOX_PACK = new Map([
2266
+ ['start', 'flex-start'],
2267
+ ['end', 'flex-end'],
2268
+ ['center', 'center'],
2269
+ ['justify', 'space-between'],
2270
+ ]);
2271
+ const BOX_ALIGN = new Map([
2272
+ ['start', 'flex-start'],
2273
+ ['end', 'flex-end'],
2274
+ ['center', 'center'],
2275
+ ['baseline', 'baseline'],
2276
+ ['stretch', 'stretch'],
2277
+ ]);
804
2278
  const DISPLAYS = new Set([
805
2279
  'none',
806
2280
  'block',
@@ -819,6 +2293,16 @@ const DISPLAYS = new Set([
819
2293
  'table-caption',
820
2294
  'table-column',
821
2295
  'table-column-group',
2296
+ 'contents',
2297
+ ]);
2298
+ /** `text-decoration-line`'s lines. `blink` is one a user agent may leave
2299
+ * undrawn (CSS 2.1 16.3.1), and this one does; `overline` is not drawn
2300
+ * yet either. */
2301
+ const LINE_KEYWORDS = new Set([
2302
+ 'underline',
2303
+ 'overline',
2304
+ 'line-through',
2305
+ 'blink',
822
2306
  ]);
823
2307
  const DECORATION_STYLES = new Set([
824
2308
  'solid',
@@ -829,23 +2313,148 @@ const DECORATION_STYLES = new Set([
829
2313
  ]);
830
2314
  function overflowKeyword(v) {
831
2315
  const s = (v ?? '').toLowerCase();
832
- if (s === 'visible' || s === 'hidden' || s === 'scroll' || s === 'auto')
2316
+ if (s === 'visible' ||
2317
+ s === 'hidden' ||
2318
+ s === 'scroll' ||
2319
+ s === 'auto' ||
2320
+ s === 'clip') {
833
2321
  return s;
834
- if (s === 'clip')
835
- return 'hidden';
2322
+ }
836
2323
  return null;
837
2324
  }
2325
+ /** `contain`'s containments (CSS Containment 2, 3). */
2326
+ export const CONTAIN_SIZE = 1;
2327
+ export const CONTAIN_INLINE_SIZE = 2;
2328
+ export const CONTAIN_LAYOUT = 4;
2329
+ export const CONTAIN_STYLE = 8;
2330
+ export const CONTAIN_PAINT = 16;
2331
+ /** A `contain` value as its bits, or null where it is none: `strict` and
2332
+ * `content` are shorthands, and the rest a set of the five, `size` and
2333
+ * `inline-size` not both. */
2334
+ function containOf(value) {
2335
+ const words = value.trim().toLowerCase().split(/\s+/);
2336
+ if (words.length === 1) {
2337
+ if (words[0] === 'none')
2338
+ return 0;
2339
+ if (words[0] === 'strict') {
2340
+ return CONTAIN_SIZE | CONTAIN_LAYOUT | CONTAIN_STYLE | CONTAIN_PAINT;
2341
+ }
2342
+ if (words[0] === 'content') {
2343
+ return CONTAIN_LAYOUT | CONTAIN_STYLE | CONTAIN_PAINT;
2344
+ }
2345
+ }
2346
+ let bits = 0;
2347
+ for (const word of words) {
2348
+ const bit = word === 'size'
2349
+ ? CONTAIN_SIZE
2350
+ : word === 'inline-size'
2351
+ ? CONTAIN_INLINE_SIZE
2352
+ : word === 'layout'
2353
+ ? CONTAIN_LAYOUT
2354
+ : word === 'style'
2355
+ ? CONTAIN_STYLE
2356
+ : word === 'paint'
2357
+ ? CONTAIN_PAINT
2358
+ : 0;
2359
+ if (!bit || bits & bit)
2360
+ return null;
2361
+ bits |= bit;
2362
+ }
2363
+ if (bits & CONTAIN_SIZE && bits & CONTAIN_INLINE_SIZE)
2364
+ return null;
2365
+ return bits;
2366
+ }
2367
+ /**
2368
+ * The two axes' `overflow` as they compute together (CSS Overflow 3, 3.1):
2369
+ * `visible` and `clip` hold beside each other, and beside a value that
2370
+ * makes the box a scroll container, `visible` is `auto` and `clip` is
2371
+ * `hidden`.
2372
+ */
2373
+ /**
2374
+ * The containment `content-visibility` brings with it (CSS Containment 2,
2375
+ * 4): `auto` layout, style and paint containment — and size containment
2376
+ * where the box is not on screen, which a document laid out whole never
2377
+ * tells — and `hidden` all four.
2378
+ */
2379
+ export function settleContentVisibility(style) {
2380
+ if (style.contentVisibility === 'auto') {
2381
+ style.contain |= CONTAIN_LAYOUT | CONTAIN_STYLE | CONTAIN_PAINT;
2382
+ }
2383
+ else if (style.contentVisibility === 'hidden') {
2384
+ style.contain |=
2385
+ CONTAIN_SIZE | CONTAIN_LAYOUT | CONTAIN_STYLE | CONTAIN_PAINT;
2386
+ style.contain &= ~CONTAIN_INLINE_SIZE;
2387
+ }
2388
+ }
2389
+ export function settleOverflow(style) {
2390
+ const x = style.overflowX;
2391
+ const y = style.overflowY;
2392
+ const scrollsX = x !== 'visible' && x !== 'clip';
2393
+ const scrollsY = y !== 'visible' && y !== 'clip';
2394
+ if (scrollsX === scrollsY)
2395
+ return;
2396
+ if (scrollsX)
2397
+ style.overflowY = y === 'visible' ? 'auto' : 'hidden';
2398
+ else
2399
+ style.overflowX = x === 'visible' ? 'auto' : 'hidden';
2400
+ }
2401
+ function isVisualBox(v) {
2402
+ return v === 'border-box' || v === 'padding-box' || v === 'content-box';
2403
+ }
2404
+ export function scrolls(style) {
2405
+ const x = style.overflowX;
2406
+ const y = style.overflowY;
2407
+ return (x !== 'visible' && x !== 'clip') || (y !== 'visible' && y !== 'clip');
2408
+ }
2409
+ /** A `justify-content`: the writing mode's `start` and `end`, and `left`
2410
+ * and `right`, kept for the flex box to resolve against its direction;
2411
+ * `normal` and `stretch` pack as `flex-start` does (`overflowAligned`). */
2412
+ function justifyKeyword(value) {
2413
+ const v = overflowAligned(value);
2414
+ switch (v) {
2415
+ case 'start':
2416
+ case 'end':
2417
+ case 'left':
2418
+ case 'right':
2419
+ case 'flex-start':
2420
+ case 'flex-end':
2421
+ case 'center':
2422
+ case 'space-between':
2423
+ case 'space-around':
2424
+ case 'space-evenly':
2425
+ return v;
2426
+ case 'normal':
2427
+ case 'stretch':
2428
+ return v;
2429
+ default:
2430
+ return null;
2431
+ }
2432
+ }
2433
+ /** An alignment's keyword, with `unsafe` before it read past, which is
2434
+ * what an alignment does anyway. A `safe` one sets the subject at the
2435
+ * start where it overflows its room, which is not done here: it is no
2436
+ * value, and the declaration is dropped — which is the start's for the
2437
+ * flex box that overflows, as it was before `safe` was read at all. */
2438
+ function overflowAligned(value) {
2439
+ const v = value.trim().toLowerCase().replace(/\s+/g, ' ');
2440
+ const m = /^unsafe (.+)$/.exec(v);
2441
+ return m ? m[1] : v;
2442
+ }
838
2443
  function alignKeyword(value) {
839
- const v = value.trim().toLowerCase();
2444
+ const v = overflowAligned(value);
840
2445
  switch (v) {
841
2446
  case 'start':
842
2447
  case 'flex-start':
2448
+ case 'self-start':
843
2449
  case 'left':
844
2450
  return 'flex-start';
845
2451
  case 'end':
846
2452
  case 'flex-end':
2453
+ case 'self-end':
847
2454
  case 'right':
848
2455
  return 'flex-end';
2456
+ case 'first baseline':
2457
+ return 'baseline';
849
2458
  case 'center':
850
2459
  return 'center';
851
2460
  case 'stretch':
@@ -865,23 +2474,316 @@ function borderWidth(value, ctx) {
865
2474
  // The keywords are CSS pixels that never pass through `parseLength`, so
866
2475
  // they take the display scale here.
867
2476
  if (kw !== undefined)
868
- return kw * ctx.scale;
2477
+ return snapBorderWidth(kw * ctx.scale);
869
2478
  const len = parseLength(value, ctx);
870
- return typeof len === 'number' ? Math.max(0, len) : null;
2479
+ // a negative width is not a width: the declaration is dropped, and the
2480
+ // one before it stands (CSS 2.1 8.5.1)
2481
+ return typeof len === 'number' && len >= 0 ? snapBorderWidth(len) : null;
2482
+ }
2483
+ /**
2484
+ * A border's or an outline's width in whole device pixels, as it computes
2485
+ * (CSS Values 4, "snap as a border width"): down to the pixel, and a
2486
+ * hairline narrower than one pixel up to it — so two borders of 49.75px
2487
+ * leave the box between them its two pixels, and a 0.5px rule is drawn.
2488
+ * The fraction a length in `em` comes to beside a whole number is not
2489
+ * taken for less than it.
2490
+ */
2491
+ function snapBorderWidth(width) {
2492
+ if (width > 0 && width < 1)
2493
+ return 1;
2494
+ return Math.floor(width + 1e-6);
871
2495
  }
872
- function backgroundPosition(value, ctx) {
873
- const v = (value ?? '').trim().toLowerCase();
874
- if (!v)
2496
+ const HORIZONTAL = { left: 0, center: 50, right: 100 };
2497
+ const VERTICAL = { top: 0, center: 50, bottom: 100 };
2498
+ /**
2499
+ * A `background-position` as its horizontal and vertical parts. A keyword
2500
+ * says which axis it is on, so one value alone is centred on the other —
2501
+ * `bottom` is the bottom, midway across — and two keywords come in either
2502
+ * order; with a length or a percentage among two, the first is across and
2503
+ * the second down (CSS 2.1 14.2.1). Three or four are CSS3's edge form:
2504
+ * `right 10px center` is ten pixels in from the right, midway down.
2505
+ */
2506
+ function positionPair(parts, ctx) {
2507
+ if (parts.length === 0 || parts.length > 4)
875
2508
  return null;
876
- if (v === 'left' || v === 'top')
877
- return 0;
878
- if (v === 'center')
879
- return { pct: 50 };
880
- if (v === 'right' || v === 'bottom')
881
- return { pct: 100 };
882
- const len = parseLength(v, ctx);
883
- return len === AUTO ? 0 : len;
2509
+ const words = parts.map((p) => p.toLowerCase());
2510
+ if (parts.length > 2)
2511
+ return edgePosition(words, ctx);
2512
+ const keyword = (w) => w in HORIZONTAL || w in VERTICAL;
2513
+ const pct = (n) => (n === 0 ? 0 : { pct: n });
2514
+ if (parts.length === 1) {
2515
+ const [w] = words;
2516
+ if (w in VERTICAL && !(w in HORIZONTAL))
2517
+ return [pct(50), pct(VERTICAL[w])];
2518
+ if (keyword(w))
2519
+ return [pct(HORIZONTAL[w]), pct(50)];
2520
+ const x = positionLength(w, ctx);
2521
+ return x === null ? null : [x, pct(50)];
2522
+ }
2523
+ const [a, b] = words;
2524
+ if (keyword(a) && keyword(b)) {
2525
+ const swap = (a in VERTICAL && !(a in HORIZONTAL)) ||
2526
+ (b in HORIZONTAL && !(b in VERTICAL));
2527
+ const [h, v] = swap ? [b, a] : [a, b];
2528
+ if (!(h in HORIZONTAL) || !(v in VERTICAL))
2529
+ return null;
2530
+ return [pct(HORIZONTAL[h]), pct(VERTICAL[v])];
2531
+ }
2532
+ if (a in VERTICAL && !(a in HORIZONTAL))
2533
+ return null;
2534
+ if (b in HORIZONTAL && !(b in VERTICAL))
2535
+ return null;
2536
+ const x = keyword(a) ? pct(HORIZONTAL[a]) : positionLength(a, ctx);
2537
+ const y = keyword(b) ? pct(VERTICAL[b]) : positionLength(b, ctx);
2538
+ return x === null || y === null ? null : [x, y];
2539
+ }
2540
+ /** A position's length or percentage — `auto` is none. */
2541
+ function positionLength(word, ctx) {
2542
+ const len = parseLength(word, ctx);
2543
+ return len === AUTO ? null : len;
884
2544
  }
2545
+ /** The edge form: a keyword for each axis, each but `center` with an
2546
+ * offset in from its edge or not. */
2547
+ function edgePosition(words, ctx) {
2548
+ const keyword = (w) => w !== undefined && (w in HORIZONTAL || w in VERTICAL);
2549
+ const groups = [];
2550
+ for (let i = 0; i < words.length; i += 1) {
2551
+ const word = words[i];
2552
+ if (!keyword(word))
2553
+ return null;
2554
+ let offset = null;
2555
+ if (i + 1 < words.length && !keyword(words[i + 1])) {
2556
+ if (word === 'center')
2557
+ return null;
2558
+ offset = positionLength(words[i + 1], ctx);
2559
+ if (offset === null)
2560
+ return null;
2561
+ i += 1;
2562
+ }
2563
+ groups.push({ word, offset });
2564
+ }
2565
+ if (groups.length !== 2)
2566
+ return null;
2567
+ let [h, v] = groups;
2568
+ if ((h.word in VERTICAL && !(h.word in HORIZONTAL)) ||
2569
+ (v.word in HORIZONTAL && !(v.word in VERTICAL))) {
2570
+ [h, v] = [v, h];
2571
+ }
2572
+ if (!(h.word in HORIZONTAL) || !(v.word in VERTICAL))
2573
+ return null;
2574
+ const x = fromEdge(HORIZONTAL[h.word], h.offset);
2575
+ const y = fromEdge(VERTICAL[v.word], v.offset);
2576
+ return x === null || y === null ? null : [x, y];
2577
+ }
2578
+ function fromEdge(at, offset) {
2579
+ if (offset === null)
2580
+ return at === 0 ? 0 : { pct: at };
2581
+ if (at === 0)
2582
+ return offset;
2583
+ // in from the right or the bottom: `100% - offset`, which a comparison
2584
+ // (`right min(10%, 20px)`) is not the sum for
2585
+ if (typeof offset === 'number')
2586
+ return { pct: 100, px: -offset };
2587
+ if (offset === AUTO || offset.of)
2588
+ return null;
2589
+ return { pct: 100 - offset.pct, px: -(offset.px ?? 0) };
2590
+ }
2591
+ const ONCE = { times: 1 };
2592
+ const ALL = { pct: 100 };
2593
+ export const NO_BORDER_IMAGE = {
2594
+ source: null,
2595
+ slice: [ALL, ALL, ALL, ALL],
2596
+ fill: false,
2597
+ width: [ONCE, ONCE, ONCE, ONCE],
2598
+ outset: [0, 0, 0, 0],
2599
+ repeat: ['stretch', 'stretch'],
2600
+ };
2601
+ /** One to four values, one a side as `margin` takes them; null where one
2602
+ * is not a value. */
2603
+ function sidesOf(parts, read) {
2604
+ if (!parts.length || parts.length > 4)
2605
+ return null;
2606
+ const values = [];
2607
+ for (const part of parts) {
2608
+ const v = read(part);
2609
+ if (v === null)
2610
+ return null;
2611
+ values.push(v);
2612
+ }
2613
+ return fourSides(values);
2614
+ }
2615
+ /** A slice's depth: a number, image pixels, or a percentage; neither may
2616
+ * be negative. */
2617
+ function sliceOf(part) {
2618
+ if (part.endsWith('%')) {
2619
+ const n = parseNumber(part.slice(0, -1));
2620
+ return n !== null && n >= 0 ? { pct: n } : null;
2621
+ }
2622
+ const n = parseNumber(part);
2623
+ return n !== null && n >= 0 ? n : null;
2624
+ }
2625
+ /** `border-image-slice`: one to four depths and `fill`, before them or
2626
+ * after them. */
2627
+ function borderImageSlice(parts) {
2628
+ const words = parts.map((p) => p.toLowerCase());
2629
+ const at = words.indexOf('fill');
2630
+ let fill = false;
2631
+ if (at >= 0) {
2632
+ if (at !== 0 && at !== words.length - 1)
2633
+ return null;
2634
+ fill = true;
2635
+ words.splice(at, 1);
2636
+ }
2637
+ const slice = sidesOf(words, sliceOf);
2638
+ return slice ? { slice, fill } : null;
2639
+ }
2640
+ /** A border image's width over a side: a multiple, a length or a
2641
+ * percentage of the image area, or `auto`; none negative. */
2642
+ function imageWidthOf(part, ctx) {
2643
+ const n = parseNumber(part);
2644
+ if (n !== null)
2645
+ return n >= 0 ? { times: n } : null;
2646
+ const len = parseLength(part, ctx);
2647
+ if (len === null)
2648
+ return null;
2649
+ if (typeof len === 'number')
2650
+ return len >= 0 ? len : null;
2651
+ return len === 'auto' || (len.pct >= 0 && !len.px) ? len : null;
2652
+ }
2653
+ /** How far a border image reaches out on a side: a multiple, or a
2654
+ * length; neither negative. */
2655
+ function imageOutsetOf(part, ctx) {
2656
+ const n = parseNumber(part);
2657
+ if (n !== null)
2658
+ return n >= 0 ? { times: n } : null;
2659
+ const len = parseLength(part, ctx);
2660
+ return typeof len === 'number' && len >= 0 ? len : null;
2661
+ }
2662
+ const IMAGE_REPEATS = new Set(['stretch', 'repeat', 'round', 'space']);
2663
+ /** `border-image-repeat`: one keyword for both ways, or two. */
2664
+ function imageRepeatOf(words) {
2665
+ if (!words.length || words.length > 2)
2666
+ return null;
2667
+ if (!words.every((w) => IMAGE_REPEATS.has(w)))
2668
+ return null;
2669
+ const [x, y = x] = words;
2670
+ return [x, y];
2671
+ }
2672
+ /**
2673
+ * The `border-image` shorthand: the image, the slices — with a width after
2674
+ * a slash and an outset after a second — and the repeat, in any order; each
2675
+ * at most once, and what it does not name reset (CSS Backgrounds 3, 6.8).
2676
+ */
2677
+ function readBorderImage(value, ctx) {
2678
+ const parts = splitValue(value).flatMap(splitSlash);
2679
+ const image = { ...NO_BORDER_IMAGE };
2680
+ let seen = 0;
2681
+ const once = (bit) => {
2682
+ if (seen & bit)
2683
+ return false;
2684
+ seen |= bit;
2685
+ return true;
2686
+ };
2687
+ const isSlice = (p) => p.toLowerCase() === 'fill' || sliceOf(p) !== null;
2688
+ for (let i = 0; i < parts.length;) {
2689
+ const part = parts[i];
2690
+ const v = part.toLowerCase();
2691
+ if (v === 'none' || v.startsWith('url(') || IMAGE_FUNCTION.test(v)) {
2692
+ const source = backgroundImageOf(part, ctx);
2693
+ if (source === undefined || !once(1))
2694
+ return null;
2695
+ image.source = source;
2696
+ i += 1;
2697
+ }
2698
+ else if (IMAGE_REPEATS.has(v)) {
2699
+ let end = i + 1;
2700
+ if (end < parts.length && IMAGE_REPEATS.has(parts[end].toLowerCase())) {
2701
+ end += 1;
2702
+ }
2703
+ const repeat = imageRepeatOf(parts.slice(i, end).map((p) => p.toLowerCase()));
2704
+ if (!repeat || !once(2))
2705
+ return null;
2706
+ image.repeat = repeat;
2707
+ i = end;
2708
+ }
2709
+ else if (isSlice(part)) {
2710
+ let end = i;
2711
+ while (end < parts.length && isSlice(parts[end]))
2712
+ end += 1;
2713
+ const slice = borderImageSlice(parts.slice(i, end));
2714
+ if (!slice || !once(4))
2715
+ return null;
2716
+ image.slice = slice.slice;
2717
+ image.fill = slice.fill;
2718
+ i = end;
2719
+ if (parts[i] !== '/')
2720
+ continue;
2721
+ // `/ width`, `/ width / outset` or `/ / outset`
2722
+ i += 1;
2723
+ end = i;
2724
+ while (end < parts.length && imageWidthOf(parts[end], ctx) !== null) {
2725
+ end += 1;
2726
+ }
2727
+ if (end > i) {
2728
+ const width = sidesOf(parts.slice(i, end), (p) => imageWidthOf(p, ctx));
2729
+ if (!width)
2730
+ return null;
2731
+ image.width = width;
2732
+ }
2733
+ const widened = end > i;
2734
+ i = end;
2735
+ if (parts[i] !== '/') {
2736
+ if (!widened)
2737
+ return null;
2738
+ continue;
2739
+ }
2740
+ i += 1;
2741
+ end = i;
2742
+ while (end < parts.length && imageOutsetOf(parts[end], ctx) !== null) {
2743
+ end += 1;
2744
+ }
2745
+ const outset = sidesOf(parts.slice(i, end), (p) => imageOutsetOf(p, ctx));
2746
+ if (!outset)
2747
+ return null;
2748
+ image.outset = outset;
2749
+ i = end;
2750
+ }
2751
+ else {
2752
+ return null;
2753
+ }
2754
+ }
2755
+ return image;
2756
+ }
2757
+ function readRepeat(words) {
2758
+ if (words.length === 1) {
2759
+ const [w] = words;
2760
+ if (w === 'repeat-x')
2761
+ return repeatPair('repeat', 'no-repeat');
2762
+ if (w === 'repeat-y')
2763
+ return repeatPair('no-repeat', 'repeat');
2764
+ return isRepeatMode(w) ? repeatPair(w, w) : null;
2765
+ }
2766
+ if (words.length !== 2)
2767
+ return null;
2768
+ const [x, y] = words;
2769
+ return isRepeatMode(x) && isRepeatMode(y) ? repeatPair(x, y) : null;
2770
+ }
2771
+ const REPEATS = new Set(['repeat', 'space', 'round', 'no-repeat']);
2772
+ function isRepeatMode(word) {
2773
+ return REPEATS.has(word);
2774
+ }
2775
+ const REPEAT_PAIRS = new Map();
2776
+ /** The one pair for each way of repeating. */
2777
+ function repeatPair(x, y) {
2778
+ const key = `${x} ${y}`;
2779
+ let pair = REPEAT_PAIRS.get(key);
2780
+ if (!pair) {
2781
+ pair = Object.freeze([x, y]);
2782
+ REPEAT_PAIRS.set(key, pair);
2783
+ }
2784
+ return pair;
2785
+ }
2786
+ const REPEAT = repeatPair('repeat', 'repeat');
885
2787
  function applyBorderShorthand(style, name, value, ctx) {
886
2788
  const sides = name === 'border'
887
2789
  ? ['Top', 'Right', 'Bottom', 'Left']
@@ -891,30 +2793,66 @@ function applyBorderShorthand(style, name, value, ctx) {
891
2793
  // `border: none` and `border: 0` both mean "no border", and neither names
892
2794
  // all three components — so the shorthand resets all three first, which is
893
2795
  // what the spec says and what an author relies on to undo a UA border.
894
- let width = 3;
2796
+ let width = BORDER_WIDTH_KEYWORDS.medium * ctx.scale;
895
2797
  let borderStyle = 'none';
896
2798
  let color = 'currentColor';
2799
+ // each of the three at most once (`<line-width> || <line-style> ||
2800
+ // <color>`): `red solid 16px red` is no border, where the second colour
2801
+ // was taken for the first
2802
+ let seen = 0;
897
2803
  for (const part of splitValue(value)) {
898
2804
  const v = part.toLowerCase();
899
2805
  if (BORDER_STYLES.has(v)) {
2806
+ if (seen & 1)
2807
+ return;
2808
+ seen |= 1;
900
2809
  borderStyle = v;
901
2810
  continue;
902
2811
  }
903
2812
  const w = borderWidth(part, ctx);
904
2813
  if (w !== null) {
2814
+ if (seen & 2)
2815
+ return;
2816
+ seen |= 2;
905
2817
  width = w;
906
2818
  continue;
907
2819
  }
908
2820
  const c = parseColor(part);
909
- if (c !== null)
2821
+ if (c !== null) {
2822
+ if (seen & 4)
2823
+ return;
2824
+ seen |= 4;
910
2825
  color = c;
2826
+ continue;
2827
+ }
2828
+ // a negative width makes the whole shorthand invalid
2829
+ const len = parseLength(part, ctx);
2830
+ if (typeof len === 'number' && len < 0)
2831
+ return;
2832
+ }
2833
+ // A width with no style is kept rather than zeroed — the layout draws
2834
+ // nothing for `none` — so a later `border-style` alone finds it.
2835
+ if (name === 'border') {
2836
+ // named, not built: preflight's `border: 0 solid` is on every element,
2837
+ // and twelve stores through a computed name were most of its cost
2838
+ style.borderTopWidth = width;
2839
+ style.borderRightWidth = width;
2840
+ style.borderBottomWidth = width;
2841
+ style.borderLeftWidth = width;
2842
+ style.borderTopStyle = borderStyle;
2843
+ style.borderRightStyle = borderStyle;
2844
+ style.borderBottomStyle = borderStyle;
2845
+ style.borderLeftStyle = borderStyle;
2846
+ if (color !== null) {
2847
+ style.borderTopColor = color;
2848
+ style.borderRightColor = color;
2849
+ style.borderBottomColor = color;
2850
+ style.borderLeftColor = color;
2851
+ }
2852
+ return;
911
2853
  }
912
- // A shorthand with a style but no width takes the initial `medium`; one
913
- // with neither paints nothing, so the width is irrelevant.
914
- const effective = borderStyle === 'none' || borderStyle === 'hidden' ? 0 : (width ?? 3);
915
2854
  for (const side of sides) {
916
- style[`border${side}Width`] =
917
- effective;
2855
+ style[`border${side}Width`] = width;
918
2856
  style[`border${side}Style`] =
919
2857
  borderStyle;
920
2858
  if (color !== null)
@@ -922,53 +2860,533 @@ function applyBorderShorthand(style, name, value, ctx) {
922
2860
  color;
923
2861
  }
924
2862
  }
2863
+ /** A font family name: quoted, or a sequence of identifiers. */
2864
+ function isFamilyName(name) {
2865
+ const n = name.trim();
2866
+ if (n[0] === '"' || n[0] === "'")
2867
+ return true;
2868
+ if (!n)
2869
+ return false;
2870
+ for (const word of n.split(/\s+/)) {
2871
+ if (!startsIdent(word, 0) || readIdent(word, 0).end !== word.length) {
2872
+ return false;
2873
+ }
2874
+ }
2875
+ return true;
2876
+ }
925
2877
  function applyBackgroundShorthand(style, value, ctx) {
926
- // Only the last layer paints against the box, so a multi-layer background
927
- // reduces to its last comma group rather than being dropped.
928
- const layer = splitCommas(value).pop() ?? '';
929
- style.backgroundColor = null;
930
- style.backgroundImage = null;
931
- style.backgroundRepeat = 'repeat';
932
- style.backgroundSize = 'auto';
933
- const positions = [];
934
- for (const part of splitValue(layer)) {
2878
+ // A layer a comma group, top first: each has to be a layer, and only the
2879
+ // last, which is painted against the box, may have a colour, or the
2880
+ // declaration is none
2881
+ const texts = splitCommas(value);
2882
+ const layers = [];
2883
+ for (let k = 0; k < texts.length; k += 1) {
2884
+ const layer = readBackgroundLayer(texts[k], ctx, k === texts.length - 1);
2885
+ if (!layer)
2886
+ return;
2887
+ layers.push(layer);
2888
+ }
2889
+ if (!layers.length)
2890
+ return;
2891
+ const [top] = layers;
2892
+ style.backgroundClipText = false;
2893
+ style.backgroundColor = layers[layers.length - 1].color;
2894
+ style.backgroundImage = top.image;
2895
+ style.backgroundGradient = top.gradient;
2896
+ style.backgroundRepeat = top.repeat;
2897
+ style.backgroundSize = top.size;
2898
+ style.backgroundAttachment = top.attachment;
2899
+ style.backgroundOrigin = top.origin;
2900
+ style.backgroundClip = top.clip;
2901
+ [style.backgroundPositionX, style.backgroundPositionY] = top.position ?? [
2902
+ 0, 0,
2903
+ ];
2904
+ const many = layers.length > 1;
2905
+ style.backgroundImages = many
2906
+ ? layers.map((l) => l.image ?? l.gradient)
2907
+ : null;
2908
+ style.backgroundRepeats = many ? layers.map((l) => l.repeat) : null;
2909
+ style.backgroundSizes = many ? layers.map((l) => l.size) : null;
2910
+ style.backgroundAttachments = many ? layers.map((l) => l.attachment) : null;
2911
+ style.backgroundOrigins = many ? layers.map((l) => l.origin) : null;
2912
+ style.backgroundClips = many ? layers.map((l) => l.clip) : null;
2913
+ style.backgroundPositions = many
2914
+ ? layers.map((l) => l.position ?? [0, 0])
2915
+ : null;
2916
+ }
2917
+ /** One layer's `background-image`: a url, a gradient — none where it is
2918
+ * one this does not draw, as though there were none — or none; undefined
2919
+ * where it is not an image at all. */
2920
+ function backgroundImageOf(text, ctx) {
2921
+ const v = text.trim();
2922
+ if (IMAGE_FUNCTION.test(v.toLowerCase()))
2923
+ return parseLinearGradient(v, ctx);
2924
+ return parseUrl(v);
2925
+ }
2926
+ /** A background property's values, one a comma-separated layer, each read
2927
+ * by `read`; null where one is not a value, which drops the declaration. */
2928
+ function layerValues(value, read) {
2929
+ const out = [];
2930
+ for (const part of splitCommas(value)) {
2931
+ const v = read(part.trim());
2932
+ if (v === null)
2933
+ return null;
2934
+ out.push(v);
2935
+ }
2936
+ return out.length ? out : null;
2937
+ }
2938
+ /**
2939
+ * One layer of the `background` shorthand, read whole before anything is
2940
+ * set: each part at most once, and a token that is none of them — a
2941
+ * string, `red\;`, a second colour — makes the declaration invalid, and it
2942
+ * is dropped rather than resetting the background it meant to replace
2943
+ * (CSS 2.1 4.2). What CSS3 adds is read too, so that a declaration a
2944
+ * browser keeps is kept: `/ cover` after the position, `space` and `round`,
2945
+ * a gradient — drawn as nothing, over the layer's colour.
2946
+ */
2947
+ function readBackgroundLayer(text, ctx, last) {
2948
+ const parts = splitValue(text).flatMap(splitSlash);
2949
+ const layer = {
2950
+ color: null,
2951
+ image: null,
2952
+ gradient: null,
2953
+ repeat: REPEAT,
2954
+ size: 'auto',
2955
+ attachment: 'scroll',
2956
+ origin: 'padding-box',
2957
+ clip: 'border-box',
2958
+ position: null,
2959
+ };
2960
+ let seen = 0;
2961
+ const once = (bit) => {
2962
+ if (seen & bit)
2963
+ return false;
2964
+ seen |= bit;
2965
+ return true;
2966
+ };
2967
+ let boxes = 0;
2968
+ for (let i = 0; i < parts.length;) {
2969
+ const part = parts[i];
935
2970
  const v = part.toLowerCase();
936
- if (v.startsWith('url(')) {
937
- style.backgroundImage = parseUrl(part);
938
- continue;
2971
+ if (v === 'none' || v.startsWith('url(') || IMAGE_FUNCTION.test(v)) {
2972
+ if (!once(1))
2973
+ return null;
2974
+ if (v.startsWith('url(')) {
2975
+ const url = parseUrl(part);
2976
+ if (url === undefined)
2977
+ return null;
2978
+ layer.image = url;
2979
+ }
2980
+ else if (v !== 'none')
2981
+ layer.gradient = parseLinearGradient(part, ctx);
2982
+ i += 1;
939
2983
  }
940
- if (v === 'repeat' ||
941
- v === 'repeat-x' ||
942
- v === 'repeat-y' ||
943
- v === 'no-repeat') {
944
- style.backgroundRepeat = v;
945
- continue;
2984
+ else if (v === 'repeat-x' || v === 'repeat-y' || REPEATS.has(v)) {
2985
+ if (!once(2))
2986
+ return null;
2987
+ const pair = REPEATS.has(v) && REPEATS.has(parts[i + 1]?.toLowerCase());
2988
+ const repeat = readRepeat(pair ? [v, parts[i + 1].toLowerCase()] : [v]);
2989
+ if (!repeat)
2990
+ return null;
2991
+ layer.repeat = repeat;
2992
+ i += pair ? 2 : 1;
946
2993
  }
947
- if (v === 'cover' || v === 'contain') {
948
- style.backgroundSize = v;
949
- continue;
2994
+ else if (v === 'scroll' || v === 'fixed' || v === 'local') {
2995
+ if (!once(4))
2996
+ return null;
2997
+ layer.attachment = v;
2998
+ i += 1;
950
2999
  }
951
- if (v === 'border-box' ||
952
- v === 'padding-box' ||
953
- v === 'content-box' ||
954
- v === 'fixed' ||
955
- v === 'scroll' ||
956
- v === 'local') {
957
- continue;
3000
+ else if (isVisualBox(v)) {
3001
+ // one box is both the origin and the clip, and a second the clip
3002
+ if ((boxes += 1) > 2)
3003
+ return null;
3004
+ if (boxes === 1)
3005
+ layer.origin = v;
3006
+ layer.clip = v;
3007
+ i += 1;
958
3008
  }
959
- const c = parseColor(part);
960
- if (c !== null) {
961
- style.backgroundColor = c;
962
- continue;
3009
+ else if (isPositionPart(v, ctx)) {
3010
+ if (!once(8))
3011
+ return null;
3012
+ let end = i;
3013
+ while (end < parts.length &&
3014
+ isPositionPart(parts[end].toLowerCase(), ctx)) {
3015
+ end += 1;
3016
+ }
3017
+ layer.position = positionPair(parts.slice(i, end), ctx);
3018
+ if (!layer.position)
3019
+ return null;
3020
+ i = end;
3021
+ if (parts[i] !== '/')
3022
+ continue;
3023
+ // `/ <size>`: `cover`, `contain`, or one or two lengths
3024
+ const first = parts[i + 1]?.toLowerCase();
3025
+ if (first === 'cover' || first === 'contain') {
3026
+ layer.size = first;
3027
+ i += 2;
3028
+ }
3029
+ else if (first !== undefined && isSizePart(first, ctx)) {
3030
+ const two = i + 2 < parts.length && isSizePart(parts[i + 2].toLowerCase(), ctx);
3031
+ layer.size =
3032
+ backgroundSizeOf(parts.slice(i + 1, i + (two ? 3 : 2)), ctx) ??
3033
+ 'auto';
3034
+ i += two ? 3 : 2;
3035
+ }
3036
+ else {
3037
+ return null;
3038
+ }
3039
+ }
3040
+ else {
3041
+ const color = parseColor(part);
3042
+ if (color === null || !last || !once(16))
3043
+ return null;
3044
+ layer.color = color;
3045
+ i += 1;
3046
+ }
3047
+ }
3048
+ return layer;
3049
+ }
3050
+ /**
3051
+ * A `linear-gradient()`, or null for any other image function and for one
3052
+ * that is no gradient: a direction by angle, by side or by corner, with a
3053
+ * colour interpolation method (`in oklab`, which Tailwind 4 writes) read and
3054
+ * not honoured, and at least two stops, each with no position, one, or two;
3055
+ * a bare percentage between stops, a hint, is passed over.
3056
+ */
3057
+ function parseLinearGradient(text, ctx) {
3058
+ const m = /^linear-gradient\((.*)\)$/is.exec(text.trim());
3059
+ if (!m)
3060
+ return null;
3061
+ const args = splitCommas(m[1]).map((a) => a.trim());
3062
+ const out = { angle: Math.PI, corner: null, stops: [] };
3063
+ const first = (args[0] ?? '')
3064
+ .toLowerCase()
3065
+ .replace(/\bin\s+[a-z-]+(?:\s+(?:shorter|longer|increasing|decreasing)\s+hue)?/, '')
3066
+ .trim();
3067
+ let from = 0;
3068
+ if (!first || first.startsWith('to ')) {
3069
+ from = 1;
3070
+ const sides = first.split(/\s+/).slice(1).sort();
3071
+ const side = sides.join(' ');
3072
+ const SIDES = {
3073
+ top: 0,
3074
+ right: Math.PI / 2,
3075
+ bottom: Math.PI,
3076
+ left: (3 * Math.PI) / 2,
3077
+ };
3078
+ if (sides.length === 1 && side in SIDES)
3079
+ out.angle = SIDES[side];
3080
+ else if (sides.length === 2) {
3081
+ const [a, b] = sides;
3082
+ const vertical = a === 'bottom' || a === 'top' ? a : b;
3083
+ const horizontal = a === 'left' || a === 'right' ? a : b;
3084
+ if (vertical === horizontal)
3085
+ return null;
3086
+ out.corner = `${vertical} ${horizontal}`;
3087
+ }
3088
+ else if (sides.length)
3089
+ return null;
3090
+ }
3091
+ else {
3092
+ const angle = /^(-?\d*\.?\d+)(deg|rad|grad|turn)?$/.exec(first);
3093
+ if (angle) {
3094
+ const n = Number(angle[1]);
3095
+ const unit = angle[2] ?? (n === 0 ? 'deg' : '');
3096
+ const turns = {
3097
+ deg: 1 / 360,
3098
+ rad: 1 / (2 * Math.PI),
3099
+ grad: 1 / 400,
3100
+ turn: 1,
3101
+ };
3102
+ if (!(unit in turns))
3103
+ return null;
3104
+ out.angle = n * turns[unit] * 2 * Math.PI;
3105
+ from = 1;
3106
+ }
3107
+ }
3108
+ for (const arg of args.slice(from)) {
3109
+ const parts = splitValue(arg);
3110
+ const color = parseColor(parts[0] ?? '');
3111
+ if (color === null) {
3112
+ // a hint, the midpoint of the transition, which this does not move
3113
+ if (parts.length === 1 && parseLength(parts[0], ctx) !== null)
3114
+ continue;
3115
+ return null;
3116
+ }
3117
+ if (parts.length > 3)
3118
+ return null;
3119
+ const at = parts.slice(1).map((p) => parseLength(p, ctx));
3120
+ if (at.some((p) => p === null || p === AUTO))
3121
+ return null;
3122
+ if (!at.length)
3123
+ out.stops.push({ color, at: null });
3124
+ for (const p of at)
3125
+ out.stops.push({ color, at: p });
3126
+ }
3127
+ return out.stops.length >= 2 ? out : null;
3128
+ }
3129
+ /** The images CSS3 has beyond `url()`, which a layer may name: a linear
3130
+ * gradient is drawn, and the rest draw as nothing. */
3131
+ const IMAGE_FUNCTION = /^(?:-(?:webkit|moz|o|ms)-)?(?:(?:repeating-)?(?:linear|radial|conic)-gradient|gradient|image-set|cross-fade|element|paint)\(/;
3132
+ function isPositionPart(word, ctx) {
3133
+ return (word in HORIZONTAL || word in VERTICAL || positionLength(word, ctx) !== null);
3134
+ }
3135
+ function isSizePart(word, ctx) {
3136
+ if (word === 'auto')
3137
+ return true;
3138
+ const len = positionLength(word, ctx);
3139
+ if (len === null || len === AUTO)
3140
+ return false;
3141
+ return typeof len === 'number' ? len >= 0 : len.pct >= 0;
3142
+ }
3143
+ /** What each `white-space` keeps of the white space (CSS Text 4, 3.1). */
3144
+ const COLLAPSE_OF = {
3145
+ normal: 'collapse',
3146
+ nowrap: 'collapse',
3147
+ pre: 'preserve',
3148
+ 'pre-wrap': 'preserve',
3149
+ 'pre-line': 'preserve-breaks',
3150
+ };
3151
+ function wrapsIn(whiteSpace) {
3152
+ return whiteSpace !== 'nowrap' && whiteSpace !== 'pre';
3153
+ }
3154
+ /** The `white-space` of a collapse and a wrap. Kept breaks and no wrapping
3155
+ * have no keyword of their own, and keep their breaks, as `pre-line`. */
3156
+ function whiteSpaceOf(collapse, wrap) {
3157
+ if (collapse === 'collapse')
3158
+ return wrap ? 'normal' : 'nowrap';
3159
+ if (collapse === 'preserve')
3160
+ return wrap ? 'pre-wrap' : 'pre';
3161
+ return 'pre-line';
3162
+ }
3163
+ /**
3164
+ * The translation a `transform` makes: the sum of its translate functions'
3165
+ * and its matrices' — `translate(-50%, -50%)`, Tailwind 3's `translate(
3166
+ * var(--tw-translate-x), var(--tw-translate-y)) rotate(…) skewX(…) …` —
3167
+ * with the functions that rotate, scale, skew or add perspective read and
3168
+ * not drawn. Null for `none`, and undefined for what is no transform list,
3169
+ * which drops the declaration.
3170
+ */
3171
+ function transformTranslation(value, ctx) {
3172
+ if (value.trim().toLowerCase() === 'none')
3173
+ return null;
3174
+ let x = 0;
3175
+ let y = 0;
3176
+ const parts = splitValue(value);
3177
+ if (!parts.length)
3178
+ return undefined;
3179
+ for (const part of parts) {
3180
+ const m = /^([a-z0-9]+)\((.*)\)$/is.exec(part.trim());
3181
+ if (!m)
3182
+ return undefined;
3183
+ const name = m[1].toLowerCase();
3184
+ const args = splitCommas(m[2]).map((a) => a.trim());
3185
+ let dx = 0;
3186
+ let dy = 0;
3187
+ if (name === 'translate' || name === 'translate3d') {
3188
+ dx = parseLength(args[0] ?? '', ctx);
3189
+ dy = args[1] === undefined ? 0 : parseLength(args[1], ctx);
3190
+ }
3191
+ else if (name === 'translatex')
3192
+ dx = parseLength(args[0] ?? '', ctx);
3193
+ else if (name === 'translatey')
3194
+ dy = parseLength(args[0] ?? '', ctx);
3195
+ else if (name === 'matrix' || name === 'matrix3d') {
3196
+ // the translation is the last column's
3197
+ const n = args.map(Number);
3198
+ if (n.length !== (name === 'matrix' ? 6 : 16) || n.some(isNaN)) {
3199
+ return undefined;
3200
+ }
3201
+ [dx, dy] = name === 'matrix' ? [n[4], n[5]] : [n[12], n[13]];
3202
+ }
3203
+ else if (!TRANSFORMS.has(name))
3204
+ return undefined;
3205
+ if (dx === null || dx === AUTO || dy === null || dy === AUTO) {
3206
+ return undefined;
3207
+ }
3208
+ const sx = addLen(x, dx);
3209
+ const sy = addLen(y, dy);
3210
+ if (sx === null || sy === null)
3211
+ return undefined;
3212
+ x = sx;
3213
+ y = sy;
3214
+ }
3215
+ return [x, y];
3216
+ }
3217
+ /** The transform functions read and not drawn. */
3218
+ const TRANSFORMS = new Set([
3219
+ 'translatez',
3220
+ 'rotate',
3221
+ 'rotatex',
3222
+ 'rotatey',
3223
+ 'rotatez',
3224
+ 'rotate3d',
3225
+ 'scale',
3226
+ 'scalex',
3227
+ 'scaley',
3228
+ 'scalez',
3229
+ 'scale3d',
3230
+ 'skew',
3231
+ 'skewx',
3232
+ 'skewy',
3233
+ 'perspective',
3234
+ ]);
3235
+ /** Two lengths added, a percentage and a length kept apart; null where
3236
+ * one is a `min()` or a `max()`, which a sum cannot be kept of. */
3237
+ function addLen(a, b) {
3238
+ if (a === AUTO || b === AUTO)
3239
+ return null;
3240
+ if (typeof a === 'number' && typeof b === 'number')
3241
+ return a + b;
3242
+ if (a === 0)
3243
+ return b;
3244
+ if (b === 0)
3245
+ return a;
3246
+ const pa = typeof a === 'number' ? { pct: 0, px: a } : a;
3247
+ const pb = typeof b === 'number' ? { pct: 0, px: b } : b;
3248
+ if (pa.of || pb.of)
3249
+ return null;
3250
+ return { pct: pa.pct + pb.pct, px: (pa.px ?? 0) + (pb.px ?? 0) };
3251
+ }
3252
+ /** An intrinsic size keyword, with the prefixes browsers still read, or
3253
+ * `fit-content()` of a length or a percentage (CSS Sizing 3, 3.1) — a
3254
+ * width's, where a height's is its content height as the keyword is;
3255
+ * null for anything else. */
3256
+ function contentSizeOf(value, ctx, inline) {
3257
+ const word = value.trim().toLowerCase();
3258
+ if (word === 'stretch' ||
3259
+ word === '-webkit-fill-available' ||
3260
+ word === '-moz-available') {
3261
+ return 'stretch';
3262
+ }
3263
+ const v = word.replace(/^-(?:webkit|moz)-/, '');
3264
+ if (v === 'min-content' || v === 'max-content' || v === 'fit-content') {
3265
+ return v;
3266
+ }
3267
+ const m = /^fit-content\((.*)\)$/s.exec(v);
3268
+ if (!m)
3269
+ return null;
3270
+ const fit = parseLength(m[1], ctx);
3271
+ if (fit === null || fit === AUTO || !notNegative(fit))
3272
+ return null;
3273
+ return inline ? { fit } : 'fit-content';
3274
+ }
3275
+ /** A `background-size`: `cover`, `contain`, `auto`, or one or two of a
3276
+ * length, a percentage and `auto`, the second `auto` where there is none;
3277
+ * null for what is none of these. */
3278
+ function backgroundSizeOf(parts, ctx) {
3279
+ const words = parts.map((p) => p.toLowerCase());
3280
+ if (words.length === 1 && (words[0] === 'cover' || words[0] === 'contain')) {
3281
+ return words[0];
3282
+ }
3283
+ if (!words.length || words.length > 2)
3284
+ return null;
3285
+ if (!words.every((w) => isSizePart(w, ctx)))
3286
+ return null;
3287
+ const axis = (w) => {
3288
+ if (w === undefined || w === 'auto')
3289
+ return 'auto';
3290
+ const len = positionLength(w, ctx);
3291
+ return len === null || len === AUTO ? 'auto' : len;
3292
+ };
3293
+ const width = axis(words[0]);
3294
+ const height = axis(words[1]);
3295
+ return width === 'auto' && height === 'auto' ? 'auto' : [width, height];
3296
+ }
3297
+ /** `center/cover` is three tokens, as `center / cover` is. */
3298
+ function splitSlash(part) {
3299
+ if (!part.includes('/') || part.includes('('))
3300
+ return [part];
3301
+ return part.split(/(\/)/).filter(Boolean);
3302
+ }
3303
+ /** Each `font-variant` keyword: the longhand it belongs to and the features
3304
+ * it stands for (CSS Fonts 3, 6). */
3305
+ const VARIANTS = {
3306
+ 'lining-nums': ['numeric', 'lnum=1'],
3307
+ 'oldstyle-nums': ['numeric', 'onum=1'],
3308
+ 'proportional-nums': ['numeric', 'pnum=1'],
3309
+ 'tabular-nums': ['numeric', 'tnum=1'],
3310
+ 'diagonal-fractions': ['numeric', 'frac=1'],
3311
+ 'stacked-fractions': ['numeric', 'afrc=1'],
3312
+ ordinal: ['numeric', 'ordn=1'],
3313
+ 'slashed-zero': ['numeric', 'zero=1'],
3314
+ 'small-caps': ['caps', 'smcp=1'],
3315
+ 'all-small-caps': ['caps', 'c2sc=1,smcp=1'],
3316
+ 'petite-caps': ['caps', 'pcap=1'],
3317
+ 'all-petite-caps': ['caps', 'c2pc=1,pcap=1'],
3318
+ unicase: ['caps', 'unic=1'],
3319
+ 'titling-caps': ['caps', 'titl=1'],
3320
+ 'common-ligatures': ['ligatures', 'liga=1,clig=1'],
3321
+ 'no-common-ligatures': ['ligatures', 'liga=0,clig=0'],
3322
+ 'discretionary-ligatures': ['ligatures', 'dlig=1'],
3323
+ 'no-discretionary-ligatures': ['ligatures', 'dlig=0'],
3324
+ 'historical-ligatures': ['ligatures', 'hlig=1'],
3325
+ 'no-historical-ligatures': ['ligatures', 'hlig=0'],
3326
+ contextual: ['ligatures', 'calt=1'],
3327
+ 'no-contextual': ['ligatures', 'calt=0'],
3328
+ sub: ['position', 'subs=1'],
3329
+ super: ['position', 'sups=1'],
3330
+ };
3331
+ const VARIANT_FIELDS = {
3332
+ numeric: 'fontVariantNumeric',
3333
+ caps: 'fontVariantCaps',
3334
+ ligatures: 'fontVariantLigatures',
3335
+ position: 'fontVariantPosition',
3336
+ };
3337
+ const ALL_VARIANTS = [
3338
+ 'numeric',
3339
+ 'caps',
3340
+ 'ligatures',
3341
+ 'position',
3342
+ ];
3343
+ /**
3344
+ * A `font-variant` longhand's value, or the shorthand's (`kind` null), as
3345
+ * the fields it sets; null for a value it does not take. The shorthand
3346
+ * sets all four, the ones it names nothing of back to `normal`.
3347
+ */
3348
+ function parseFontVariant(value, kind) {
3349
+ const words = value.trim().toLowerCase().split(/\s+/);
3350
+ const kinds = kind ? [kind] : ALL_VARIANTS;
3351
+ const named = {};
3352
+ if (words.length === 1 && words[0] === 'normal') {
3353
+ // every longhand the value sets goes back to none
3354
+ }
3355
+ else if (words.length === 1 &&
3356
+ words[0] === 'none' &&
3357
+ (kind === null || kind === 'ligatures')) {
3358
+ named.ligatures = ['liga=0,clig=0,dlig=0,hlig=0,calt=0'];
3359
+ }
3360
+ else {
3361
+ for (const word of words) {
3362
+ const known = VARIANTS[word];
3363
+ if (!known || !kinds.includes(known[0]))
3364
+ return null;
3365
+ (named[known[0]] ??= []).push(known[1]);
963
3366
  }
964
- const pos = backgroundPosition(part, ctx);
965
- if (pos !== null)
966
- positions.push(pos);
967
3367
  }
968
- if (positions.length) {
969
- style.backgroundPositionX = positions[0];
970
- style.backgroundPositionY = positions[1] ?? { pct: 50 };
3368
+ const fields = {};
3369
+ for (const k of kinds) {
3370
+ fields[VARIANT_FIELDS[k]] = (named[k] ?? []).join(',');
3371
+ }
3372
+ return fields;
3373
+ }
3374
+ /** `font-feature-settings`: `"tnum", "liga" 0, "ss01" on` as `tnum=1,…`,
3375
+ * '' for `normal`, and null for a value that is neither. */
3376
+ function parseFeatureSettings(value) {
3377
+ const v = value.trim();
3378
+ if (v.toLowerCase() === 'normal')
3379
+ return '';
3380
+ const pairs = [];
3381
+ for (const part of splitCommas(v)) {
3382
+ const m = /^(["'])([\x20-\x7e]{4})\1(?:\s+(on|off|\d+))?$/i.exec(part.trim());
3383
+ if (!m)
3384
+ return null;
3385
+ const setting = (m[3] ?? 'on').toLowerCase();
3386
+ const n = setting === 'on' ? 1 : setting === 'off' ? 0 : Number(setting);
3387
+ pairs.push(`${m[2]}=${n}`);
971
3388
  }
3389
+ return pairs.join(',');
972
3390
  }
973
3391
  function applyFontShorthand(style, parent, value, ctx) {
974
3392
  // The `font: caption | menu | …` system forms name a font this renderer
@@ -976,26 +3394,58 @@ function applyFontShorthand(style, parent, value, ctx) {
976
3394
  const parts = splitValue(value);
977
3395
  if (parts.length < 2)
978
3396
  return;
3397
+ let fontStyle = 'normal';
3398
+ let weight = null;
3399
+ let smallCaps = false;
979
3400
  let i = 0;
980
3401
  for (; i < parts.length; i += 1) {
981
3402
  const v = parts[i].toLowerCase();
982
3403
  if (v === 'italic' || v === 'oblique')
983
- style.fontStyle = v;
3404
+ fontStyle = v;
984
3405
  else if (v === 'bold' ||
985
3406
  v === 'bolder' ||
986
3407
  v === 'lighter' ||
987
3408
  /^\d{3}$/.test(v)) {
988
- style.fontWeight = parseWeight(v, parent.fontWeight);
3409
+ weight = v;
989
3410
  }
990
- else if (v === 'normal' || v === 'small-caps')
3411
+ else if (v === 'small-caps')
3412
+ smallCaps = true;
3413
+ else if (v === 'normal')
991
3414
  continue;
992
3415
  else
993
3416
  break;
994
3417
  }
995
- const sizePart = parts[i];
996
- if (!sizePart)
3418
+ // `12px/1.5`, or the same with space round the slash
3419
+ let [sizeText, lineText] = (parts[i] ?? '').split('/');
3420
+ let next = i + 1;
3421
+ if (lineText === '' || (lineText === undefined && parts[next]?.[0] === '/')) {
3422
+ const slash = lineText === undefined ? parts[next++].slice(1) : '';
3423
+ lineText = slash || parts[next++];
3424
+ }
3425
+ const family = parts.slice(next).join(' ');
3426
+ // A size and a family, or the value is not a font and the declaration is
3427
+ // dropped whole, as CSS drops any value it cannot read — a line height
3428
+ // below nought included: `font: 4em/-2em serif` set the text at 4em.
3429
+ const size = keywordFontSize(sizeText ?? '', parent.fontSize, ctx.rem) ??
3430
+ parseLength(sizeText ?? '', { ...ctx, em: parent.fontSize });
3431
+ if (!family || size === null || size === AUTO)
3432
+ return;
3433
+ if (lineText && negativeLength(lineText, ctx))
997
3434
  return;
998
- const [sizeText, lineText] = sizePart.split('/');
3435
+ // What the shorthand does not name goes back to its initial value rather
3436
+ // than keeping the parent's (CSS 2.1 15.8): `p { font: 12pt serif }`
3437
+ // inside a document set at `20px/1em` has lines of normal height, not 20px.
3438
+ style.fontStyle = fontStyle;
3439
+ style.fontWeight = weight ? parseWeight(weight, parent.fontWeight) : 400;
3440
+ // the variants too, save the small capitals it may name; not the
3441
+ // features `font-feature-settings` sets, which the shorthand leaves
3442
+ style.fontVariantNumeric = '';
3443
+ style.fontVariantCaps = smallCaps ? 'smcp=1' : '';
3444
+ style.fontVariantLigatures = '';
3445
+ style.fontVariantPosition = '';
3446
+ style.fontKerning = '';
3447
+ style.lineHeight = 'normal';
3448
+ style.lineHeightIsLength = false;
999
3449
  applyDeclaration(style, parent, 'font-size', sizeText, ctx);
1000
3450
  if (lineText) {
1001
3451
  applyDeclaration(style, parent, 'line-height', lineText, {
@@ -1003,9 +3453,17 @@ function applyFontShorthand(style, parent, value, ctx) {
1003
3453
  em: style.fontSize,
1004
3454
  });
1005
3455
  }
1006
- const family = parts.slice(i + 1).join(' ');
1007
- if (family)
1008
- applyDeclaration(style, parent, 'font-family', family, ctx);
3456
+ applyDeclaration(style, parent, 'font-family', family, ctx);
3457
+ }
3458
+ /** Whether a line height is a number or a length below nought. */
3459
+ function negativeLength(value, ctx) {
3460
+ const n = parseNumber(value);
3461
+ if (n !== null)
3462
+ return n < 0;
3463
+ const len = parseLength(value, ctx);
3464
+ if (typeof len === 'number')
3465
+ return len < 0;
3466
+ return !!len && typeof len === 'object' && resolve(len, 100) < 0;
1009
3467
  }
1010
3468
  function applyFlexShorthand(style, value, ctx) {
1011
3469
  const v = value.trim().toLowerCase();
@@ -1025,6 +3483,10 @@ function applyFlexShorthand(style, value, ctx) {
1025
3483
  const numbers = [];
1026
3484
  let basis = null;
1027
3485
  for (const part of parts) {
3486
+ if (part.toLowerCase() === 'content') {
3487
+ basis = 'content';
3488
+ continue;
3489
+ }
1028
3490
  const n = parseNumber(part);
1029
3491
  if (n !== null && numbers.length < 2 && !part.includes('%')) {
1030
3492
  numbers.push(n);
@@ -1042,11 +3504,644 @@ function applyFlexShorthand(style, value, ctx) {
1042
3504
  style.flexShrink = numbers.length > 1 ? numbers[1] : 1;
1043
3505
  style.flexBasis = basis ?? (numbers.length ? 0 : AUTO);
1044
3506
  }
3507
+ // --- grid ------------------------------------------------------------------
3508
+ const AUTO_TRACK = { min: 'auto', max: 'auto' };
3509
+ /** One track's sizing, or null for what is not one. */
3510
+ function parseGridTrack(token, ctx) {
3511
+ const t = token.trim().toLowerCase();
3512
+ if (!t)
3513
+ return null;
3514
+ if (t === 'auto')
3515
+ return AUTO_TRACK;
3516
+ if (t === 'min-content' || t === 'max-content')
3517
+ return { min: t, max: t };
3518
+ const fr = /^(\d*\.?\d+)fr$/.exec(t);
3519
+ if (fr)
3520
+ return { min: 'auto', max: { fr: Number(fr[1]) } };
3521
+ const fn = /^(minmax|fit-content)\((.*)\)$/s.exec(t);
3522
+ if (fn) {
3523
+ if (fn[1] === 'fit-content') {
3524
+ const fit = parseLength(fn[2].trim(), ctx);
3525
+ if (fit === null || fit === AUTO)
3526
+ return null;
3527
+ if (typeof fit === 'number' && fit < 0)
3528
+ return null;
3529
+ return { min: AUTO, max: { fit } };
3530
+ }
3531
+ const [a, b] = splitCommas(fn[2]).map((p) => p.trim());
3532
+ if (a === undefined || b === undefined)
3533
+ return null;
3534
+ const min = parseGridTrack(a, ctx);
3535
+ const max = parseGridTrack(b, ctx);
3536
+ // an `fr` is no minimum
3537
+ if (!min ||
3538
+ !max ||
3539
+ (typeof min.max === 'object' && min.max !== null && 'fr' in min.max))
3540
+ return null;
3541
+ return { min: min.min, max: max.max };
3542
+ }
3543
+ const len = parseLength(token.trim(), ctx);
3544
+ if (len === null || len === AUTO)
3545
+ return null;
3546
+ return { min: len, max: len };
3547
+ }
3548
+ /**
3549
+ * A track list's parts: a bracketed group of line names as one part, a
3550
+ * quoted string as one, and a size or a function as one — a group of two
3551
+ * names was two parts, and the second was no size. Undefined where a
3552
+ * bracket or a quote is not closed.
3553
+ */
3554
+ function trackParts(value) {
3555
+ const out = [];
3556
+ const text = value.trim();
3557
+ let i = 0;
3558
+ while (i < text.length) {
3559
+ const ch = text[i];
3560
+ if (/\s/.test(ch)) {
3561
+ i += 1;
3562
+ continue;
3563
+ }
3564
+ const close = ch === '[' ? ']' : ch === '"' || ch === "'" ? ch : null;
3565
+ if (close) {
3566
+ const end = text.indexOf(close, i + 1);
3567
+ if (end < 0)
3568
+ return undefined;
3569
+ out.push(text.slice(i, end + 1));
3570
+ i = end + 1;
3571
+ continue;
3572
+ }
3573
+ let depth = 0;
3574
+ let j = i;
3575
+ for (; j < text.length; j += 1) {
3576
+ const c = text[j];
3577
+ if (c === '(')
3578
+ depth += 1;
3579
+ else if (c === ')')
3580
+ depth -= 1;
3581
+ else if (depth === 0 && /[\s["']/.test(c))
3582
+ break;
3583
+ }
3584
+ out.push(text.slice(i, j));
3585
+ i = j;
3586
+ }
3587
+ return out;
3588
+ }
3589
+ /** The names in a bracketed group of line names, or undefined for a part
3590
+ * that is not one or names what no line may be called. */
3591
+ function lineNames(part) {
3592
+ if (!part.startsWith('[') || !part.endsWith(']'))
3593
+ return undefined;
3594
+ const names = part.slice(1, -1).trim().split(/\s+/).filter(Boolean);
3595
+ return names.every(isLineName) ? names : undefined;
3596
+ }
3597
+ /** A `<custom-ident>` a grid line may take: not `span` nor `auto`. */
3598
+ function isLineName(word) {
3599
+ return (/^-?[a-zA-Z_\u0080-\uffff][\w\u0080-\uffff-]*$/.test(word) &&
3600
+ !/^(span|auto|inherit|initial|unset|default)$/i.test(word));
3601
+ }
3602
+ /**
3603
+ * A track list (CSS Grid 1, 7.2): lengths, `fr`s, `auto`, `minmax()`,
3604
+ * `fit-content()`, `repeat()` with a count or with `auto-fill`/`auto-fit`,
3605
+ * whose count the width decides, and the names of the lines between them.
3606
+ * Undefined for a list that is none.
3607
+ */
3608
+ function parseGridTemplate(value, ctx) {
3609
+ const out = { tracks: [], names: [[]], repeat: null };
3610
+ // where a group of names goes: the line after the last track, or the
3611
+ // line after an auto repeat until a track follows it
3612
+ let pending = null;
3613
+ const read = (parts, into, names, top) => {
3614
+ for (const part of parts) {
3615
+ if (part.startsWith('[')) {
3616
+ const group = lineNames(part);
3617
+ if (!group)
3618
+ return false;
3619
+ (top && pending ? pending : names[names.length - 1]).push(...group);
3620
+ continue;
3621
+ }
3622
+ const repeat = /^repeat\((.*)\)$/is.exec(part);
3623
+ if (repeat) {
3624
+ const [count, ...rest] = splitCommas(repeat[1]);
3625
+ const inner = [];
3626
+ const innerNames = [[]];
3627
+ const innerParts = trackParts(rest.join(','));
3628
+ if (!innerParts ||
3629
+ !read(innerParts, inner, innerNames, false) ||
3630
+ !inner.length) {
3631
+ return false;
3632
+ }
3633
+ const n = count.trim().toLowerCase();
3634
+ if (n === 'auto-fill' || n === 'auto-fit') {
3635
+ if (out.repeat || !top)
3636
+ return false;
3637
+ out.repeat = {
3638
+ at: into.length,
3639
+ tracks: inner,
3640
+ names: innerNames,
3641
+ after: [],
3642
+ fit: n === 'auto-fit',
3643
+ };
3644
+ pending = out.repeat.after;
3645
+ continue;
3646
+ }
3647
+ const times = Number(n);
3648
+ if (!Number.isInteger(times) || times < 1 || times > 1000)
3649
+ return false;
3650
+ for (let i = 0; i < times; i += 1) {
3651
+ names[names.length - 1].push(...innerNames[0]);
3652
+ for (let t = 0; t < inner.length; t += 1) {
3653
+ into.push(inner[t]);
3654
+ names.push([...innerNames[t + 1]]);
3655
+ }
3656
+ }
3657
+ if (top)
3658
+ pending = null;
3659
+ continue;
3660
+ }
3661
+ const track = parseGridTrack(part, ctx);
3662
+ if (!track)
3663
+ return false;
3664
+ into.push(track);
3665
+ names.push([]);
3666
+ if (top)
3667
+ pending = null;
3668
+ }
3669
+ return true;
3670
+ };
3671
+ const parts = trackParts(value);
3672
+ if (!parts || !read(parts, out.tracks, out.names, true))
3673
+ return undefined;
3674
+ if (!out.tracks.length && !out.repeat)
3675
+ return undefined;
3676
+ return out;
3677
+ }
3678
+ /**
3679
+ * `grid-template-areas`' strings (CSS Grid 1, 7.3): a row each, a cell to a
3680
+ * word or a run of dots, every row as wide as the first and every name a
3681
+ * rectangle. Undefined where they are not.
3682
+ */
3683
+ function parseAreas(strings) {
3684
+ const grid = [];
3685
+ for (const quoted of strings) {
3686
+ const cells = [];
3687
+ const text = quoted.slice(1, -1);
3688
+ const re = /\s*(?:(\.+)|([\w\u0080-\uffff-]+)|(\S))/g;
3689
+ let m;
3690
+ while ((m = re.exec(text)) && m[0].trim()) {
3691
+ if (m[3])
3692
+ return undefined;
3693
+ cells.push(m[1] ? null : m[2]);
3694
+ }
3695
+ if (!cells.length)
3696
+ return undefined;
3697
+ if (grid.length && cells.length !== grid[0].length)
3698
+ return undefined;
3699
+ grid.push(cells);
3700
+ }
3701
+ const areas = new Map();
3702
+ grid.forEach((cells, r) => cells.forEach((name, c) => {
3703
+ if (name === null)
3704
+ return;
3705
+ const area = areas.get(name);
3706
+ if (!area)
3707
+ areas.set(name, { rows: [r, r + 1], columns: [c, c + 1] });
3708
+ else {
3709
+ area.rows[1] = Math.max(area.rows[1], r + 1);
3710
+ area.columns[1] = Math.max(area.columns[1], c + 1);
3711
+ }
3712
+ }));
3713
+ // each name fills the rectangle its first and last cells make, and
3714
+ // nothing else
3715
+ let cellsNamed = 0;
3716
+ for (const cells of grid)
3717
+ for (const name of cells)
3718
+ if (name)
3719
+ cellsNamed += 1;
3720
+ let rectangles = 0;
3721
+ for (const [name, { rows, columns }] of areas) {
3722
+ for (let r = rows[0]; r < rows[1]; r += 1) {
3723
+ for (let c = columns[0]; c < columns[1]; c += 1) {
3724
+ if (grid[r][c] !== name)
3725
+ return undefined;
3726
+ }
3727
+ }
3728
+ rectangles += (rows[1] - rows[0]) * (columns[1] - columns[0]);
3729
+ }
3730
+ if (rectangles !== cellsNamed)
3731
+ return undefined;
3732
+ return { rows: grid.length, columns: grid[0].length, areas };
3733
+ }
3734
+ /**
3735
+ * `grid-template`'s rows and columns either side of a slash (7.4): the
3736
+ * rows as track list, or as the strings of `grid-template-areas`, each
3737
+ * string's row the size after it or `auto`, with the names of the lines
3738
+ * before and after it. Undefined for no value.
3739
+ */
3740
+ function parseTemplateShorthand(value, ctx) {
3741
+ const parts = splitTopLevelSlash(value);
3742
+ if (parts.length !== 2)
3743
+ return undefined;
3744
+ const columnsNone = parts[1].trim().toLowerCase() === 'none';
3745
+ const columns = columnsNone ? null : parseGridTemplate(parts[1], ctx);
3746
+ if (columns === undefined)
3747
+ return undefined;
3748
+ if (!/["']/.test(parts[0])) {
3749
+ const rows = parts[0].trim().toLowerCase() === 'none'
3750
+ ? null
3751
+ : parseGridTemplate(parts[0], ctx);
3752
+ return rows === undefined ? undefined : { rows, columns, areas: null };
3753
+ }
3754
+ // the strings' form takes no `repeat()` for its columns
3755
+ if (columns?.repeat)
3756
+ return undefined;
3757
+ const pieces = trackParts(parts[0]);
3758
+ if (!pieces)
3759
+ return undefined;
3760
+ const tracks = [];
3761
+ const names = [[]];
3762
+ const strings = [];
3763
+ // after a string, and before its size, if it has one
3764
+ let open = false;
3765
+ for (const piece of pieces) {
3766
+ if (piece.startsWith('[')) {
3767
+ const group = lineNames(piece);
3768
+ if (!group)
3769
+ return undefined;
3770
+ if (open) {
3771
+ tracks.push(AUTO_TRACK);
3772
+ names.push([]);
3773
+ open = false;
3774
+ }
3775
+ names[names.length - 1].push(...group);
3776
+ continue;
3777
+ }
3778
+ if (/^["']/.test(piece)) {
3779
+ if (open) {
3780
+ tracks.push(AUTO_TRACK);
3781
+ names.push([]);
3782
+ }
3783
+ strings.push(piece);
3784
+ open = true;
3785
+ continue;
3786
+ }
3787
+ if (!open)
3788
+ return undefined;
3789
+ const track = parseGridTrack(piece, ctx);
3790
+ if (!track)
3791
+ return undefined;
3792
+ tracks.push(track);
3793
+ names.push([]);
3794
+ open = false;
3795
+ }
3796
+ if (open) {
3797
+ tracks.push(AUTO_TRACK);
3798
+ names.push([]);
3799
+ }
3800
+ const areas = parseAreas(strings);
3801
+ if (!areas)
3802
+ return undefined;
3803
+ if (columns && columns.tracks.length !== areas.columns)
3804
+ return undefined;
3805
+ return { rows: { tracks, names, repeat: null }, columns, areas };
3806
+ }
3807
+ /**
3808
+ * A grid line (CSS Grid 1, 8.3): `auto`; a number; a name; a number and a
3809
+ * name, in either order; or `span` with a number, a name or both.
3810
+ * Undefined for a value that is none of them.
3811
+ */
3812
+ function parseGridLine(value) {
3813
+ if (value === undefined)
3814
+ return null;
3815
+ const words = value.trim().split(/\s+/).filter(Boolean);
3816
+ if (words.length === 1 && words[0].toLowerCase() === 'auto')
3817
+ return null;
3818
+ let span = false;
3819
+ let count = null;
3820
+ let name = null;
3821
+ for (const word of words) {
3822
+ if (word.toLowerCase() === 'span') {
3823
+ if (span)
3824
+ return undefined;
3825
+ span = true;
3826
+ }
3827
+ else if (/^[+-]?\d+$/.test(word)) {
3828
+ if (count !== null)
3829
+ return undefined;
3830
+ count = Number(word);
3831
+ }
3832
+ else if (isLineName(word)) {
3833
+ if (name !== null)
3834
+ return undefined;
3835
+ name = word;
3836
+ }
3837
+ else
3838
+ return undefined;
3839
+ }
3840
+ // `span` goes first or last, never between a number and a name
3841
+ if (span && words.length === 3 && words[1].toLowerCase() === 'span') {
3842
+ return undefined;
3843
+ }
3844
+ if (span) {
3845
+ if (count !== null && count < 1)
3846
+ return undefined;
3847
+ const out = { span: count ?? 1 };
3848
+ if (name !== null)
3849
+ out.name = name;
3850
+ return out;
3851
+ }
3852
+ if (count !== null) {
3853
+ if (count === 0)
3854
+ return undefined;
3855
+ return name === null ? { line: count } : { line: count, name };
3856
+ }
3857
+ return name === null ? undefined : { name };
3858
+ }
3859
+ /** A grid line that is a name alone, which a shorthand copies to the
3860
+ * line it leaves out; auto for any other. */
3861
+ function named(line) {
3862
+ return line && 'name' in line && !('line' in line) && !('span' in line)
3863
+ ? line
3864
+ : null;
3865
+ }
3866
+ /** A value's parts either side of a top-level `/`. */
3867
+ function splitTopLevelSlash(value) {
3868
+ const out = [];
3869
+ let depth = 0;
3870
+ let start = 0;
3871
+ for (let i = 0; i < value.length; i += 1) {
3872
+ const c = value[i];
3873
+ if (c === '(')
3874
+ depth += 1;
3875
+ else if (c === ')')
3876
+ depth -= 1;
3877
+ else if (c === '/' && depth === 0) {
3878
+ out.push(value.slice(start, i).trim());
3879
+ start = i + 1;
3880
+ }
3881
+ }
3882
+ out.push(value.slice(start).trim());
3883
+ return out;
3884
+ }
3885
+ // --- logical properties ----------------------------------------------------
3886
+ const OBJECT_FITS = new Set(['fill', 'contain', 'cover', 'none', 'scale-down']);
3887
+ /**
3888
+ * `aspect-ratio` (CSS Sizing 4, 5.1): `auto`, a ratio — one number, or
3889
+ * two with a `/` between — or both, in either order. Undefined where it is
3890
+ * none of that, and the declaration is dropped; a ratio with a nought in it
3891
+ * is none, and `auto` is what is left.
3892
+ */
3893
+ function parseAspectRatio(value) {
3894
+ let auto = false;
3895
+ let rest = value.trim().toLowerCase();
3896
+ const word = /(?:^auto\s+|\s+auto$|^auto$)/.exec(rest);
3897
+ if (word) {
3898
+ auto = true;
3899
+ rest = rest.replace(word[0], '').trim();
3900
+ if (!rest)
3901
+ return null;
3902
+ }
3903
+ const m = /^(\d*\.?\d+(?:e[+-]?\d+)?)(?:\s*\/\s*(\d*\.?\d+(?:e[+-]?\d+)?))?$/.exec(rest);
3904
+ if (!m)
3905
+ return undefined;
3906
+ const w = Number(m[1]);
3907
+ const h = m[2] === undefined ? 1 : Number(m[2]);
3908
+ if (!(w > 0 && h > 0))
3909
+ return null;
3910
+ return { ratio: w / h, auto };
3911
+ }
3912
+ /**
3913
+ * A `box-shadow` (CSS Backgrounds 3, 7.1): `none`, or shadows front to
3914
+ * back, each two offsets and then a blur and a spread, with a colour and
3915
+ * `inset` on either side of the lengths. Undefined where the value is none
3916
+ * of that, and the declaration is dropped. A shadow no colour can be seen
3917
+ * in is left out — Tailwind writes four of them, `0 0 #0000`, under every
3918
+ * one it means.
3919
+ */
3920
+ /** `box-shadow`'s shadows, or with `text` `text-shadow`'s, which have no
3921
+ * spread and are never inset (CSS Text Decoration 3, 4): null for none,
3922
+ * and undefined for a value that is not one. */
3923
+ function parseBoxShadow(value, ctx, text = false) {
3924
+ if (value.trim().toLowerCase() === 'none')
3925
+ return null;
3926
+ const out = [];
3927
+ for (const part of splitCommas(value)) {
3928
+ let inset = false;
3929
+ let color = null;
3930
+ const lengths = [];
3931
+ // the lengths are written together, with nothing between them
3932
+ let closed = false;
3933
+ for (const token of splitValue(part.trim())) {
3934
+ if (token.toLowerCase() === 'inset') {
3935
+ if (inset || text)
3936
+ return undefined;
3937
+ inset = true;
3938
+ closed = lengths.length > 0;
3939
+ continue;
3940
+ }
3941
+ const len = parseLength(token, ctx);
3942
+ if (typeof len === 'number') {
3943
+ if (closed || lengths.length === (text ? 3 : 4))
3944
+ return undefined;
3945
+ lengths.push(len);
3946
+ continue;
3947
+ }
3948
+ const c = parseColor(token);
3949
+ if (c === null || color !== null)
3950
+ return undefined;
3951
+ color = c;
3952
+ closed = lengths.length > 0;
3953
+ }
3954
+ if (lengths.length < 2)
3955
+ return undefined;
3956
+ const [x, y, blur = 0, spread = 0] = lengths;
3957
+ if (blur < 0)
3958
+ return undefined;
3959
+ const ink = color ?? 'currentColor';
3960
+ if (alphaOf(ink) === 0 || isTransparent(ink))
3961
+ continue;
3962
+ out.push({ x, y, blur, spread, color: ink, inset });
3963
+ }
3964
+ return out.length ? out : null;
3965
+ }
3966
+ /** A corner's radius: a length or a percentage, and neither negative nor
3967
+ * `auto`; null where it is none. */
3968
+ function radiusOf(text, ctx) {
3969
+ const len = parseLength(text, ctx);
3970
+ if (len === null || len === AUTO)
3971
+ return null;
3972
+ if (typeof len === 'number')
3973
+ return len < 0 ? null : len;
3974
+ return len.pct < 0 && !len.px && !len.of ? null : len;
3975
+ }
3976
+ /** Whether two corners' radii are the same values. */
3977
+ function sameRadii(a, b) {
3978
+ for (let i = 0; i < 4; i += 1) {
3979
+ const x = a[i];
3980
+ const y = b[i];
3981
+ if (x === y)
3982
+ continue;
3983
+ if (typeof x !== 'object' || typeof y !== 'object')
3984
+ return false;
3985
+ if (x.pct !== y.pct || x.px !== y.px || x.of || y.of)
3986
+ return false;
3987
+ }
3988
+ return true;
3989
+ }
3990
+ /** The four corners, in `border-radius`'s order. */
3991
+ const CORNERS = [
3992
+ 'border-top-left-radius',
3993
+ 'border-top-right-radius',
3994
+ 'border-bottom-right-radius',
3995
+ 'border-bottom-left-radius',
3996
+ ];
3997
+ /**
3998
+ * CSS Logical Properties 1, in the horizontal writing mode `<Html>` lays
3999
+ * out: the inline axis is the line's, its start the left of a
4000
+ * left-to-right element and the right of a right-to-left one, and the block
4001
+ * axis runs down. Tailwind 4 writes its spacing in them — `px-4` is
4002
+ * `padding-inline`, `mx-auto` is `margin-inline: auto` — and every modern
4003
+ * reset writes some.
4004
+ */
4005
+ const LOGICAL = (() => {
4006
+ const out = {};
4007
+ const length = (part, ctx) => parseLength(part, ctx) !== null;
4008
+ const groups = [
4009
+ ['margin', (side) => `margin-${side}`, length],
4010
+ [
4011
+ 'padding',
4012
+ (side) => `padding-${side}`,
4013
+ (part, ctx) => validPadding(parseLength(part, ctx)),
4014
+ ],
4015
+ ['inset', (side) => side, length],
4016
+ [
4017
+ 'border-*-width',
4018
+ (side) => `border-${side}-width`,
4019
+ (part, ctx) => borderWidth(part, ctx) !== null,
4020
+ ],
4021
+ [
4022
+ 'border-*-style',
4023
+ (side) => `border-${side}-style`,
4024
+ (part) => BORDER_STYLES.has(part.toLowerCase()),
4025
+ ],
4026
+ [
4027
+ 'border-*-color',
4028
+ (side) => `border-${side}-color`,
4029
+ (part) => parseColor(part) !== null,
4030
+ ],
4031
+ ];
4032
+ for (const [group, physical, valid] of groups) {
4033
+ for (const axis of ['inline', 'block']) {
4034
+ const name = group.includes('*')
4035
+ ? group.replace('*', axis)
4036
+ : `${group}-${axis}`;
4037
+ out[name] = { kind: 'axis', axis, same: false, physical, valid };
4038
+ for (const end of ['start', 'end']) {
4039
+ const side = `${axis}-${end}`;
4040
+ const longhand = group.includes('*')
4041
+ ? group.replace('*', side)
4042
+ : `${group}-${side}`;
4043
+ out[longhand] = { kind: 'side', side, physical };
4044
+ }
4045
+ }
4046
+ }
4047
+ for (const axis of ['inline', 'block']) {
4048
+ out[`border-${axis}`] = {
4049
+ kind: 'axis',
4050
+ axis,
4051
+ same: true,
4052
+ physical: (side) => `border-${side}`,
4053
+ valid: () => true,
4054
+ };
4055
+ for (const end of ['start', 'end']) {
4056
+ out[`border-${axis}-${end}`] = {
4057
+ kind: 'side',
4058
+ side: `${axis}-${end}`,
4059
+ physical: (side) => `border-${side}`,
4060
+ };
4061
+ }
4062
+ }
4063
+ const sizes = [
4064
+ ['inline-size', 'width'],
4065
+ ['block-size', 'height'],
4066
+ ['min-inline-size', 'min-width'],
4067
+ ['min-block-size', 'min-height'],
4068
+ ['max-inline-size', 'max-width'],
4069
+ ['max-block-size', 'max-height'],
4070
+ ];
4071
+ for (const [name, physical] of sizes)
4072
+ out[name] = { kind: 'size', physical };
4073
+ for (const block of ['start', 'end']) {
4074
+ for (const inline of ['start', 'end']) {
4075
+ out[`border-${block}-${inline}-radius`] = {
4076
+ kind: 'corner',
4077
+ block,
4078
+ inline,
4079
+ };
4080
+ }
4081
+ }
4082
+ return out;
4083
+ })();
4084
+ /** The physical side a logical one is, by the element's direction. */
4085
+ function physicalSide(side, rtl) {
4086
+ switch (side) {
4087
+ case 'inline-start':
4088
+ return rtl ? 'right' : 'left';
4089
+ case 'inline-end':
4090
+ return rtl ? 'left' : 'right';
4091
+ case 'block-start':
4092
+ return 'top';
4093
+ default:
4094
+ return 'bottom';
4095
+ }
4096
+ }
4097
+ /**
4098
+ * A logical declaration, as the physical ones it stands for. The direction
4099
+ * is the element's as far as the cascade has got: its parent's, or one it
4100
+ * set itself earlier in cascade order, which is where HTML's `dir` puts it.
4101
+ * A two-value shorthand with a part that is no value for its property is
4102
+ * none, and the declaration goes, as a physical shorthand's does.
4103
+ */
4104
+ function applyLogical(style, parent, logical, value, ctx) {
4105
+ const rtl = style.direction === 'rtl';
4106
+ switch (logical.kind) {
4107
+ case 'side':
4108
+ applyDeclaration(style, parent, logical.physical(physicalSide(logical.side, rtl)), value, ctx);
4109
+ return;
4110
+ case 'size':
4111
+ applyDeclaration(style, parent, logical.physical, value, ctx);
4112
+ return;
4113
+ case 'corner': {
4114
+ const vertical = logical.block === 'start' ? 'top' : 'bottom';
4115
+ const left = (logical.inline === 'start') !== rtl;
4116
+ applyDeclaration(style, parent, `border-${vertical}-${left ? 'left' : 'right'}-radius`, value, ctx);
4117
+ return;
4118
+ }
4119
+ default: {
4120
+ const start = physicalSide(`${logical.axis}-start`, rtl);
4121
+ const end = physicalSide(`${logical.axis}-end`, rtl);
4122
+ let parts;
4123
+ if (logical.same || CSS_WIDE.has(value.toLowerCase())) {
4124
+ parts = [value, value];
4125
+ }
4126
+ else {
4127
+ parts = splitValue(value);
4128
+ if (parts.length === 1)
4129
+ parts.push(parts[0]);
4130
+ if (parts.length !== 2 || !parts.every((p) => logical.valid(p, ctx)))
4131
+ return;
4132
+ }
4133
+ applyDeclaration(style, parent, logical.physical(start), parts[0], ctx);
4134
+ applyDeclaration(style, parent, logical.physical(end), parts[1], ctx);
4135
+ }
4136
+ }
4137
+ }
4138
+ const CSS_WIDE = new Set(['inherit', 'initial', 'unset', 'revert']);
1045
4139
  function camel(name) {
1046
4140
  return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1047
4141
  }
1048
4142
  const INHERITED_NAMES = new Set([
1049
4143
  'color',
4144
+ '-webkit-text-fill-color',
1050
4145
  'font',
1051
4146
  'font-family',
1052
4147
  'font-size',
@@ -1054,60 +4149,448 @@ const INHERITED_NAMES = new Set([
1054
4149
  'font-style',
1055
4150
  'line-height',
1056
4151
  'text-align',
4152
+ 'text-align-last',
4153
+ 'text-justify',
4154
+ 'block-ellipsis',
1057
4155
  'text-indent',
1058
4156
  'text-transform',
1059
4157
  'letter-spacing',
1060
4158
  'word-spacing',
1061
4159
  'white-space',
4160
+ 'overflow-wrap',
4161
+ 'word-wrap',
4162
+ 'word-break',
4163
+ 'line-break',
4164
+ 'white-space-collapse',
4165
+ 'text-wrap',
4166
+ 'text-wrap-mode',
4167
+ 'text-wrap-style',
1062
4168
  'direction',
1063
4169
  'visibility',
1064
4170
  'list-style',
1065
4171
  'list-style-type',
1066
4172
  'list-style-position',
4173
+ 'list-style-image',
1067
4174
  'cursor',
1068
4175
  'border-collapse',
1069
4176
  'border-spacing',
4177
+ 'empty-cells',
4178
+ 'quotes',
1070
4179
  ]);
1071
- function isInherited(name) {
4180
+ export function isInherited(name) {
1072
4181
  return INHERITED_NAMES.has(name);
1073
4182
  }
1074
4183
  /** `prop: inherit` — take the parent's computed value for whatever longhands
1075
4184
  * the property names. Shorthands copy each of their longhands. */
4185
+ /** A property back to its initial value: what a declaration whose `var()`
4186
+ * has no value comes to, where the property is not inherited. */
4187
+ export function initialOne(style, initial, name) {
4188
+ const keys = INHERIT_TARGETS[name];
4189
+ if (!keys)
4190
+ return;
4191
+ for (const key of keys) {
4192
+ style[key] = initial[key];
4193
+ }
4194
+ }
1076
4195
  function inheritOne(style, parent, name) {
1077
4196
  const keys = INHERIT_TARGETS[name];
1078
4197
  if (!keys)
1079
4198
  return;
4199
+ // A border colour left to `currentColor` inherits as the keyword and
4200
+ // takes the child's own colour (CSS Color 4) — not the parent's colour,
4201
+ // which is what CSS 2.1 computed it to.
1080
4202
  for (const key of keys) {
1081
4203
  style[key] = parent[key];
1082
4204
  }
1083
4205
  }
4206
+ const SIDES = ['Top', 'Right', 'Bottom', 'Left'];
4207
+ const sides = (make) => SIDES.map(make);
1084
4208
  const INHERIT_TARGETS = {
1085
4209
  color: ['color'],
4210
+ '-webkit-text-fill-color': ['textFillColor'],
4211
+ 'background-clip': [
4212
+ 'backgroundClipText',
4213
+ 'backgroundClip',
4214
+ 'backgroundClips',
4215
+ ],
4216
+ '-webkit-background-clip': [
4217
+ 'backgroundClipText',
4218
+ 'backgroundClip',
4219
+ 'backgroundClips',
4220
+ ],
4221
+ 'background-origin': ['backgroundOrigin', 'backgroundOrigins'],
4222
+ 'border-image': ['borderImage'],
4223
+ 'border-image-source': ['borderImage'],
4224
+ 'border-image-slice': ['borderImage'],
4225
+ 'border-image-width': ['borderImage'],
4226
+ 'border-image-outset': ['borderImage'],
4227
+ 'border-image-repeat': ['borderImage'],
1086
4228
  'font-family': ['fontFamily'],
1087
4229
  'font-size': ['fontSize'],
1088
4230
  'font-weight': ['fontWeight'],
1089
4231
  'font-style': ['fontStyle'],
1090
- font: ['fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'lineHeight'],
4232
+ // the unit travels with the height: a length inherited as a bare number
4233
+ // would be read as a multiple of the font size
4234
+ font: [
4235
+ 'fontFamily',
4236
+ 'fontSize',
4237
+ 'fontWeight',
4238
+ 'fontStyle',
4239
+ 'lineHeight',
4240
+ 'lineHeightIsLength',
4241
+ 'fontVariantNumeric',
4242
+ 'fontVariantCaps',
4243
+ 'fontVariantLigatures',
4244
+ 'fontVariantPosition',
4245
+ 'fontKerning',
4246
+ ],
1091
4247
  'line-height': ['lineHeight', 'lineHeightIsLength'],
1092
- 'text-align': ['textAlign'],
4248
+ 'text-align': ['textAlign', 'alignBlocks'],
4249
+ 'text-align-last': ['textAlignLast'],
4250
+ 'text-justify': ['textJustify'],
1093
4251
  'text-indent': ['textIndent'],
1094
4252
  'text-transform': ['textTransform'],
1095
4253
  'letter-spacing': ['letterSpacing'],
1096
4254
  'word-spacing': ['wordSpacing'],
4255
+ 'font-variant': [
4256
+ 'fontVariantNumeric',
4257
+ 'fontVariantCaps',
4258
+ 'fontVariantLigatures',
4259
+ 'fontVariantPosition',
4260
+ ],
4261
+ 'font-variant-numeric': ['fontVariantNumeric'],
4262
+ 'font-variant-caps': ['fontVariantCaps'],
4263
+ 'font-variant-ligatures': ['fontVariantLigatures'],
4264
+ 'font-variant-position': ['fontVariantPosition'],
4265
+ 'font-kerning': ['fontKerning'],
4266
+ 'font-feature-settings': ['fontFeatureSettings'],
4267
+ 'tab-size': ['tabSize', 'tabSizeIsLength'],
4268
+ '-moz-tab-size': ['tabSize', 'tabSizeIsLength'],
1097
4269
  'white-space': ['whiteSpace'],
4270
+ 'overflow-wrap': ['overflowWrap'],
4271
+ 'word-wrap': ['overflowWrap'],
4272
+ 'word-break': ['wordBreak'],
4273
+ 'line-break': ['lineBreakAnywhere'],
4274
+ 'white-space-collapse': ['whiteSpace'],
4275
+ 'text-wrap': ['whiteSpace', 'textWrapStyle'],
4276
+ 'text-wrap-mode': ['whiteSpace'],
4277
+ 'text-wrap-style': ['textWrapStyle'],
1098
4278
  direction: ['direction'],
4279
+ 'unicode-bidi': ['unicodeBidi'],
1099
4280
  visibility: ['visibility'],
1100
- 'list-style': ['listStyleType', 'listStylePosition'],
4281
+ 'list-style': ['listStyleType', 'listStylePosition', 'listStyleImage'],
1101
4282
  'list-style-type': ['listStyleType'],
1102
4283
  'list-style-position': ['listStylePosition'],
4284
+ 'list-style-image': ['listStyleImage'],
1103
4285
  cursor: ['cursor'],
1104
4286
  'border-collapse': ['borderCollapse'],
1105
- 'border-spacing': ['borderSpacing'],
1106
- display: ['display'],
1107
- width: ['width'],
1108
- height: ['height'],
4287
+ 'caption-side': ['captionSide'],
4288
+ 'empty-cells': ['emptyCells'],
4289
+ quotes: ['quotes'],
4290
+ content: ['content'],
4291
+ 'counter-reset': ['counterReset'],
4292
+ 'counter-increment': ['counterIncrement'],
4293
+ 'counter-set': ['counterSet'],
4294
+ 'border-spacing': ['borderSpacing', 'borderSpacingY'],
4295
+ display: ['display', 'flowRoot', 'webkitBox', 'grid'],
4296
+ width: ['width', 'widthKeyword'],
4297
+ height: ['height', 'heightKeyword'],
4298
+ 'min-width': ['minWidth', 'minWidthKeyword'],
4299
+ 'max-width': ['maxWidth', 'maxWidthKeyword'],
4300
+ 'min-height': ['minHeight', 'minHeightKeyword'],
4301
+ 'max-height': ['maxHeight', 'maxHeightKeyword'],
4302
+ 'box-sizing': ['boxSizing'],
4303
+ margin: sides((s) => `margin${s}`),
4304
+ 'margin-top': ['marginTop'],
4305
+ 'margin-right': ['marginRight'],
4306
+ 'margin-bottom': ['marginBottom'],
4307
+ 'margin-left': ['marginLeft'],
4308
+ padding: sides((s) => `padding${s}`),
4309
+ 'padding-top': ['paddingTop'],
4310
+ 'padding-right': ['paddingRight'],
4311
+ 'padding-bottom': ['paddingBottom'],
4312
+ 'padding-left': ['paddingLeft'],
4313
+ border: [
4314
+ ...sides((s) => `border${s}Width`),
4315
+ ...sides((s) => `border${s}Style`),
4316
+ ...sides((s) => `border${s}Color`),
4317
+ ],
4318
+ 'border-width': sides((s) => `border${s}Width`),
4319
+ 'border-style': sides((s) => `border${s}Style`),
4320
+ 'border-color': sides((s) => `border${s}Color`),
4321
+ ...Object.fromEntries(SIDES.flatMap((s) => {
4322
+ const side = s.toLowerCase();
4323
+ return [
4324
+ [
4325
+ `border-${side}`,
4326
+ [`border${s}Width`, `border${s}Style`, `border${s}Color`],
4327
+ ],
4328
+ [`border-${side}-width`, [`border${s}Width`]],
4329
+ [`border-${side}-style`, [`border${s}Style`]],
4330
+ [`border-${side}-color`, [`border${s}Color`]],
4331
+ ];
4332
+ })),
4333
+ 'box-shadow': ['boxShadow'],
4334
+ outline: ['outlineStyle', 'outlineWidth', 'outlineColor'],
4335
+ 'outline-style': ['outlineStyle'],
4336
+ 'outline-width': ['outlineWidth'],
4337
+ 'outline-color': ['outlineColor'],
4338
+ 'outline-offset': ['outlineOffset'],
4339
+ 'text-shadow': ['textShadow'],
4340
+ 'line-clamp': ['maxLines', 'clampContinue', 'blockEllipsis'],
4341
+ '-webkit-line-clamp': ['maxLines', 'clampContinue', 'blockEllipsis'],
4342
+ 'max-lines': ['maxLines'],
4343
+ continue: ['clampContinue'],
4344
+ 'block-ellipsis': ['blockEllipsis'],
4345
+ '-webkit-box-orient': ['webkitBoxOrient'],
4346
+ '-webkit-box-direction': ['webkitBoxDirection'],
4347
+ '-webkit-box-pack': ['webkitBoxPack'],
4348
+ '-webkit-box-align': ['webkitBoxAlign'],
4349
+ '-webkit-box-flex': ['webkitBoxFlex'],
4350
+ columns: ['columns'],
4351
+ 'column-count': ['columns'],
4352
+ 'column-width': ['columns'],
4353
+ 'text-overflow': ['textOverflow'],
4354
+ // a flex box's and its items', and a grid's
4355
+ 'flex-direction': ['flexDirection'],
4356
+ 'flex-wrap': ['flexWrap'],
4357
+ 'flex-flow': ['flexDirection', 'flexWrap'],
4358
+ 'justify-content': ['justifyContent'],
4359
+ 'align-items': ['alignItems'],
4360
+ 'align-self': ['alignSelf'],
4361
+ 'align-content': ['alignContent'],
4362
+ flex: ['flexGrow', 'flexShrink', 'flexBasis'],
4363
+ 'flex-grow': ['flexGrow'],
4364
+ 'flex-shrink': ['flexShrink'],
4365
+ 'flex-basis': ['flexBasis'],
4366
+ order: ['order'],
4367
+ gap: ['rowGap', 'columnGap'],
4368
+ 'row-gap': ['rowGap'],
4369
+ 'column-gap': ['columnGap'],
4370
+ 'grid-gap': ['rowGap', 'columnGap'],
4371
+ 'grid-row-gap': ['rowGap'],
4372
+ 'grid-column-gap': ['columnGap'],
4373
+ 'grid-template-columns': ['gridColumns'],
4374
+ 'grid-template-rows': ['gridRows'],
4375
+ 'grid-auto-rows': ['gridAutoRows'],
4376
+ 'grid-auto-columns': ['gridAutoColumns'],
4377
+ 'grid-template-areas': ['gridAreas'],
4378
+ 'grid-auto-flow': ['gridAutoFlow'],
4379
+ 'grid-template': ['gridRows', 'gridColumns', 'gridAreas'],
4380
+ grid: [
4381
+ 'gridRows',
4382
+ 'gridColumns',
4383
+ 'gridAreas',
4384
+ 'gridAutoRows',
4385
+ 'gridAutoColumns',
4386
+ 'gridAutoFlow',
4387
+ ],
4388
+ 'grid-area': [
4389
+ 'gridRowStart',
4390
+ 'gridColumnStart',
4391
+ 'gridRowEnd',
4392
+ 'gridColumnEnd',
4393
+ ],
4394
+ 'grid-row': ['gridRowStart', 'gridRowEnd'],
4395
+ 'grid-row-start': ['gridRowStart'],
4396
+ 'grid-row-end': ['gridRowEnd'],
4397
+ 'grid-column': ['gridColumnStart', 'gridColumnEnd'],
4398
+ 'grid-column-start': ['gridColumnStart'],
4399
+ 'grid-column-end': ['gridColumnEnd'],
4400
+ 'justify-items': ['justifyItems'],
4401
+ 'justify-self': ['justifySelf'],
4402
+ 'place-items': ['alignItems', 'justifyItems'],
4403
+ 'place-self': ['alignSelf', 'justifySelf'],
4404
+ 'border-radius': ['borderRadius', 'borderRadiusY'],
4405
+ 'border-top-left-radius': ['borderRadius', 'borderRadiusY'],
4406
+ 'border-top-right-radius': ['borderRadius', 'borderRadiusY'],
4407
+ 'border-bottom-right-radius': ['borderRadius', 'borderRadiusY'],
4408
+ 'border-bottom-left-radius': ['borderRadius', 'borderRadiusY'],
4409
+ inset: ['top', 'right', 'bottom', 'left'],
4410
+ background: [
4411
+ 'backgroundColor',
4412
+ 'backgroundImage',
4413
+ 'backgroundGradient',
4414
+ 'backgroundRepeat',
4415
+ 'backgroundSize',
4416
+ 'backgroundAttachment',
4417
+ 'backgroundPositionX',
4418
+ 'backgroundPositionY',
4419
+ 'backgroundImages',
4420
+ 'backgroundRepeats',
4421
+ 'backgroundSizes',
4422
+ 'backgroundAttachments',
4423
+ 'backgroundPositions',
4424
+ 'backgroundClipText',
4425
+ 'backgroundClip',
4426
+ 'backgroundOrigin',
4427
+ 'backgroundClips',
4428
+ 'backgroundOrigins',
4429
+ ],
1109
4430
  'background-color': ['backgroundColor'],
4431
+ 'background-image': [
4432
+ 'backgroundImage',
4433
+ 'backgroundGradient',
4434
+ 'backgroundImages',
4435
+ ],
4436
+ 'background-repeat': ['backgroundRepeat', 'backgroundRepeats'],
4437
+ 'background-size': ['backgroundSize', 'backgroundSizes'],
4438
+ 'background-attachment': ['backgroundAttachment', 'backgroundAttachments'],
4439
+ 'background-position': [
4440
+ 'backgroundPositionX',
4441
+ 'backgroundPositionY',
4442
+ 'backgroundPositions',
4443
+ ],
4444
+ 'aspect-ratio': ['aspectRatio'],
4445
+ 'object-fit': ['objectFit'],
4446
+ appearance: ['appearance'],
4447
+ '-webkit-appearance': ['appearance'],
4448
+ '-moz-appearance': ['appearance'],
4449
+ contain: ['contain'],
4450
+ 'content-visibility': ['contentVisibility'],
4451
+ 'overflow-clip-margin': ['overflowClipBox', 'overflowClipMargin'],
4452
+ 'contain-intrinsic-size': ['containIntrinsicWidth', 'containIntrinsicHeight'],
4453
+ 'contain-intrinsic-width': ['containIntrinsicWidth'],
4454
+ 'contain-intrinsic-height': ['containIntrinsicHeight'],
4455
+ 'contain-intrinsic-inline-size': ['containIntrinsicWidth'],
4456
+ 'contain-intrinsic-block-size': ['containIntrinsicHeight'],
4457
+ 'object-position': ['objectPositionX', 'objectPositionY'],
4458
+ position: ['position'],
4459
+ top: ['top'],
4460
+ right: ['right'],
4461
+ bottom: ['bottom'],
4462
+ left: ['left'],
4463
+ float: ['float'],
4464
+ clear: ['clear'],
4465
+ overflow: ['overflowX', 'overflowY'],
4466
+ clip: ['clip'],
4467
+ 'overflow-x': ['overflowX'],
4468
+ 'overflow-y': ['overflowY'],
4469
+ opacity: ['opacity'],
4470
+ translate: ['translate'],
4471
+ transform: ['transformTranslate'],
4472
+ 'z-index': ['zIndex'],
4473
+ 'vertical-align': ['verticalAlign'],
4474
+ 'text-decoration': [
4475
+ 'textDecorationLine',
4476
+ 'textDecorationColor',
4477
+ 'textDecorationStyle',
4478
+ ],
4479
+ 'text-decoration-line': ['textDecorationLine'],
4480
+ 'text-decoration-style': ['textDecorationStyle'],
4481
+ 'text-decoration-thickness': ['textDecorationThickness'],
4482
+ 'text-underline-offset': ['textUnderlineOffset'],
4483
+ 'table-layout': ['tableLayout'],
1110
4484
  };
4485
+ /**
4486
+ * A decoration's thickness or an underline's offset: a length, a
4487
+ * percentage of the font size, or null for `auto` (and `alsoAuto`, which is
4488
+ * `from-font` for a thickness); undefined for a value that is none.
4489
+ */
4490
+ function decorationLength(value, ctx, alsoAuto) {
4491
+ const v = value.trim().toLowerCase();
4492
+ if (v === 'auto' || v === alsoAuto)
4493
+ return null;
4494
+ const pct = /^(-?\d*\.?\d+)%$/.exec(v);
4495
+ if (pct)
4496
+ return (Number(pct[1]) / 100) * ctx.em;
4497
+ const len = parseLength(v, ctx);
4498
+ return typeof len === 'number' ? len : undefined;
4499
+ }
4500
+ /**
4501
+ * The decorations a box's text is drawn with (CSS 2.1 16.3.1): those its
4502
+ * ancestors propagate to it, and its own, each in the colour of the box that
4503
+ * set it, so an underlined link's `<strong>` is underlined in the link's
4504
+ * colour. A float, an absolutely positioned box and an atomic inline box —
4505
+ * an inline block, table or flex box — take none from above, and `none`
4506
+ * takes none away. After `blockify`, whose `float` and `position` it reads.
4507
+ */
4508
+ export function decorate(style) {
4509
+ const display = style.display;
4510
+ if (style.float !== 'none' ||
4511
+ style.position === 'absolute' ||
4512
+ style.position === 'fixed' ||
4513
+ display === 'inline-block' ||
4514
+ display === 'inline-table' ||
4515
+ display === 'inline-flex') {
4516
+ style.underline = null;
4517
+ style.lineThrough = null;
4518
+ }
4519
+ const own = style.textDecorationLine;
4520
+ if (own === 'none')
4521
+ return;
4522
+ const lines = own.split(' ');
4523
+ if (lines.includes('underline')) {
4524
+ style.underline = inkColor(style.textDecorationColor ?? 'currentColor', style.color);
4525
+ style.underlineStyle = style.textDecorationStyle;
4526
+ style.underlineThickness = style.textDecorationThickness;
4527
+ style.underlineOffset = style.textUnderlineOffset;
4528
+ }
4529
+ if (lines.includes('line-through')) {
4530
+ style.lineThrough = inkColor(style.textDecorationColor ?? 'currentColor', style.color);
4531
+ }
4532
+ }
4533
+ /**
4534
+ * The absolutely positioned styles that were inline-level before they were
4535
+ * made blocks: where such a box would have been in flow is on its line,
4536
+ * where a block's is under it (CSS 2.1 10.3.7, `staticPositions`).
4537
+ */
4538
+ export const INLINE_BEFORE_ABSOLUTE = new WeakSet();
4539
+ /**
4540
+ * `line-clamp`'s longhands and the legacy flexible box they are written
4541
+ * with, settled once the cascade is done, since each reads the others
4542
+ * (CSS Overflow 4, 5.1.1 and 5.3). `continue: collapse` makes a block
4543
+ * container a line-clamp container, and `-webkit-line-clamp`'s
4544
+ * `-webkit-legacy` does only in a `display: -webkit-box` whose
4545
+ * `-webkit-box-orient` is vertical — which is then a block of its own
4546
+ * formatting context, as Tailwind's `line-clamp-2` writes all four. Any
4547
+ * other `-webkit-box` is a flex box, in its orient's direction, packed and
4548
+ * aligned as it says, whose items grow and shrink by `-webkit-box-flex`
4549
+ * alone (as Blink lays one out). Before `blockify`, whose `display` this
4550
+ * decides.
4551
+ */
4552
+ export function settleClamp(style, parent) {
4553
+ const box = style.webkitBox;
4554
+ const vertical = style.webkitBoxOrient === 'vertical';
4555
+ const collapses = style.clampContinue === 'collapse' ||
4556
+ (style.clampContinue === 'legacy' && box !== null && vertical);
4557
+ if (box !== null) {
4558
+ if (collapses && vertical) {
4559
+ style.display = box === 'inline' ? 'inline-block' : 'block';
4560
+ style.flowRoot = box === 'block';
4561
+ }
4562
+ else {
4563
+ const reverse = style.webkitBoxDirection === 'reverse';
4564
+ style.flexDirection = vertical
4565
+ ? reverse
4566
+ ? 'column-reverse'
4567
+ : 'column'
4568
+ : reverse
4569
+ ? 'row-reverse'
4570
+ : 'row';
4571
+ style.flexWrap = 'nowrap';
4572
+ style.justifyContent = style.webkitBoxPack ?? 'flex-start';
4573
+ style.alignItems = style.webkitBoxAlign ?? 'stretch';
4574
+ }
4575
+ }
4576
+ if (parent?.webkitBox &&
4577
+ (parent.display === 'flex' || parent.display === 'inline-flex')) {
4578
+ style.flexGrow = style.webkitBoxFlex;
4579
+ style.flexShrink = style.webkitBoxFlex;
4580
+ }
4581
+ style.lineClamp =
4582
+ collapses && CLAMPS.has(style.display) && !style.columns
4583
+ ? (style.maxLines ?? Infinity)
4584
+ : null;
4585
+ }
4586
+ /** The displays of a block container, which `continue` applies to. */
4587
+ const CLAMPS = new Set([
4588
+ 'block',
4589
+ 'inline-block',
4590
+ 'list-item',
4591
+ 'table-cell',
4592
+ 'table-caption',
4593
+ ]);
1111
4594
  /**
1112
4595
  * The blockification the box tree depends on: a floated or absolutely
1113
4596
  * positioned element is a block whatever `display` said, and a flex item's
@@ -1123,6 +4606,13 @@ export function blockify(style, inFlexContainer) {
1123
4606
  style.position === 'fixed';
1124
4607
  if (!isOutOfFlow && !inFlexContainer)
1125
4608
  return;
4609
+ if ((style.position === 'absolute' || style.position === 'fixed') &&
4610
+ (out === 'inline' ||
4611
+ out === 'inline-block' ||
4612
+ out === 'inline-table' ||
4613
+ out === 'inline-flex')) {
4614
+ INLINE_BEFORE_ABSOLUTE.add(style);
4615
+ }
1126
4616
  switch (out) {
1127
4617
  case 'inline':
1128
4618
  case 'inline-block':
@@ -1132,6 +4622,8 @@ export function blockify(style, inFlexContainer) {
1132
4622
  case 'table-header-group':
1133
4623
  case 'table-footer-group':
1134
4624
  case 'table-caption':
4625
+ case 'table-column':
4626
+ case 'table-column-group':
1135
4627
  style.display = 'block';
1136
4628
  return;
1137
4629
  case 'inline-flex':