pagyra-js 0.0.20 → 0.0.21

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 (286) hide show
  1. package/README.md +55 -0
  2. package/dist/assets/fonts/licenses/selawik/SIL Open Font License.txt +43 -0
  3. package/dist/assets/fonts/ttf/arimo/Arimo-Bold.ttf +0 -0
  4. package/dist/assets/fonts/ttf/arimo/Arimo-BoldItalic.ttf +0 -0
  5. package/dist/assets/fonts/ttf/arimo/Arimo-Italic.ttf +0 -0
  6. package/dist/assets/fonts/ttf/arimo/Arimo-Regular.ttf +0 -0
  7. package/dist/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Black.ttf +0 -0
  8. package/dist/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Bold.ttf +0 -0
  9. package/dist/assets/fonts/ttf/cinzeldecorative/CinzelDecorative-Regular.ttf +0 -0
  10. package/dist/assets/fonts/ttf/dejavu/DejaVuSans.ttf +0 -0
  11. package/dist/assets/fonts/ttf/firecode/FiraCode-Bold.ttf +0 -0
  12. package/dist/assets/fonts/ttf/firecode/FiraCode-Light.ttf +0 -0
  13. package/dist/assets/fonts/ttf/firecode/FiraCode-Medium.ttf +0 -0
  14. package/dist/assets/fonts/ttf/firecode/FiraCode-Regular.ttf +0 -0
  15. package/dist/assets/fonts/ttf/firecode/FiraCode-SemiBold.ttf +0 -0
  16. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Bold.ttf +0 -0
  17. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Light.ttf +0 -0
  18. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Medium.ttf +0 -0
  19. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-Regular.ttf +0 -0
  20. package/dist/assets/fonts/ttf/notoemoji/NotoEmoji-SemiBold.ttf +0 -0
  21. package/dist/assets/fonts/ttf/notosans/NotoSans-Regular.ttf +0 -0
  22. package/dist/assets/fonts/ttf/roboto/Roboto-Bold.ttf +0 -0
  23. package/dist/assets/fonts/ttf/roboto/Roboto-BoldItalic.ttf +0 -0
  24. package/dist/assets/fonts/ttf/roboto/Roboto-Italic.ttf +0 -0
  25. package/dist/assets/fonts/ttf/roboto/Roboto-Regular.ttf +0 -0
  26. package/dist/assets/fonts/ttf/selawik/selawk.ttf +0 -0
  27. package/dist/assets/fonts/ttf/selawik/selawkb.ttf +0 -0
  28. package/dist/assets/fonts/ttf/selawik/selawkl.ttf +0 -0
  29. package/dist/assets/fonts/ttf/selawik/selawksb.ttf +0 -0
  30. package/dist/assets/fonts/ttf/selawik/selawksl.ttf +0 -0
  31. package/dist/assets/fonts/ttf/stixtwomath/STIXTwoMath-Regular.ttf +0 -0
  32. package/dist/assets/fonts/ttf/tinos/Tinos-Bold.ttf +0 -0
  33. package/dist/assets/fonts/ttf/tinos/Tinos-BoldItalic.ttf +0 -0
  34. package/dist/assets/fonts/ttf/tinos/Tinos-Italic.ttf +0 -0
  35. package/dist/assets/fonts/ttf/tinos/Tinos-Regular.ttf +0 -0
  36. package/dist/assets/fonts/woff/lato/lato-latin-400-italic.woff +0 -0
  37. package/dist/assets/fonts/woff/lato/lato-latin-400-normal.woff +0 -0
  38. package/dist/assets/fonts/woff/lato/lato-latin-700-italic.woff +0 -0
  39. package/dist/assets/fonts/woff/lato/lato-latin-700-normal.woff +0 -0
  40. package/dist/assets/fonts/woff2/caveat/Caveat-Bold.woff2 +0 -0
  41. package/dist/assets/fonts/woff2/caveat/Caveat-Regular.woff2 +0 -0
  42. package/dist/assets/fonts/woff2/lato/lato-latin-400-italic.woff2 +0 -0
  43. package/dist/assets/fonts/woff2/lato/lato-latin-400-normal.woff2 +0 -0
  44. package/dist/assets/fonts/woff2/lato/lato-latin-700-italic.woff2 +0 -0
  45. package/dist/assets/fonts/woff2/lato/lato-latin-700-normal.woff2 +0 -0
  46. package/dist/browser/pagyra.min.js +34 -34
  47. package/dist/browser/pagyra.min.js.map +4 -4
  48. package/dist/playground/server.js +2 -0
  49. package/dist/src/css/compute-style/base-options.d.ts +7 -0
  50. package/dist/src/css/compute-style/base-options.js +24 -0
  51. package/dist/src/css/compute-style/declarations.d.ts +10 -0
  52. package/dist/src/css/compute-style/declarations.js +77 -0
  53. package/dist/src/css/compute-style/decoration.d.ts +8 -0
  54. package/dist/src/css/compute-style/decoration.js +55 -0
  55. package/dist/src/css/compute-style/defaults.d.ts +3 -0
  56. package/dist/src/css/compute-style/defaults.js +34 -0
  57. package/dist/src/css/compute-style/display.d.ts +3 -0
  58. package/dist/src/css/compute-style/display.js +85 -0
  59. package/dist/src/css/compute-style/float.d.ts +2 -0
  60. package/dist/src/css/compute-style/float.js +13 -0
  61. package/dist/src/css/compute-style/font.d.ts +12 -0
  62. package/dist/src/css/compute-style/font.js +57 -0
  63. package/dist/src/css/compute-style/overrides.d.ts +3 -0
  64. package/dist/src/css/compute-style/overrides.js +241 -0
  65. package/dist/src/css/compute-style.d.ts +2 -0
  66. package/dist/src/css/compute-style.js +34 -487
  67. package/dist/src/css/enums.d.ts +4 -0
  68. package/dist/src/css/enums.js +5 -0
  69. package/dist/src/css/layout-property-resolver.js +30 -18
  70. package/dist/src/css/length.d.ts +26 -2
  71. package/dist/src/css/length.js +48 -0
  72. package/dist/src/css/parsers/background-parser.js +1 -1
  73. package/dist/src/css/parsers/calc-parser.d.ts +2 -0
  74. package/dist/src/css/parsers/calc-parser.js +310 -0
  75. package/dist/src/css/parsers/content-parser.d.ts +2 -1
  76. package/dist/src/css/parsers/content-parser.js +7 -2
  77. package/dist/src/css/parsers/dimension-parser.js +37 -18
  78. package/dist/src/css/parsers/display-flex-parser.d.ts +4 -0
  79. package/dist/src/css/parsers/display-flex-parser.js +97 -0
  80. package/dist/src/css/parsers/filter-parser.d.ts +14 -0
  81. package/dist/src/css/parsers/filter-parser.js +255 -0
  82. package/dist/src/css/parsers/grid-parser-extended.d.ts +1 -0
  83. package/dist/src/css/parsers/grid-parser-extended.js +40 -1
  84. package/dist/src/css/parsers/grid-parser.d.ts +5 -2
  85. package/dist/src/css/parsers/grid-parser.js +71 -7
  86. package/dist/src/css/parsers/length-parser.d.ts +8 -3
  87. package/dist/src/css/parsers/length-parser.js +45 -2
  88. package/dist/src/css/parsers/margin-block-parser.js +3 -3
  89. package/dist/src/css/parsers/margin-parser.js +3 -3
  90. package/dist/src/css/parsers/padding-block-parser.js +3 -3
  91. package/dist/src/css/parsers/padding-inline-parser.js +3 -3
  92. package/dist/src/css/parsers/padding-parser.js +6 -6
  93. package/dist/src/css/parsers/position-parser.js +2 -22
  94. package/dist/src/css/parsers/register-parsers.js +29 -2
  95. package/dist/src/css/parsers/word-break-parser.d.ts +2 -0
  96. package/dist/src/css/parsers/word-break-parser.js +23 -0
  97. package/dist/src/css/properties/grid.d.ts +16 -2
  98. package/dist/src/css/properties/layout.d.ts +3 -1
  99. package/dist/src/css/properties/layout.js +1 -1
  100. package/dist/src/css/properties/misc.d.ts +5 -0
  101. package/dist/src/css/properties/typography.d.ts +3 -0
  102. package/dist/src/css/properties/visual.d.ts +36 -0
  103. package/dist/src/css/shorthands/box-shorthand.d.ts +2 -2
  104. package/dist/src/css/style-inheritance.d.ts +2 -1
  105. package/dist/src/css/style-inheritance.js +1 -0
  106. package/dist/src/css/style.d.ts +30 -10
  107. package/dist/src/css/style.js +8 -1
  108. package/dist/src/css/ua-defaults/base-defaults.d.ts +1 -0
  109. package/dist/src/css/ua-defaults/base-defaults.js +10 -1
  110. package/dist/src/css/ua-defaults/element-defaults.js +0 -2
  111. package/dist/src/html/css/parse-css.d.ts +2 -0
  112. package/dist/src/html/css/parse-css.js +32 -3
  113. package/dist/src/html/dom-converter/background-images.d.ts +3 -0
  114. package/dist/src/html/dom-converter/background-images.js +88 -0
  115. package/dist/src/html/dom-converter/convert-dom-node.d.ts +5 -0
  116. package/dist/src/html/dom-converter/convert-dom-node.js +81 -0
  117. package/dist/src/html/dom-converter/handlers/br-handler.d.ts +2 -0
  118. package/dist/src/html/dom-converter/handlers/br-handler.js +20 -0
  119. package/dist/src/html/dom-converter/handlers/form-control-handler.d.ts +2 -0
  120. package/dist/src/html/dom-converter/handlers/form-control-handler.js +28 -0
  121. package/dist/src/html/dom-converter/handlers/img-handler.d.ts +2 -0
  122. package/dist/src/html/dom-converter/handlers/img-handler.js +4 -0
  123. package/dist/src/html/dom-converter/handlers/index.d.ts +4 -0
  124. package/dist/src/html/dom-converter/handlers/index.js +19 -0
  125. package/dist/src/html/dom-converter/handlers/svg-handler.d.ts +2 -0
  126. package/dist/src/html/dom-converter/handlers/svg-handler.js +32 -0
  127. package/dist/src/html/dom-converter/handlers/types.d.ts +12 -0
  128. package/dist/src/html/dom-converter/handlers/types.js +2 -0
  129. package/dist/src/html/dom-converter/helpers.d.ts +7 -0
  130. package/dist/src/html/dom-converter/helpers.js +35 -0
  131. package/dist/src/html/dom-converter/index.d.ts +1 -0
  132. package/dist/src/html/dom-converter/index.js +1 -0
  133. package/dist/src/html/dom-converter/pseudo-elements.d.ts +6 -0
  134. package/dist/src/html/dom-converter/pseudo-elements.js +48 -0
  135. package/dist/src/html/dom-converter/text.d.ts +15 -0
  136. package/dist/src/html/dom-converter/text.js +170 -0
  137. package/dist/src/html/dom-converter.d.ts +1 -5
  138. package/dist/src/html/dom-converter.js +1 -417
  139. package/dist/src/html/image-converter.d.ts +5 -0
  140. package/dist/src/html-to-pdf/document-css.d.ts +14 -0
  141. package/dist/src/html-to-pdf/document-css.js +45 -0
  142. package/dist/src/html-to-pdf/fonts.d.ts +16 -0
  143. package/dist/src/html-to-pdf/fonts.js +74 -0
  144. package/dist/src/html-to-pdf/header-footer.d.ts +14 -0
  145. package/dist/src/html-to-pdf/header-footer.js +101 -0
  146. package/dist/src/html-to-pdf/html-parser.d.ts +6 -0
  147. package/dist/src/html-to-pdf/html-parser.js +81 -0
  148. package/dist/src/html-to-pdf/index.d.ts +3 -0
  149. package/dist/src/html-to-pdf/index.js +2 -0
  150. package/dist/src/html-to-pdf/layout-build.d.ts +37 -0
  151. package/dist/src/html-to-pdf/layout-build.js +73 -0
  152. package/dist/src/html-to-pdf/prepare-html-render.d.ts +2 -0
  153. package/dist/src/html-to-pdf/prepare-html-render.js +121 -0
  154. package/dist/src/html-to-pdf/render-finalize.d.ts +15 -0
  155. package/dist/src/html-to-pdf/render-finalize.js +27 -0
  156. package/dist/src/html-to-pdf/render-html-to-pdf.d.ts +3 -0
  157. package/dist/src/html-to-pdf/render-html-to-pdf.js +25 -0
  158. package/dist/src/html-to-pdf/resource-loader.d.ts +6 -0
  159. package/dist/src/html-to-pdf/resource-loader.js +120 -0
  160. package/dist/src/html-to-pdf/types.d.ts +38 -0
  161. package/dist/src/html-to-pdf/types.js +2 -0
  162. package/dist/src/html-to-pdf.d.ts +1 -37
  163. package/dist/src/html-to-pdf.js +1 -537
  164. package/dist/src/image/js-png-backend.d.ts +7 -0
  165. package/dist/src/image/js-png-backend.js +9 -0
  166. package/dist/src/image/png-backend.d.ts +5 -0
  167. package/dist/src/image/png-backend.js +1 -0
  168. package/dist/src/image/png-wasm-loader.d.ts +5 -0
  169. package/dist/src/image/png-wasm-loader.js +59 -0
  170. package/dist/src/image/wasm/png_decoder_wasm.d.ts +8 -0
  171. package/dist/src/image/wasm/png_decoder_wasm.js +24 -0
  172. package/dist/src/image/wasm/png_decoder_wasm_bg.js +16 -0
  173. package/dist/src/image/wasm-png-backend.d.ts +6 -0
  174. package/dist/src/image/wasm-png-backend.js +17 -0
  175. package/dist/src/layout/counter.d.ts +1 -2
  176. package/dist/src/layout/counter.js +18 -18
  177. package/dist/src/layout/inline/inline-utils.d.ts +1 -1
  178. package/dist/src/layout/inline/inline-utils.js +8 -7
  179. package/dist/src/layout/inline/layout.js +16 -3
  180. package/dist/src/layout/inline/run-placer.d.ts +1 -0
  181. package/dist/src/layout/inline/run-placer.js +2 -10
  182. package/dist/src/layout/pipeline/out-of-flow-manager.js +25 -1
  183. package/dist/src/layout/strategies/block.js +35 -24
  184. package/dist/src/layout/strategies/flex.js +305 -61
  185. package/dist/src/layout/strategies/form.d.ts +2 -0
  186. package/dist/src/layout/strategies/form.js +38 -13
  187. package/dist/src/layout/strategies/grid.js +166 -29
  188. package/dist/src/layout/strategies/image.js +53 -27
  189. package/dist/src/layout/strategies/inline.js +26 -21
  190. package/dist/src/layout/strategies/table.js +26 -18
  191. package/dist/src/layout/utils/content-measurer.d.ts +1 -1
  192. package/dist/src/layout/utils/content-measurer.js +8 -7
  193. package/dist/src/layout/utils/floats.d.ts +1 -0
  194. package/dist/src/layout/utils/floats.js +14 -12
  195. package/dist/src/layout/utils/margin.d.ts +4 -4
  196. package/dist/src/layout/utils/margin.js +20 -16
  197. package/dist/src/layout/utils/node-math.d.ts +12 -6
  198. package/dist/src/layout/utils/node-math.js +71 -41
  199. package/dist/src/layout/utils/sizing.js +2 -1
  200. package/dist/src/pdf/font-subset/font-registry.d.ts +6 -0
  201. package/dist/src/pdf/font-subset/font-registry.js +30 -2
  202. package/dist/src/pdf/header-footer-renderer.js +12 -1
  203. package/dist/src/pdf/layout-tree-builder.js +5 -1
  204. package/dist/src/pdf/page-painter.js +13 -0
  205. package/dist/src/pdf/pagination.js +2 -2
  206. package/dist/src/pdf/renderer/box-painter.js +28 -3
  207. package/dist/src/pdf/renderer/page-paint.js +11 -3
  208. package/dist/src/pdf/renderers/radius-utils.js +31 -38
  209. package/dist/src/pdf/renderers/shape-renderer.js +1 -1
  210. package/dist/src/pdf/renderers/shape-utils.js +1 -1
  211. package/dist/src/pdf/renderers/text-renderer.d.ts +9 -1
  212. package/dist/src/pdf/renderers/text-renderer.js +36 -2
  213. package/dist/src/pdf/stacking/build-stacking-contexts.js +1 -2
  214. package/dist/src/pdf/stacking/resolve-paint-order.d.ts +5 -6
  215. package/dist/src/pdf/stacking/resolve-paint-order.js +29 -9
  216. package/dist/src/pdf/stacking/types.d.ts +14 -0
  217. package/dist/src/pdf/svg/shape-renderer.js +47 -20
  218. package/dist/src/pdf/types.d.ts +7 -1
  219. package/dist/src/pdf/utils/border-radius-utils.js +31 -38
  220. package/dist/src/pdf/utils/color-utils.js +17 -2
  221. package/dist/src/pdf/utils/filter-utils.d.ts +29 -0
  222. package/dist/src/pdf/utils/filter-utils.js +85 -0
  223. package/dist/src/pdf/utils/node-text-run-factory.js +1 -1
  224. package/dist/src/pdf/utils/text-layout-adjuster.d.ts +0 -8
  225. package/dist/src/pdf/utils/text-layout-adjuster.js +12 -9
  226. package/dist/src/units/units.d.ts +1 -1
  227. package/dist/tests/css/box-sizing.spec.d.ts +1 -0
  228. package/dist/tests/css/box-sizing.spec.js +46 -0
  229. package/dist/tests/css/calc-parser.spec.d.ts +1 -0
  230. package/dist/tests/css/calc-parser.spec.js +68 -0
  231. package/dist/tests/css/container-query-units.spec.d.ts +1 -0
  232. package/dist/tests/css/container-query-units.spec.js +64 -0
  233. package/dist/tests/css/content-parser.spec.js +13 -0
  234. package/dist/tests/css/filter-parser.spec.d.ts +1 -0
  235. package/dist/tests/css/filter-parser.spec.js +116 -0
  236. package/dist/tests/css/flex-shorthand.spec.d.ts +1 -0
  237. package/dist/tests/css/flex-shorthand.spec.js +45 -0
  238. package/dist/tests/css/grid-clamp.spec.d.ts +1 -0
  239. package/dist/tests/css/grid-clamp.spec.js +82 -0
  240. package/dist/tests/css/parse-css-pseudo.spec.d.ts +1 -0
  241. package/dist/tests/css/parse-css-pseudo.spec.js +26 -0
  242. package/dist/tests/helpers/render-utils.d.ts +18 -2
  243. package/dist/tests/helpers/render-utils.js +25 -12
  244. package/dist/tests/html/dom-converter-pseudo-elements.spec.d.ts +1 -0
  245. package/dist/tests/html/dom-converter-pseudo-elements.spec.js +33 -0
  246. package/dist/tests/html/dom-converter-text.spec.d.ts +1 -0
  247. package/dist/tests/html/dom-converter-text.spec.js +67 -0
  248. package/dist/tests/image/png-backend.spec.d.ts +1 -0
  249. package/dist/tests/image/png-backend.spec.js +34 -0
  250. package/dist/tests/layout/box-sizing.spec.d.ts +1 -0
  251. package/dist/tests/layout/box-sizing.spec.js +75 -0
  252. package/dist/tests/layout/calc-padding.spec.d.ts +1 -0
  253. package/dist/tests/layout/calc-padding.spec.js +19 -0
  254. package/dist/tests/layout/container-query-units.spec.d.ts +1 -0
  255. package/dist/tests/layout/container-query-units.spec.js +24 -0
  256. package/dist/tests/layout/flex-auto-height.spec.d.ts +1 -0
  257. package/dist/tests/layout/flex-auto-height.spec.js +35 -0
  258. package/dist/tests/layout/flex-wrap-cards.spec.d.ts +1 -0
  259. package/dist/tests/layout/flex-wrap-cards.spec.js +16 -0
  260. package/dist/tests/layout/flex-wrap-grow-align-content.spec.d.ts +1 -0
  261. package/dist/tests/layout/flex-wrap-grow-align-content.spec.js +20 -0
  262. package/dist/tests/layout/grid-clamp-gap.spec.d.ts +1 -0
  263. package/dist/tests/layout/grid-clamp-gap.spec.js +22 -0
  264. package/dist/tests/layout/inline-fragments.spec.js +38 -0
  265. package/dist/tests/layout/paged-body-margin.spec.d.ts +1 -0
  266. package/dist/tests/layout/paged-body-margin.spec.js +92 -0
  267. package/dist/tests/layout/pseudo-counters-generated-content.spec.d.ts +1 -0
  268. package/dist/tests/layout/pseudo-counters-generated-content.spec.js +51 -0
  269. package/dist/tests/layout/responsive-clamp-grid-parity.spec.d.ts +1 -0
  270. package/dist/tests/layout/responsive-clamp-grid-parity.spec.js +75 -0
  271. package/dist/tests/layout/run-placer-baseline.spec.js +13 -11
  272. package/dist/tests/pdf/backdrop-filter-noop.spec.d.ts +1 -0
  273. package/dist/tests/pdf/backdrop-filter-noop.spec.js +140 -0
  274. package/dist/tests/pdf/filter-drop-shadow.spec.d.ts +1 -0
  275. package/dist/tests/pdf/filter-drop-shadow.spec.js +74 -0
  276. package/dist/tests/pdf/filter-opacity.spec.d.ts +1 -0
  277. package/dist/tests/pdf/filter-opacity.spec.js +30 -0
  278. package/dist/tests/pdf/font-subset-registry-key.spec.d.ts +1 -0
  279. package/dist/tests/pdf/font-subset-registry-key.spec.js +66 -0
  280. package/dist/tests/pdf/selawik-opt-in.spec.d.ts +1 -0
  281. package/dist/tests/pdf/selawik-opt-in.spec.js +106 -0
  282. package/dist/tests/pdf/system-ui-fallback-subset-regression.spec.d.ts +1 -0
  283. package/dist/tests/pdf/system-ui-fallback-subset-regression.spec.js +39 -0
  284. package/dist/tests/pdf/text-renderer-fallback.spec.js +55 -0
  285. package/dist/tests/pdf/text-transform-matrix.spec.js +8 -7
  286. package/package.json +2 -2
