@react-x11/components 0.9.0 → 0.11.0

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