@@ -0,0 +1,85 @@
1
+ // src/pdf/utils/filter-utils.ts
2
+ import { parseColor, cloneColor } from "./color-utils.js";
3
+ import { log } from "../../logging/debug.js";
4
+ /**
5
+ * Extrai o multiplicador de opacity de uma lista de filter functions.
6
+ * Múltiplos `opacity()` são multiplicados entre si (composição sequencial).
7
+ * Retorna 1.0 se não houver `opacity()` na lista.
8
+ */
9
+ export function extractOpacityMultiplier(filters) {
10
+ let multiplier = 1;
11
+ for (const fn of filters) {
12
+ if (fn.kind === "opacity") {
13
+ multiplier *= fn.value;
14
+ }
15
+ }
16
+ return Math.max(0, Math.min(1, multiplier));
17
+ }
18
+ /**
19
+ * Converte `drop-shadow()` do filter em ShadowLayer[] compatível
20
+ * com o pipeline de box-shadow existente.
21
+ *
22
+ * Diferenças em relação a box-shadow real:
23
+ * - Sem `inset` (sempre false).
24
+ * - Sem `spread-radius` (sempre 0).
25
+ * - Na prática do CSS, drop-shadow segue a forma alpha do conteúdo,
26
+ * mas no MVP aproximamos como sombra retangular (box-shadow).
27
+ */
28
+ export function extractDropShadowLayers(filters, fallbackColor) {
29
+ const result = [];
30
+ for (const fn of filters) {
31
+ if (fn.kind !== "drop-shadow")
32
+ continue;
33
+ const offsetX = typeof fn.offsetX === "number" ? fn.offsetX : 0;
34
+ const offsetY = typeof fn.offsetY === "number" ? fn.offsetY : 0;
35
+ const blur = typeof fn.blurRadius === "number" ? Math.max(0, fn.blurRadius) : 0;
36
+ let color;
37
+ if (fn.color) {
38
+ const parsed = parseColor(fn.color);
39
+ color = parsed ? cloneColor(parsed) : cloneColor(fallbackColor);
40
+ }
41
+ else {
42
+ color = cloneColor(fallbackColor);
43
+ }
44
+ result.push({
45
+ inset: false,
46
+ offsetX,
47
+ offsetY,
48
+ blur,
49
+ spread: 0,
50
+ color,
51
+ });
52
+ }
53
+ return result;
54
+ }
55
+ /**
56
+ * Lista nomes de funções de filtro que não são renderizáveis no MVP.
57
+ * Usado para emitir warnings durante a fase de pintura.
58
+ */
59
+ export function listUnsupportedFilters(filters) {
60
+ if (!filters || filters.length === 0)
61
+ return [];
62
+ const unsupported = [];
63
+ for (const fn of filters) {
64
+ // opacity e drop-shadow são suportados no MVP
65
+ if (fn.kind === "opacity" || fn.kind === "drop-shadow")
66
+ continue;
67
+ unsupported.push(fn.kind);
68
+ }
69
+ return unsupported;
70
+ }
71
+ /**
72
+ * Emite warnings para filtros não suportados.
73
+ * Chamado uma vez por box durante a fase de pintura.
74
+ */
75
+ export function warnUnsupportedFilters(filters, label, boxId) {
76
+ const names = label === "backdrop-filter"
77
+ ? (filters ?? []).map((fn) => fn.kind)
78
+ : listUnsupportedFilters(filters);
79
+ if (names.length === 0)
80
+ return;
81
+ log("paint", "warn", `Unsupported ${label} function(s) ignored: ${names.join(", ")}`, {
82
+ boxId,
83
+ functions: names,
84
+ });
85
+ }
@@ -20,7 +20,7 @@ export function buildNodeTextRuns(context) {
20
20
  }
21
21
  }
22
22
  if (transform && textRuns.length > 0) {
23
- applyTransformToTextRuns(textRuns, transform, borderBox);
23
+ // applyTransformToTextRuns(textRuns, transform, borderBox);
24
24
  }
25
25
  return textRuns;
26
26
  }
@@ -1,12 +1,4 @@
1
1
  import type { RenderBox } from "../types.js";
2
- /**
3
- * Global pass to fix justified layout in the render tree.
4
- *
5
- * For each RenderBox with textAlign: "justify":
6
- * - collect ALL runs from that box and its descendants
7
- * - group runs by lineIndex
8
- import type { RenderBox, Run } from "../types.js";
9
-
10
2
  /**
11
3
  * Global pass to fix justified layout in the render tree.
12
4
  *
@@ -1,11 +1,3 @@
1
- /**
2
- * Global pass to fix justified layout in the render tree.
3
- *
4
- * For each RenderBox with textAlign: "justify":
5
- * - collect ALL runs from that box and its descendants
6
- * - group runs by lineIndex
7
- import type { RenderBox, Run } from "../types.js";
8
-
9
1
  /**
10
2
  * Global pass to fix justified layout in the render tree.
11
3
  *
@@ -73,6 +65,17 @@ function adjustLinePositions(runs) {
73
65
  /^\s+$/.test(runs[firstContentIndex].text)) {
74
66
  firstContentIndex++;
75
67
  }
68
+ const hasLeadingWhitespace = firstContentIndex > 0;
69
+ if (firstContentIndex >= runs.length) {
70
+ const anchorX = runs[0].lineMatrix?.e ?? 0;
71
+ for (const run of runs) {
72
+ if (run.lineMatrix) {
73
+ run.lineMatrix.e = anchorX;
74
+ }
75
+ run.advanceWidth = 0;
76
+ }
77
+ return;
78
+ }
76
79
  // Calculate width to redistribute from leading spaces
77
80
  let widthLost = 0;
78
81
  for (let i = 0; i < firstContentIndex; i++) {
@@ -85,7 +88,7 @@ function adjustLinePositions(runs) {
85
88
  }
86
89
  const isLastLine = runs[0].isLastLine ?? false;
87
90
  // Redistribute width if not last line and we have spaces to distribute to
88
- if (!isLastLine && widthLost > 0 && remainingSpaces > 0) {
91
+ if (hasLeadingWhitespace && !isLastLine && widthLost > 0 && remainingSpaces > 0) {
89
92
  const adjustment = widthLost / remainingSpaces;
90
93
  for (let i = firstContentIndex; i < runs.length; i++) {
91
94
  const run = runs[i];
@@ -13,6 +13,6 @@ export interface UnitCtx {
13
13
  }
14
14
  export declare function makeUnitParsers(_ctx: UnitCtx): {
15
15
  parseLength: (value: string) => number | import("../css/length.js").RelativeLength | undefined;
16
- parseLengthOrPercent: (value: string) => number | import("../css/length.js").AbsoluteLength | import("../css/length.js").RelativeLength | undefined;
16
+ parseLengthOrPercent: (value: string) => number | import("../css/length.js").AbsoluteLength | import("../css/length.js").RelativeLength | import("../css/length.js").CalcLength | undefined;
17
17
  };
18
18
  export type UnitParsers = ReturnType<typeof makeUnitParsers>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ import { registerAllPropertyParsers } from "../../src/css/parsers/register-parsers.js";
2
+ import { computeStyleForElement } from "../../src/css/compute-style.js";
3
+ import { ComputedStyle } from "../../src/css/style.js";
4
+ import { BoxSizing } from "../../src/css/enums.js";
5
+ import { makeUnitParsers } from "../../src/units/units.js";
6
+ function makeElement(inlineStyle) {
7
+ return {
8
+ nodeType: 1,
9
+ nodeName: "DIV",
10
+ tagName: "div",
11
+ getAttribute(name) {
12
+ if (name === "style") {
13
+ return inlineStyle;
14
+ }
15
+ return null;
16
+ },
17
+ hasAttribute(name) {
18
+ return name === "style";
19
+ },
20
+ querySelectorAll(_selectors) {
21
+ return [];
22
+ },
23
+ parentElement: null,
24
+ firstElementChild: null,
25
+ lastElementChild: null,
26
+ nextElementSibling: null,
27
+ previousElementSibling: null,
28
+ };
29
+ }
30
+ describe("box-sizing parser and computed style", () => {
31
+ beforeAll(() => {
32
+ registerAllPropertyParsers();
33
+ });
34
+ it("defaults to content-box", () => {
35
+ const style = computeStyleForElement(makeElement("width: 200px;"), [], new ComputedStyle(), makeUnitParsers({ viewport: { width: 800, height: 600 } }), 16);
36
+ expect(style.boxSizing).toBe(BoxSizing.ContentBox);
37
+ });
38
+ it("parses border-box from inline style", () => {
39
+ const style = computeStyleForElement(makeElement("box-sizing: border-box; width: 200px;"), [], new ComputedStyle(), makeUnitParsers({ viewport: { width: 800, height: 600 } }), 16);
40
+ expect(style.boxSizing).toBe(BoxSizing.BorderBox);
41
+ });
42
+ it("parses content-box explicitly", () => {
43
+ const style = computeStyleForElement(makeElement("box-sizing: content-box;"), [], new ComputedStyle(), makeUnitParsers({ viewport: { width: 800, height: 600 } }), 16);
44
+ expect(style.boxSizing).toBe(BoxSizing.ContentBox);
45
+ });
46
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,68 @@
1
+ import { parseCalcLength } from "../../src/css/parsers/calc-parser.js";
2
+ import { computeStyleForElement } from "../../src/css/compute-style.js";
3
+ import { ComputedStyle } from "../../src/css/style.js";
4
+ import { registerAllPropertyParsers } from "../../src/css/parsers/register-parsers.js";
5
+ import { makeUnitParsers } from "../../src/units/units.js";
6
+ function makeElement(inlineStyle) {
7
+ return {
8
+ nodeType: 1,
9
+ nodeName: "DIV",
10
+ tagName: "div",
11
+ getAttribute(name) {
12
+ if (name === "style") {
13
+ return inlineStyle;
14
+ }
15
+ return null;
16
+ },
17
+ hasAttribute(name) {
18
+ return name === "style";
19
+ },
20
+ querySelectorAll(_selectors) {
21
+ return [];
22
+ },
23
+ parentElement: null,
24
+ firstElementChild: null,
25
+ lastElementChild: null,
26
+ nextElementSibling: null,
27
+ previousElementSibling: null,
28
+ };
29
+ }
30
+ describe("calc() parser", () => {
31
+ beforeAll(() => {
32
+ registerAllPropertyParsers();
33
+ });
34
+ it("parses arithmetic with mixed units", () => {
35
+ const parsed = parseCalcLength("calc((10px + 5%) * 2 - 4px / 2)");
36
+ expect(parsed).toBeDefined();
37
+ expect(parsed).toEqual({
38
+ kind: "calc",
39
+ px: 18,
40
+ percent: 0.1,
41
+ em: 0,
42
+ rem: 0,
43
+ cqw: 0,
44
+ cqh: 0,
45
+ cqi: 0,
46
+ cqb: 0,
47
+ cqmin: 0,
48
+ cqmax: 0,
49
+ });
50
+ });
51
+ it("supports calc in width declarations", () => {
52
+ const style = computeStyleForElement(makeElement("width: calc(100% - 20px);"), [], new ComputedStyle(), makeUnitParsers({ viewport: { width: 800, height: 600 } }), 16);
53
+ expect(typeof style.width).toBe("object");
54
+ expect(style.width).toEqual({
55
+ kind: "calc",
56
+ px: -20,
57
+ percent: 1,
58
+ em: 0,
59
+ rem: 0,
60
+ cqw: 0,
61
+ cqh: 0,
62
+ cqi: 0,
63
+ cqb: 0,
64
+ cqmin: 0,
65
+ cqmax: 0,
66
+ });
67
+ });
68
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,64 @@
1
+ import { registerAllPropertyParsers } from "../../src/css/parsers/register-parsers.js";
2
+ import { computeStyleForElement } from "../../src/css/compute-style.js";
3
+ import { ComputedStyle } from "../../src/css/style.js";
4
+ import { makeUnitParsers } from "../../src/units/units.js";
5
+ function makeElement(inlineStyle) {
6
+ return {
7
+ nodeType: 1,
8
+ nodeName: "DIV",
9
+ tagName: "div",
10
+ getAttribute(name) {
11
+ if (name === "style") {
12
+ return inlineStyle;
13
+ }
14
+ return null;
15
+ },
16
+ hasAttribute(name) {
17
+ return name === "style";
18
+ },
19
+ querySelectorAll(_selectors) {
20
+ return [];
21
+ },
22
+ parentElement: null,
23
+ firstElementChild: null,
24
+ lastElementChild: null,
25
+ nextElementSibling: null,
26
+ previousElementSibling: null,
27
+ };
28
+ }
29
+ describe("container query length units", () => {
30
+ beforeAll(() => {
31
+ registerAllPropertyParsers();
32
+ });
33
+ it("parses cqw/cqh in normal lengths", () => {
34
+ const style = computeStyleForElement(makeElement("width:50cqw;height:25cqh;"), [], new ComputedStyle(), makeUnitParsers({ viewport: { width: 800, height: 600 } }), 16);
35
+ expect(style.width).toMatchObject({
36
+ kind: "calc",
37
+ px: 0,
38
+ percent: 0,
39
+ cqw: 0.5,
40
+ });
41
+ expect(style.height).toMatchObject({
42
+ kind: "calc",
43
+ px: 0,
44
+ percent: 0,
45
+ cqh: 0.25,
46
+ });
47
+ });
48
+ it("parses cq units inside calc()", () => {
49
+ const style = computeStyleForElement(makeElement("width:calc(10px + 10cqw - 2cqh + 1cqmin);"), [], new ComputedStyle(), makeUnitParsers({ viewport: { width: 800, height: 600 } }), 16);
50
+ expect(style.width).toEqual({
51
+ kind: "calc",
52
+ px: 10,
53
+ percent: 0,
54
+ em: 0,
55
+ rem: 0,
56
+ cqw: 0.1,
57
+ cqh: -0.02,
58
+ cqi: 0,
59
+ cqb: 0,
60
+ cqmin: 0.01,
61
+ cqmax: 0,
62
+ });
63
+ });
64
+ });
@@ -34,6 +34,13 @@ describe("content CSS property parsing", () => {
34
34
  const item = expectContentType(target.content[0], "counter");
35
35
  expect(item.style).toBe("decimal");
36
36
  });
37
+ it("parses counter() with decimal-leading-zero style", () => {
38
+ const target = {};
39
+ parseContent("counter(step, decimal-leading-zero)", target);
40
+ expect(target.content).toBeDefined();
41
+ const item = expectContentType(target.content[0], "counter");
42
+ expect(item.style).toBe("decimal-leading-zero");
43
+ });
37
44
  it("parses attr() function", () => {
38
45
  const target = {};
39
46
  parseContent('attr(data-index)', target);
@@ -85,6 +92,12 @@ describe("formatCounterValue", () => {
85
92
  expect(formatCounterValue(26, "upper-alpha")).toBe("Z");
86
93
  expect(formatCounterValue(27, "upper-alpha")).toBe("AA");
87
94
  });
95
+ it("formats decimal-leading-zero", () => {
96
+ expect(formatCounterValue(1, "decimal-leading-zero")).toBe("01");
97
+ expect(formatCounterValue(9, "decimal-leading-zero")).toBe("09");
98
+ expect(formatCounterValue(10, "decimal-leading-zero")).toBe("10");
99
+ expect(formatCounterValue(-1, "decimal-leading-zero")).toBe("-01");
100
+ });
88
101
  });
89
102
  describe("evaluateContent", () => {
90
103
  it("evaluates string content", () => {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseFilterList } from "../../src/css/parsers/filter-parser.js";
3
+ describe("parseFilterList", () => {
4
+ // Keywords globais
5
+ it("returns [] for 'none'", () => {
6
+ expect(parseFilterList("none")).toEqual([]);
7
+ });
8
+ it("returns [] for 'initial'", () => {
9
+ expect(parseFilterList("initial")).toEqual([]);
10
+ });
11
+ it("returns undefined for 'inherit'", () => {
12
+ expect(parseFilterList("inherit")).toBeUndefined();
13
+ });
14
+ it("returns undefined for 'revert'", () => {
15
+ expect(parseFilterList("revert")).toBeUndefined();
16
+ });
17
+ it("returns undefined for empty string", () => {
18
+ expect(parseFilterList("")).toBeUndefined();
19
+ });
20
+ // Funções individuais
21
+ it("parses blur(5px)", () => {
22
+ const result = parseFilterList("blur(5px)");
23
+ expect(result).toEqual([{ kind: "blur", value: 5 }]);
24
+ });
25
+ it("parses blur() with no args as blur(0px)", () => {
26
+ const result = parseFilterList("blur()");
27
+ expect(result).toEqual([{ kind: "blur", value: 0 }]);
28
+ });
29
+ it("parses brightness(1.5)", () => {
30
+ const result = parseFilterList("brightness(1.5)");
31
+ expect(result).toEqual([{ kind: "brightness", value: 1.5 }]);
32
+ });
33
+ it("parses brightness(150%)", () => {
34
+ const result = parseFilterList("brightness(150%)");
35
+ expect(result).toEqual([{ kind: "brightness", value: 1.5 }]);
36
+ });
37
+ it("parses opacity(50%)", () => {
38
+ const result = parseFilterList("opacity(50%)");
39
+ expect(result).toEqual([{ kind: "opacity", value: 0.5 }]);
40
+ });
41
+ it("clamps opacity to [0, 1]", () => {
42
+ const result = parseFilterList("opacity(200%)");
43
+ expect(result).toEqual([{ kind: "opacity", value: 1 }]);
44
+ });
45
+ it("clamps grayscale to [0, 1]", () => {
46
+ const result = parseFilterList("grayscale(150%)");
47
+ expect(result).toEqual([{ kind: "grayscale", value: 1 }]);
48
+ });
49
+ it("does not clamp brightness above 1", () => {
50
+ const result = parseFilterList("brightness(3)");
51
+ expect(result).toEqual([{ kind: "brightness", value: 3 }]);
52
+ });
53
+ // Ângulos
54
+ it("parses hue-rotate(90deg)", () => {
55
+ const result = parseFilterList("hue-rotate(90deg)");
56
+ expect(result).toEqual([{ kind: "hue-rotate", valueDeg: 90 }]);
57
+ });
58
+ it("parses hue-rotate(0.5turn)", () => {
59
+ const result = parseFilterList("hue-rotate(0.5turn)");
60
+ expect(result).toEqual([{ kind: "hue-rotate", valueDeg: 180 }]);
61
+ });
62
+ it("parses hue-rotate(3.14159rad)", () => {
63
+ const result = parseFilterList("hue-rotate(3.14159rad)");
64
+ expect(result[0].kind).toBe("hue-rotate");
65
+ expect(result[0].valueDeg).toBeCloseTo(180, 1);
66
+ });
67
+ it("parses hue-rotate with negative angle", () => {
68
+ const result = parseFilterList("hue-rotate(-45deg)");
69
+ expect(result).toEqual([{ kind: "hue-rotate", valueDeg: -45 }]);
70
+ });
71
+ // drop-shadow
72
+ it("parses drop-shadow(2px 4px 6px black)", () => {
73
+ const result = parseFilterList("drop-shadow(2px 4px 6px black)");
74
+ expect(result).toEqual([{
75
+ kind: "drop-shadow",
76
+ offsetX: 2,
77
+ offsetY: 4,
78
+ blurRadius: 6,
79
+ color: "black",
80
+ }]);
81
+ });
82
+ it("parses drop-shadow with only offsets", () => {
83
+ const result = parseFilterList("drop-shadow(2px 4px)");
84
+ expect(result).toEqual([{
85
+ kind: "drop-shadow",
86
+ offsetX: 2,
87
+ offsetY: 4,
88
+ blurRadius: 0,
89
+ color: undefined,
90
+ }]);
91
+ });
92
+ it("parses drop-shadow with rgb() color", () => {
93
+ const result = parseFilterList("drop-shadow(1px 1px 3px rgb(255, 0, 0))");
94
+ expect(result[0].kind).toBe("drop-shadow");
95
+ expect(result[0].color).toBe("rgb(255, 0, 0)");
96
+ });
97
+ // Múltiplas funções
98
+ it("parses multiple functions separated by space", () => {
99
+ const result = parseFilterList("blur(2px) opacity(0.5) brightness(1.2)");
100
+ expect(result).toHaveLength(3);
101
+ expect(result[0]).toEqual({ kind: "blur", value: 2 });
102
+ expect(result[1]).toEqual({ kind: "opacity", value: 0.5 });
103
+ expect(result[2]).toEqual({ kind: "brightness", value: 1.2 });
104
+ });
105
+ // Valores inválidos
106
+ it("returns undefined for unknown function", () => {
107
+ const result = parseFilterList("foo(42)");
108
+ expect(result).toBeUndefined();
109
+ });
110
+ it("drops invalid values but keeps valid ones", () => {
111
+ const result = parseFilterList("blur(5px) unknown(42) opacity(0.8)");
112
+ expect(result).toHaveLength(2);
113
+ expect(result[0].kind).toBe("blur");
114
+ expect(result[1].kind).toBe("opacity");
115
+ });
116
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ import { registerAllPropertyParsers } from "../../src/css/parsers/register-parsers.js";
2
+ import { computeStyleForElement } from "../../src/css/compute-style.js";
3
+ import { ComputedStyle } from "../../src/css/style.js";
4
+ import { makeUnitParsers } from "../../src/units/units.js";
5
+ function makeElement(inlineStyle) {
6
+ return {
7
+ nodeType: 1,
8
+ nodeName: "DIV",
9
+ tagName: "div",
10
+ getAttribute(name) {
11
+ if (name === "style") {
12
+ return inlineStyle;
13
+ }
14
+ return null;
15
+ },
16
+ hasAttribute(name) {
17
+ return name === "style";
18
+ },
19
+ querySelectorAll(_selectors) {
20
+ return [];
21
+ },
22
+ parentElement: null,
23
+ firstElementChild: null,
24
+ lastElementChild: null,
25
+ nextElementSibling: null,
26
+ previousElementSibling: null,
27
+ };
28
+ }
29
+ describe("flex shorthand parsing", () => {
30
+ beforeAll(() => {
31
+ registerAllPropertyParsers();
32
+ });
33
+ it("parses flex: 1 1 200px", () => {
34
+ const style = computeStyleForElement(makeElement("display:flex;flex:1 1 200px;"), [], new ComputedStyle(), makeUnitParsers({ viewport: { width: 800, height: 600 } }), 16);
35
+ expect(style.flexGrow).toBe(1);
36
+ expect(style.flexShrink).toBe(1);
37
+ expect(style.flexBasis).toBe(200);
38
+ });
39
+ it("parses single-value numeric flex shorthand", () => {
40
+ const style = computeStyleForElement(makeElement("display:flex;flex:2;"), [], new ComputedStyle(), makeUnitParsers({ viewport: { width: 800, height: 600 } }), 16);
41
+ expect(style.flexGrow).toBe(2);
42
+ expect(style.flexShrink).toBe(1);
43
+ expect(style.flexBasis).toBe(0);
44
+ });
45
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
1
+ import { registerAllPropertyParsers } from "../../src/css/parsers/register-parsers.js";
2
+ import { parseGap, parseGridTemplate } from "../../src/css/parsers/grid-parser.js";
3
+ import { setViewportSize } from "../../src/css/viewport.js";
4
+ import { computeStyleForElement } from "../../src/css/compute-style.js";
5
+ import { ComputedStyle } from "../../src/css/style.js";
6
+ import { makeUnitParsers } from "../../src/units/units.js";
7
+ function makeElement(inlineStyle) {
8
+ return {
9
+ nodeType: 1,
10
+ nodeName: "DIV",
11
+ tagName: "div",
12
+ getAttribute(name) {
13
+ if (name === "style") {
14
+ return inlineStyle;
15
+ }
16
+ return null;
17
+ },
18
+ hasAttribute(name) {
19
+ return name === "style";
20
+ },
21
+ querySelectorAll(_selectors) {
22
+ return [];
23
+ },
24
+ parentElement: null,
25
+ firstElementChild: null,
26
+ lastElementChild: null,
27
+ nextElementSibling: null,
28
+ previousElementSibling: null,
29
+ };
30
+ }
31
+ describe("grid clamp parsing and resolution", () => {
32
+ beforeAll(() => {
33
+ registerAllPropertyParsers();
34
+ });
35
+ beforeEach(() => {
36
+ setViewportSize(800, 600);
37
+ });
38
+ it("parses clamp() track sizes in grid-template-columns", () => {
39
+ const parsed = parseGridTemplate("clamp(150px, 20vw, 300px) 1fr");
40
+ expect(parsed).toBeDefined();
41
+ expect(parsed).toHaveLength(2);
42
+ const first = parsed?.[0];
43
+ const second = parsed?.[1];
44
+ expect(first).toEqual({
45
+ kind: "clamp",
46
+ min: 150,
47
+ preferred: 160,
48
+ max: 300,
49
+ });
50
+ expect(second).toEqual({
51
+ kind: "flex",
52
+ flex: 1,
53
+ });
54
+ });
55
+ it("parses clamp() values in gap shorthand", () => {
56
+ const parsed = parseGap("clamp(10px, 5vw, 40px)");
57
+ expect(parsed).toBeDefined();
58
+ expect(parsed?.row).toEqual({
59
+ kind: "clamp",
60
+ min: 10,
61
+ preferred: 40,
62
+ max: 40,
63
+ });
64
+ expect(parsed?.column).toEqual({
65
+ kind: "clamp",
66
+ min: 10,
67
+ preferred: 40,
68
+ max: 40,
69
+ });
70
+ });
71
+ it("resolves clamp() gap values and preserves clamp tracks in computed style", () => {
72
+ const style = computeStyleForElement(makeElement("display:grid;grid-template-columns:clamp(150px,20vw,300px) 1fr;gap:clamp(10px,5vw,40px);"), [], new ComputedStyle(), makeUnitParsers({ viewport: { width: 800, height: 600 } }), 16);
73
+ expect(style.rowGap).toBeCloseTo(40, 3);
74
+ expect(style.columnGap).toBeCloseTo(40, 3);
75
+ expect(style.trackListColumns[0]).toEqual({
76
+ kind: "clamp",
77
+ min: 150,
78
+ preferred: 160,
79
+ max: 300,
80
+ });
81
+ });
82
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import { buildCssRules } from "../../src/html/css/parse-css.js";
2
+ describe("parse-css pseudo-element rules", () => {
3
+ it("keeps terminal ::before selectors and matches the host element", () => {
4
+ const css = ".item::before { content: 'x'; color: red; }";
5
+ const parsed = buildCssRules(css);
6
+ expect(parsed.styleRules).toHaveLength(1);
7
+ const rule = parsed.styleRules[0];
8
+ expect(rule.pseudoElement).toBe("::before");
9
+ const fakeEl = {
10
+ tagName: "div",
11
+ id: "",
12
+ classList: {
13
+ contains: (cls) => cls === "item",
14
+ },
15
+ parentElement: null,
16
+ firstElementChild: null,
17
+ lastElementChild: null,
18
+ nextElementSibling: null,
19
+ previousElementSibling: null,
20
+ ownerDocument: undefined,
21
+ textContent: "",
22
+ getAttribute: (name) => (name === "class" ? "item" : null),
23
+ };
24
+ expect(rule.match(fakeEl)).toBe(true);
25
+ });
26
+ });