diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.376450581.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.376450581.png new file mode 100644 index 0000000000..9d6e1e88ca Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.376450581.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.40c24f661.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.40c24f661.png new file mode 100644 index 0000000000..06d18357b4 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.40c24f661.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.4c7826371.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.4c7826371.png new file mode 100644 index 0000000000..6008288451 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.4c7826371.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.6783cc201.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.6783cc201.png new file mode 100644 index 0000000000..750ebae9db Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.6783cc201.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.d688798b1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.d688798b1.png new file mode 100644 index 0000000000..c483ee9edf Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fill.ts.d688798b1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.011a8ae51.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.011a8ae51.png new file mode 100644 index 0000000000..17a56d8351 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.011a8ae51.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.011a8ae52.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.011a8ae52.png new file mode 100644 index 0000000000..297353194e Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.011a8ae52.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.500c32681.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.500c32681.png new file mode 100644 index 0000000000..d9d88b684a Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.500c32681.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.a4c8bf021.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.a4c8bf021.png new file mode 100644 index 0000000000..a4dea3cfc7 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.a4c8bf021.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.c54b08a71.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.c54b08a71.png new file mode 100644 index 0000000000..430fea9244 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.c54b08a71.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.f23af4fc1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.f23af4fc1.png new file mode 100644 index 0000000000..28f664438c Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/auto-fit.ts.f23af4fc1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.7926c0ee1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.7926c0ee1.png new file mode 100644 index 0000000000..07a6caa6f2 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.7926c0ee1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.796c1c6c1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.796c1c6c1.png new file mode 100644 index 0000000000..702429600f Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.796c1c6c1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.7d3efdcf1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.7d3efdcf1.png new file mode 100644 index 0000000000..fa6a692bfa Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.7d3efdcf1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.d00fec5b1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.d00fec5b1.png new file mode 100644 index 0000000000..e1f394d9fe Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.d00fec5b1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.e6d1e1401.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.e6d1e1401.png new file mode 100644 index 0000000000..0b7a567517 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.e6d1e1401.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.f5ac7eaf1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.f5ac7eaf1.png new file mode 100644 index 0000000000..22006b58e4 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/content-distribution.ts.f5ac7eaf1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.1b0cf3d51.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.1b0cf3d51.png new file mode 100644 index 0000000000..82864a59dd Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.1b0cf3d51.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.2601c00e1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.2601c00e1.png new file mode 100644 index 0000000000..d677c29fd5 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.2601c00e1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.3eb230691.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.3eb230691.png new file mode 100644 index 0000000000..254f6ab7d0 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.3eb230691.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.506319751.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.506319751.png new file mode 100644 index 0000000000..6c4ea0990f Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.506319751.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.9705d0f31.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.9705d0f31.png new file mode 100644 index 0000000000..7d19a69e07 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.9705d0f31.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.e1f93bef1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.e1f93bef1.png new file mode 100644 index 0000000000..3cff567532 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.e1f93bef1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.fbf17caa1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.fbf17caa1.png new file mode 100644 index 0000000000..b4c3fce11e Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fit-content.ts.fbf17caa1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.0c5eb1e01.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.0c5eb1e01.png new file mode 100644 index 0000000000..ba54f1e251 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.0c5eb1e01.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.1f13ba8a1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.1f13ba8a1.png new file mode 100644 index 0000000000..15c19acf11 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.1f13ba8a1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.6d37521c1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.6d37521c1.png new file mode 100644 index 0000000000..734c848064 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.6d37521c1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.bec56e441.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.bec56e441.png new file mode 100644 index 0000000000..9afb9607f3 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.bec56e441.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.bff5f7c11.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.bff5f7c11.png new file mode 100644 index 0000000000..a6dd7a2f98 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.bff5f7c11.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.cee39a3d1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.cee39a3d1.png new file mode 100644 index 0000000000..a55cbc553d Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.cee39a3d1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.e39590eb1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.e39590eb1.png new file mode 100644 index 0000000000..17ce715337 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.e39590eb1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.e9140b121.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.e9140b121.png new file mode 100644 index 0000000000..77f69249cf Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.e9140b121.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.ee1cbc591.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.ee1cbc591.png new file mode 100644 index 0000000000..3dbc630c79 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.ee1cbc591.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.fbaac5bb1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.fbaac5bb1.png new file mode 100644 index 0000000000..16893a34d6 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/fr-units.ts.fbaac5bb1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.181aa7801.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.181aa7801.png new file mode 100644 index 0000000000..424b399fb0 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.181aa7801.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.2a11b2bf1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.2a11b2bf1.png new file mode 100644 index 0000000000..dfbadfc06b Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.2a11b2bf1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.802bea7e1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.802bea7e1.png new file mode 100644 index 0000000000..e74c72276f Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.802bea7e1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.83f8e86e1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.83f8e86e1.png new file mode 100644 index 0000000000..c8881e188e Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.83f8e86e1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.e16008111.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.e16008111.png new file mode 100644 index 0000000000..71d5c1dbef Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.e16008111.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.eb1f3e561.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.eb1f3e561.png new file mode 100644 index 0000000000..1c92f65a00 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/intrinsic-sizing.ts.eb1f3e561.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.39aa26311.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.39aa26311.png new file mode 100644 index 0000000000..61870e61b1 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.39aa26311.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.ad9036821.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.ad9036821.png new file mode 100644 index 0000000000..64267f7c20 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.ad9036821.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.bf3a2c001.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.bf3a2c001.png new file mode 100644 index 0000000000..03cfd2c1f6 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.bf3a2c001.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.c453dc2b1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.c453dc2b1.png new file mode 100644 index 0000000000..6506f45fd1 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.c453dc2b1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.e0cae0421.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.e0cae0421.png new file mode 100644 index 0000000000..62628c68a6 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.e0cae0421.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.e4f7ea641.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.e4f7ea641.png new file mode 100644 index 0000000000..fc0e5a13b7 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.e4f7ea641.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.e9535f0b1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.e9535f0b1.png new file mode 100644 index 0000000000..d0324f030e Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-advanced.ts.e9535f0b1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.03c42ed21.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.03c42ed21.png new file mode 100644 index 0000000000..1387e0deab Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.03c42ed21.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.08ec7ad51.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.08ec7ad51.png new file mode 100644 index 0000000000..854080b06a Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.08ec7ad51.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.49ada4b01.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.49ada4b01.png new file mode 100644 index 0000000000..09d8865967 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.49ada4b01.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.53b15c851.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.53b15c851.png new file mode 100644 index 0000000000..3001bbca16 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.53b15c851.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.5d00cade1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.5d00cade1.png new file mode 100644 index 0000000000..db839b645b Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.5d00cade1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.63206a061.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.63206a061.png new file mode 100644 index 0000000000..2b3b43d930 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.63206a061.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.8a42302c1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.8a42302c1.png new file mode 100644 index 0000000000..a31c38d14c Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.8a42302c1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.a104a0aa1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.a104a0aa1.png new file mode 100644 index 0000000000..a355ab966c Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.a104a0aa1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.c35a6c471.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.c35a6c471.png new file mode 100644 index 0000000000..1c5de70da3 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/minmax-basic.ts.c35a6c471.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.3776f4b61.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.3776f4b61.png new file mode 100644 index 0000000000..5a554c5ad4 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.3776f4b61.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.44ee5ded1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.44ee5ded1.png new file mode 100644 index 0000000000..ee1afd9d47 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.44ee5ded1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.68a4b1f71.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.68a4b1f71.png new file mode 100644 index 0000000000..5cd0b2f5ec Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.68a4b1f71.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.70ef7a551.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.70ef7a551.png new file mode 100644 index 0000000000..cd63ad90f4 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.70ef7a551.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.d8625dc31.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.d8625dc31.png new file mode 100644 index 0000000000..b6ac707f1b Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.d8625dc31.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.e2ab2a231.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.e2ab2a231.png new file mode 100644 index 0000000000..2168e4bba9 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/spanning-intrinsic.ts.e2ab2a231.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.5a7b467f1.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.5a7b467f1.png new file mode 100644 index 0000000000..a546f8b4c7 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.5a7b467f1.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.868f21c51.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.868f21c51.png new file mode 100644 index 0000000000..4ee2c1f88f Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.868f21c51.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.bedc05541.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.bedc05541.png new file mode 100644 index 0000000000..d6211f8ddf Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.bedc05541.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.c01bb0021.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.c01bb0021.png new file mode 100644 index 0000000000..31a090db26 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.c01bb0021.png differ diff --git a/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.db37b9851.png b/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.db37b9851.png new file mode 100644 index 0000000000..aa02d78cfa Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/advanced-sizing/track-breadth.ts.db37b9851.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.19dbc6861.png b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.19dbc6861.png new file mode 100644 index 0000000000..6568de3b5d Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.19dbc6861.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.33d22a2b1.png b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.33d22a2b1.png new file mode 100644 index 0000000000..e956c2ad4b Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.33d22a2b1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.4002ab751.png b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.4002ab751.png new file mode 100644 index 0000000000..4408c7ba7d Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.4002ab751.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.421bf73e1.png b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.421bf73e1.png new file mode 100644 index 0000000000..014379b78a Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.421bf73e1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.488571ee1.png b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.488571ee1.png new file mode 100644 index 0000000000..bbbc26622d Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.488571ee1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.6527fe141.png b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.6527fe141.png new file mode 100644 index 0000000000..e8f61244f0 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.6527fe141.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.7a3335fe1.png b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.7a3335fe1.png new file mode 100644 index 0000000000..cbd6c5a5da Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.7a3335fe1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.7bbe3d471.png b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.7bbe3d471.png new file mode 100644 index 0000000000..4e6c424832 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.7bbe3d471.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.9924c9671.png b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.9924c9671.png new file mode 100644 index 0000000000..eec81555d8 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.9924c9671.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.ca98b6551.png b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.ca98b6551.png new file mode 100644 index 0000000000..1dd3879740 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.ca98b6551.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.e8dcb6091.png b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.e8dcb6091.png new file mode 100644 index 0000000000..53ac3f8741 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.e8dcb6091.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.fa6d4cbe1.png b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.fa6d4cbe1.png new file mode 100644 index 0000000000..f42d7c7bd3 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-content.ts.fa6d4cbe1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.0c974f5c1.png b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.0c974f5c1.png new file mode 100644 index 0000000000..e95a890ad9 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.0c974f5c1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.4520c7f91.png b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.4520c7f91.png new file mode 100644 index 0000000000..8bc322f6bc Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.4520c7f91.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.6334a9a11.png b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.6334a9a11.png new file mode 100644 index 0000000000..7333e5b11b Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.6334a9a11.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.6d0557f91.png b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.6d0557f91.png new file mode 100644 index 0000000000..3919e6f3b1 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.6d0557f91.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.85fe81931.png b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.85fe81931.png new file mode 100644 index 0000000000..68c8f40ba4 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.85fe81931.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.d45a88dd1.png b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.d45a88dd1.png new file mode 100644 index 0000000000..03ac39f691 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.d45a88dd1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.e8ab91891.png b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.e8ab91891.png new file mode 100644 index 0000000000..810c34ef09 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.e8ab91891.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.f025f6541.png b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.f025f6541.png new file mode 100644 index 0000000000..58951d6fef Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.f025f6541.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.f401a75d1.png b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.f401a75d1.png new file mode 100644 index 0000000000..e8e3413607 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-items.ts.f401a75d1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.0c9924dd1.png b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.0c9924dd1.png new file mode 100644 index 0000000000..a58c4d95ee Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.0c9924dd1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.169773c21.png b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.169773c21.png new file mode 100644 index 0000000000..9e7d7820fd Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.169773c21.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.42491f0d1.png b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.42491f0d1.png new file mode 100644 index 0000000000..45fc4f3b11 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.42491f0d1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.544d81581.png b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.544d81581.png new file mode 100644 index 0000000000..af91a4f096 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.544d81581.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.78e6b9e81.png b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.78e6b9e81.png new file mode 100644 index 0000000000..dffeda4bdd Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.78e6b9e81.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.861bb82f1.png b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.861bb82f1.png new file mode 100644 index 0000000000..f4d173f915 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.861bb82f1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.86c8fd671.png b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.86c8fd671.png new file mode 100644 index 0000000000..3797312645 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.86c8fd671.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.9a4b4a8e1.png b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.9a4b4a8e1.png new file mode 100644 index 0000000000..aa4f93b07a Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/align-self.ts.9a4b4a8e1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.197170b51.png b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.197170b51.png new file mode 100644 index 0000000000..a4eb536d1e Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.197170b51.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.392ca2701.png b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.392ca2701.png new file mode 100644 index 0000000000..3c75eb88c6 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.392ca2701.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.8b1e7b251.png b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.8b1e7b251.png new file mode 100644 index 0000000000..5d983459eb Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.8b1e7b251.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.958c37681.png b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.958c37681.png new file mode 100644 index 0000000000..f4bd313291 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.958c37681.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.b45d396c1.png b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.b45d396c1.png new file mode 100644 index 0000000000..70528cd563 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.b45d396c1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.d2d5e51b1.png b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.d2d5e51b1.png new file mode 100644 index 0000000000..dd3f2150a0 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.d2d5e51b1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.ed80d4081.png b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.ed80d4081.png new file mode 100644 index 0000000000..ca6808a45d Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.ed80d4081.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.fb43b7e21.png b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.fb43b7e21.png new file mode 100644 index 0000000000..ce90457f62 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/auto-margins.ts.fb43b7e21.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.07b7304e1.png b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.07b7304e1.png new file mode 100644 index 0000000000..c8eaa0b545 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.07b7304e1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.17aa0d991.png b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.17aa0d991.png new file mode 100644 index 0000000000..82d729f372 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.17aa0d991.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.4f32693a1.png b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.4f32693a1.png new file mode 100644 index 0000000000..7c4c1d584d Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.4f32693a1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.8b2f400b1.png b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.8b2f400b1.png new file mode 100644 index 0000000000..05cb068860 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.8b2f400b1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.93ddc3c41.png b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.93ddc3c41.png new file mode 100644 index 0000000000..7f18ea2c00 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.93ddc3c41.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.990efc121.png b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.990efc121.png new file mode 100644 index 0000000000..29b171fa06 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.990efc121.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.ab04435e1.png b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.ab04435e1.png new file mode 100644 index 0000000000..dac6c318ae Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.ab04435e1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.b1453ff41.png b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.b1453ff41.png new file mode 100644 index 0000000000..7af001da9b Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.b1453ff41.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.d1902aa41.png b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.d1902aa41.png new file mode 100644 index 0000000000..dac275fe2c Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.d1902aa41.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.f01314a31.png b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.f01314a31.png new file mode 100644 index 0000000000..3faa2930d3 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.f01314a31.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.f20329341.png b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.f20329341.png new file mode 100644 index 0000000000..bf18b036b9 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.f20329341.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.f7f5d8c71.png b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.f7f5d8c71.png new file mode 100644 index 0000000000..949318a898 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-content.ts.f7f5d8c71.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.0cf2b3f61.png b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.0cf2b3f61.png new file mode 100644 index 0000000000..55ba097873 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.0cf2b3f61.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.1ce3aa1c1.png b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.1ce3aa1c1.png new file mode 100644 index 0000000000..a43aac9305 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.1ce3aa1c1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.4cf99bad1.png b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.4cf99bad1.png new file mode 100644 index 0000000000..1329b6d79f Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.4cf99bad1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.56a3d35e1.png b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.56a3d35e1.png new file mode 100644 index 0000000000..25a4e85bf1 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.56a3d35e1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.6b461e121.png b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.6b461e121.png new file mode 100644 index 0000000000..061b9a5f82 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.6b461e121.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.7f7ea6771.png b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.7f7ea6771.png new file mode 100644 index 0000000000..b1c9d25761 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.7f7ea6771.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.d19b6e691.png b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.d19b6e691.png new file mode 100644 index 0000000000..e44f0ac23c Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.d19b6e691.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.df23ae741.png b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.df23ae741.png new file mode 100644 index 0000000000..40829901b7 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.df23ae741.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.e6c146861.png b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.e6c146861.png new file mode 100644 index 0000000000..3a6a788a73 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-items.ts.e6c146861.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.06725a0e1.png b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.06725a0e1.png new file mode 100644 index 0000000000..4811395735 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.06725a0e1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.1ea0e9a11.png b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.1ea0e9a11.png new file mode 100644 index 0000000000..9cb3299620 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.1ea0e9a11.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.4c6e34781.png b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.4c6e34781.png new file mode 100644 index 0000000000..978ca1d2dc Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.4c6e34781.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.66e14a881.png b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.66e14a881.png new file mode 100644 index 0000000000..09a4d7e43f Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.66e14a881.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.8deb4b011.png b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.8deb4b011.png new file mode 100644 index 0000000000..ec1e82aa0c Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.8deb4b011.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.a3fb2c4e1.png b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.a3fb2c4e1.png new file mode 100644 index 0000000000..45bd0d8ca3 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.a3fb2c4e1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.b84392b11.png b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.b84392b11.png new file mode 100644 index 0000000000..70f0a643b9 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/justify-self.ts.b84392b11.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.06e4bded1.png b/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.06e4bded1.png new file mode 100644 index 0000000000..01111b60d2 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.06e4bded1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.363516301.png b/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.363516301.png new file mode 100644 index 0000000000..b81d97e7b2 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.363516301.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.48b4670b1.png b/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.48b4670b1.png new file mode 100644 index 0000000000..6089e96923 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.48b4670b1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.5bece2781.png b/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.5bece2781.png new file mode 100644 index 0000000000..0fc721cf06 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.5bece2781.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.a5c795891.png b/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.a5c795891.png new file mode 100644 index 0000000000..bed87876bb Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-content.ts.a5c795891.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.2b2353db1.png b/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.2b2353db1.png new file mode 100644 index 0000000000..7b06187e7a Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.2b2353db1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.6149438a1.png b/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.6149438a1.png new file mode 100644 index 0000000000..f3aa6fc1c8 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.6149438a1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.c0eeca941.png b/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.c0eeca941.png new file mode 100644 index 0000000000..688e3c23a2 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.c0eeca941.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.ea634b0b1.png b/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.ea634b0b1.png new file mode 100644 index 0000000000..31bd36c63e Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.ea634b0b1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.fcc2b9d81.png b/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.fcc2b9d81.png new file mode 100644 index 0000000000..25d0d0fb32 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-items.ts.fcc2b9d81.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.36ac0c801.png b/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.36ac0c801.png new file mode 100644 index 0000000000..375a2de47e Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.36ac0c801.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.8c6397b61.png b/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.8c6397b61.png new file mode 100644 index 0000000000..d08534f738 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.8c6397b61.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.9aa22f2c1.png b/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.9aa22f2c1.png new file mode 100644 index 0000000000..2e26804e38 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.9aa22f2c1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.9f77c7f41.png b/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.9f77c7f41.png new file mode 100644 index 0000000000..b5895aa7d2 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.9f77c7f41.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.a73ef52f1.png b/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.a73ef52f1.png new file mode 100644 index 0000000000..d75788943b Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/place-self.ts.a73ef52f1.png differ diff --git a/integration_tests/snapshots/css/css-grid/alignment/writing-modes.ts.4e0035c41.png b/integration_tests/snapshots/css/css-grid/alignment/writing-modes.ts.4e0035c41.png new file mode 100644 index 0000000000..859df83b16 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/alignment/writing-modes.ts.4e0035c41.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.373ccbe61.png b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.373ccbe61.png new file mode 100644 index 0000000000..1ca872c847 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.373ccbe61.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.420ba3551.png b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.420ba3551.png new file mode 100644 index 0000000000..282fab48bd Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.420ba3551.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.6a4d5c7f1.png b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.6a4d5c7f1.png new file mode 100644 index 0000000000..0d67956303 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.6a4d5c7f1.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.c2ba759e1.png b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.c2ba759e1.png new file mode 100644 index 0000000000..d71ac297e8 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.c2ba759e1.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.c568fc711.png b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.c568fc711.png new file mode 100644 index 0000000000..93abfbcf96 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.c568fc711.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.ca9cff801.png b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.ca9cff801.png new file mode 100644 index 0000000000..aa389a3680 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.ca9cff801.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.da69c11a1.png b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.da69c11a1.png new file mode 100644 index 0000000000..81e36c9621 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/add-remove-items.ts.da69c11a1.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.002a2a651.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.002a2a651.png new file mode 100644 index 0000000000..a7654bf58a Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.002a2a651.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.002a2a652.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.002a2a652.png new file mode 100644 index 0000000000..331eda2ed5 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.002a2a652.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.2a7828011.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.2a7828011.png new file mode 100644 index 0000000000..567f60e078 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.2a7828011.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.2a7828012.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.2a7828012.png new file mode 100644 index 0000000000..a870313811 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.2a7828012.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.8d5953731.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.8d5953731.png new file mode 100644 index 0000000000..07159e9905 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.8d5953731.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.8d5953732.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.8d5953732.png new file mode 100644 index 0000000000..0721d1440d Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.8d5953732.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.908a7d221.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.908a7d221.png new file mode 100644 index 0000000000..09ed61d7a6 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.908a7d221.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.908a7d222.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.908a7d222.png new file mode 100644 index 0000000000..f79e36b547 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.908a7d222.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.936cb3341.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.936cb3341.png new file mode 100644 index 0000000000..c59e43b74c Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.936cb3341.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.936cb3342.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.936cb3342.png new file mode 100644 index 0000000000..2d68cd903f Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.936cb3342.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.a35af0f01.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.a35af0f01.png new file mode 100644 index 0000000000..6a545a469b Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.a35af0f01.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.a35af0f02.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.a35af0f02.png new file mode 100644 index 0000000000..ca8ff093a1 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.a35af0f02.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.ab99b82c1.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.ab99b82c1.png new file mode 100644 index 0000000000..d145de0bae Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.ab99b82c1.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.ab99b82c2.png b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.ab99b82c2.png new file mode 100644 index 0000000000..0e2d804bc6 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/resize.ts.ab99b82c2.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.17b43b3c1.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.17b43b3c1.png new file mode 100644 index 0000000000..442173bbd5 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.17b43b3c1.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.17b43b3c2.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.17b43b3c2.png new file mode 100644 index 0000000000..bbbbbb5f82 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.17b43b3c2.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.59f10a351.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.59f10a351.png new file mode 100644 index 0000000000..2c7e60a357 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.59f10a351.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.59f10a352.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.59f10a352.png new file mode 100644 index 0000000000..deaa789156 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.59f10a352.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.59f10a353.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.59f10a353.png new file mode 100644 index 0000000000..2c7e60a357 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.59f10a353.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.623b51a21.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.623b51a21.png new file mode 100644 index 0000000000..0f691300a2 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.623b51a21.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.623b51a22.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.623b51a22.png new file mode 100644 index 0000000000..3890acc545 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.623b51a22.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.64f14cd51.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.64f14cd51.png new file mode 100644 index 0000000000..afb6931444 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.64f14cd51.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.64f14cd52.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.64f14cd52.png new file mode 100644 index 0000000000..fe24dc969d Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.64f14cd52.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.7ad7e3ee1.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.7ad7e3ee1.png new file mode 100644 index 0000000000..72aa1bb120 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.7ad7e3ee1.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.7ad7e3ee2.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.7ad7e3ee2.png new file mode 100644 index 0000000000..165976bb58 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.7ad7e3ee2.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.a05d328e1.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.a05d328e1.png new file mode 100644 index 0000000000..bd3fd483fe Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.a05d328e1.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.a05d328e2.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.a05d328e2.png new file mode 100644 index 0000000000..9d96c29f87 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.a05d328e2.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.c924e4281.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.c924e4281.png new file mode 100644 index 0000000000..7d2669b308 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.c924e4281.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.c924e4282.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.c924e4282.png new file mode 100644 index 0000000000..06d64a129d Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.c924e4282.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.fefae61f1.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.fefae61f1.png new file mode 100644 index 0000000000..2a47d35985 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.fefae61f1.png differ diff --git a/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.fefae61f2.png b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.fefae61f2.png new file mode 100644 index 0000000000..1a2db40f23 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/dynamic/style-changes.ts.fefae61f2.png differ diff --git a/integration_tests/snapshots/css/css-grid/grid-definition/template-areas-advanced.ts.790ccafa2.png b/integration_tests/snapshots/css/css-grid/grid-definition/template-areas-advanced.ts.790ccafa2.png index 42e36e5173..75c967e727 100644 Binary files a/integration_tests/snapshots/css/css-grid/grid-definition/template-areas-advanced.ts.790ccafa2.png and b/integration_tests/snapshots/css/css-grid/grid-definition/template-areas-advanced.ts.790ccafa2.png differ diff --git a/integration_tests/snapshots/css/css-grid/grid-definition/template-areas-advanced.ts.bfcda8ab1.png b/integration_tests/snapshots/css/css-grid/grid-definition/template-areas-advanced.ts.bfcda8ab1.png index 5567486314..4c1ceb5591 100644 Binary files a/integration_tests/snapshots/css/css-grid/grid-definition/template-areas-advanced.ts.bfcda8ab1.png and b/integration_tests/snapshots/css/css-grid/grid-definition/template-areas-advanced.ts.bfcda8ab1.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.0706d33d1.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.0706d33d1.png new file mode 100644 index 0000000000..c32492a761 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.0706d33d1.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.077faca41.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.077faca41.png new file mode 100644 index 0000000000..66569d4890 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.077faca41.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.31e492451.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.31e492451.png new file mode 100644 index 0000000000..121f66c695 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.31e492451.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.424b79f61.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.424b79f61.png new file mode 100644 index 0000000000..47f5ef0abe Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.424b79f61.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.5c2d3b051.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.5c2d3b051.png new file mode 100644 index 0000000000..f18935b7b2 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.5c2d3b051.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.a89fe1c91.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.a89fe1c91.png new file mode 100644 index 0000000000..9740209ebe Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.a89fe1c91.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.e96f8b9f1.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.e96f8b9f1.png new file mode 100644 index 0000000000..f3997c1125 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-columns-extended.ts.e96f8b9f1.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.02e3c8871.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.02e3c8871.png new file mode 100644 index 0000000000..d469b978b3 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.02e3c8871.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.2e552a8a1.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.2e552a8a1.png new file mode 100644 index 0000000000..1bb3cee765 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.2e552a8a1.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.4d69d7a61.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.4d69d7a61.png new file mode 100644 index 0000000000..4f238a0567 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.4d69d7a61.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.763bfc801.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.763bfc801.png new file mode 100644 index 0000000000..734c848064 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.763bfc801.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.9f5f27f11.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.9f5f27f11.png new file mode 100644 index 0000000000..3c1be352d6 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.9f5f27f11.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.b0acb6d01.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.b0acb6d01.png new file mode 100644 index 0000000000..1c943a6c67 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.b0acb6d01.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.b12cbbc91.png b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.b12cbbc91.png new file mode 100644 index 0000000000..626b11d12a Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/auto-rows.ts.b12cbbc91.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.4dc6b2c31.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.4dc6b2c31.png new file mode 100644 index 0000000000..4adfb34c33 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.4dc6b2c31.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.7e544e871.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.7e544e871.png new file mode 100644 index 0000000000..98a21836a1 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.7e544e871.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.8c9f9cf51.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.8c9f9cf51.png new file mode 100644 index 0000000000..dd92868f14 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.8c9f9cf51.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.b1659c8f1.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.b1659c8f1.png new file mode 100644 index 0000000000..292902bb0d Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.b1659c8f1.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.cf7d16e21.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.cf7d16e21.png new file mode 100644 index 0000000000..32e217c76a Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.cf7d16e21.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.d7dfa4061.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.d7dfa4061.png new file mode 100644 index 0000000000..cef7cd47b7 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.d7dfa4061.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.fdae6c611.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.fdae6c611.png new file mode 100644 index 0000000000..50ec2abf27 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-creation.ts.fdae6c611.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.02d89dd41.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.02d89dd41.png new file mode 100644 index 0000000000..6270181755 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.02d89dd41.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.4836171b1.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.4836171b1.png new file mode 100644 index 0000000000..3463ee6945 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.4836171b1.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.7ad472d71.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.7ad472d71.png new file mode 100644 index 0000000000..eb2f104643 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.7ad472d71.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.d46314051.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.d46314051.png new file mode 100644 index 0000000000..fe412e188c Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.d46314051.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.e1b8bae21.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.e1b8bae21.png new file mode 100644 index 0000000000..55e827fbe1 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.e1b8bae21.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.e459160d1.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.e459160d1.png new file mode 100644 index 0000000000..9d2ce19eec Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-named-lines.ts.e459160d1.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.0363b92b1.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.0363b92b1.png new file mode 100644 index 0000000000..bb2fe27e45 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.0363b92b1.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.40f1dd411.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.40f1dd411.png new file mode 100644 index 0000000000..ef867c687d Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.40f1dd411.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.445ab2f81.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.445ab2f81.png new file mode 100644 index 0000000000..8a0617873b Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.445ab2f81.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.7a8d6cc41.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.7a8d6cc41.png new file mode 100644 index 0000000000..b7e0a4de39 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.7a8d6cc41.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.91ce14681.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.91ce14681.png new file mode 100644 index 0000000000..34f0551dd5 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.91ce14681.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.bdc428b81.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.bdc428b81.png new file mode 100644 index 0000000000..6b4834aa11 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.bdc428b81.png differ diff --git a/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.fd1775121.png b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.fd1775121.png new file mode 100644 index 0000000000..1c2cd8eb03 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/implicit-grids/implicit-with-gaps.ts.fd1775121.png differ diff --git a/integration_tests/snapshots/css/css-grid/placement/overlapping-items.ts.49880d8c1.png b/integration_tests/snapshots/css/css-grid/placement/overlapping-items.ts.49880d8c1.png new file mode 100644 index 0000000000..6d12bdff58 Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/placement/overlapping-items.ts.49880d8c1.png differ diff --git a/integration_tests/snapshots/css/css-grid/placement/overlapping-items.ts.5aefef961.png b/integration_tests/snapshots/css/css-grid/placement/overlapping-items.ts.5aefef961.png new file mode 100644 index 0000000000..6b6d16851c Binary files /dev/null and b/integration_tests/snapshots/css/css-grid/placement/overlapping-items.ts.5aefef961.png differ diff --git a/integration_tests/specs/css/css-grid/advanced-sizing/auto-fill.ts b/integration_tests/specs/css/css-grid/advanced-sizing/auto-fill.ts new file mode 100644 index 0000000000..4a82fd3931 --- /dev/null +++ b/integration_tests/specs/css/css-grid/advanced-sizing/auto-fill.ts @@ -0,0 +1,166 @@ +describe('CSS Grid auto-fill', () => { + it('creates tracks with auto-fill and fixed size', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(auto-fill, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '350px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 5; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726', '#BA68C8', '#9575CD'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Should fit 3 columns (350/100 = 3), wrapping to rows + expect(items[0].getBoundingClientRect().width).toBe(100); + expect(items[1].getBoundingClientRect().width).toBe(100); + expect(items[2].getBoundingClientRect().width).toBe(100); + + grid.remove(); + }); + + it('creates empty tracks with auto-fill', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(auto-fill, 80px)'; + grid.style.gridTemplateRows = '70px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 2; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#2196F3', '#1E88E5'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Should create tracks even if not all filled + const items = Array.from(grid.children) as HTMLElement[]; + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBe(80); + }); + + grid.remove(); + }); + + it('uses auto-fill with minmax', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(100px, 1fr))'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '350px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 5; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0', '#8E24AA', '#7B1FA2'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Should fit 3 columns at minimum (350/100 = 3) + items.slice(0, 3).forEach(item => { + expect(item.getBoundingClientRect().width).toBeGreaterThanOrEqual(100); + }); + + grid.remove(); + }); + + it('handles auto-fill with gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(auto-fill, 90px)'; + grid.style.gridTemplateRows = '70px'; + grid.style.width = '300px'; + grid.style.columnGap = '10px'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800', '#FB8C00'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // With gaps: 90 + 10 + 90 + 10 + 90 = 290px, fits 3 columns + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBe(90); + }); + + grid.remove(); + }); + + it('handles auto-fill with small items', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(60px, 1fr))'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '250px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Should fit 4 columns (250/60 = 4), but only 3 items + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBeGreaterThanOrEqual(60); + }); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/advanced-sizing/auto-fit.ts b/integration_tests/specs/css/css-grid/advanced-sizing/auto-fit.ts new file mode 100644 index 0000000000..59261085ef --- /dev/null +++ b/integration_tests/specs/css/css-grid/advanced-sizing/auto-fit.ts @@ -0,0 +1,187 @@ +describe('CSS Grid auto-fit', () => { + it('collapses empty tracks with auto-fit', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(auto-fit, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '350px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 2; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#42A5F5', '#66BB6A'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // auto-fit collapses empty tracks, items may stretch + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBeGreaterThanOrEqual(100); + }); + + grid.remove(); + }); + + it('uses auto-fit with minmax to stretch items', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(auto-fit, minmax(100px, 1fr))'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '350px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 2; i++) { + const item = document.createElement('div'); + item.textContent = `Item ${i + 1}`; + item.style.backgroundColor = ['#2196F3', '#1E88E5'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should stretch to fill available space + expect(items[0].getBoundingClientRect().width).toBeGreaterThan(100); + expect(items[1].getBoundingClientRect().width).toBeGreaterThan(100); + + grid.remove(); + }); + + it('handles auto-fit with gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(auto-fit, minmax(80px, 1fr))'; + grid.style.gridTemplateRows = '70px'; + grid.style.width = '300px'; + grid.style.columnGap = '10px'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBeGreaterThanOrEqual(80); + }); + + grid.remove(); + }); + + it('compares auto-fit vs auto-fill behavior', async () => { + const grid1 = document.createElement('div'); + grid1.style.display = 'grid'; + grid1.style.gridTemplateColumns = 'repeat(auto-fill, 100px)'; + grid1.style.gridTemplateRows = '70px'; + grid1.style.width = '350px'; + grid1.style.gap = '0'; + grid1.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 2; i++) { + const item = document.createElement('div'); + item.textContent = `Fill ${i + 1}`; + item.style.backgroundColor = ['#FFB74D', '#FFA726'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid1.appendChild(item); + } + + document.body.appendChild(grid1); + await waitForFrame(); + await snapshot(); + + const fillItems = Array.from(grid1.children) as HTMLElement[]; + expect(fillItems[0].getBoundingClientRect().width).toBe(100); + + grid1.remove(); + + const grid2 = document.createElement('div'); + grid2.style.display = 'grid'; + grid2.style.gridTemplateColumns = 'repeat(auto-fit, minmax(100px, 1fr))'; + grid2.style.gridTemplateRows = '70px'; + grid2.style.width = '350px'; + grid2.style.gap = '0'; + grid2.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 2; i++) { + const item = document.createElement('div'); + item.textContent = `Fit ${i + 1}`; + item.style.backgroundColor = ['#66BB6A', '#4CAF50'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid2.appendChild(item); + } + + document.body.appendChild(grid2); + await waitForFrame(); + await snapshot(); + + const fitItems = Array.from(grid2.children) as HTMLElement[]; + // auto-fit items should be wider (stretched) + expect(fitItems[0].getBoundingClientRect().width).toBeGreaterThan(100); + + grid2.remove(); + }); + + it('handles auto-fit with single item', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(auto-fit, minmax(100px, 1fr))'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + const item = document.createElement('div'); + item.textContent = 'Single item'; + item.style.backgroundColor = '#9575CD'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Single item should stretch to fill available space + expect(item.getBoundingClientRect().width).toBe(300); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/advanced-sizing/content-distribution.ts b/integration_tests/specs/css/css-grid/advanced-sizing/content-distribution.ts new file mode 100644 index 0000000000..94e1a4280f --- /dev/null +++ b/integration_tests/specs/css/css-grid/advanced-sizing/content-distribution.ts @@ -0,0 +1,216 @@ +describe('CSS Grid content distribution', () => { + it('distributes extra space with fr units', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px 1fr 2fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '400px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = ['100px', '1fr', '2fr'][i]; + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '12px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // 400px - 100px = 300px to distribute (1fr + 2fr = 3fr) + expect(items[0].getBoundingClientRect().width).toBe(100); + expect(items[1].getBoundingClientRect().width).toBe(100); // 1/3 of 300px + expect(items[2].getBoundingClientRect().width).toBe(200); // 2/3 of 300px + + grid.remove(); + }); + + it('handles space distribution with minmax', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(100px, 1fr) minmax(150px, 2fr)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '450px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 2; i++) { + const item = document.createElement('div'); + item.textContent = `Track ${i + 1}`; + item.style.backgroundColor = ['#2196F3', '#1E88E5'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // grid-template-columns: minmax(100px, 1fr) minmax(150px, 2fr) + // Total Width: 450px + expect(items[0].getBoundingClientRect().width).toBeCloseTo(150, 0); // 450 × 1/3 + expect(items[1].getBoundingClientRect().width).toBeCloseTo(300, 0); // 450 × 2/3 + + grid.remove(); + }); + + it('distributes space with auto tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'auto 1fr auto'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '400px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + const item1 = document.createElement('div'); + item1.textContent = 'Auto 1'; + item1.style.backgroundColor = '#BA68C8'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + item1.style.fontSize = '11px'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = '1fr'; + item2.style.backgroundColor = '#AB47BC'; + item2.style.display = 'flex'; + item2.style.alignItems = 'center'; + item2.style.justifyContent = 'center'; + item2.style.color = 'white'; + grid.appendChild(item2); + + const item3 = document.createElement('div'); + item3.textContent = 'Auto 2'; + item3.style.backgroundColor = '#9C27B0'; + item3.style.padding = '10px'; + item3.style.color = 'white'; + item3.style.fontSize = '11px'; + grid.appendChild(item3); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Auto tracks take minimum space, fr gets remaining + expect(items[1].getBoundingClientRect().width).toBeGreaterThan(0); + + grid.remove(); + }); + + it('handles unequal fr distribution', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '0.5fr 1fr 1.5fr 2fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '500px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = ['0.5fr', '1fr', '1.5fr', '2fr'][i]; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800', '#FB8C00'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Total: 5fr, 500px / 5 = 100px per fr + expect(items[0].getBoundingClientRect().width).toBe(50); // 0.5fr + expect(items[1].getBoundingClientRect().width).toBe(100); // 1fr + expect(items[2].getBoundingClientRect().width).toBe(150); // 1.5fr + expect(items[3].getBoundingClientRect().width).toBe(200); // 2fr + + grid.remove(); + }); + + it('distributes remaining space after fixed tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '80px 20% 1fr 2fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '480px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = ['80px', '20%', '1fr', '2fr'][i]; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047', '#388E3C'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // 80px + 96px (20%) = 176px, remaining 304px for 3fr + expect(items[0].getBoundingClientRect().width).toBe(80); + expect(items[1].getBoundingClientRect().width).toBeCloseTo(96, 0); + expect(items[2].getBoundingClientRect().width).toBeCloseTo(101, 0); // ~1/3 of 304 + expect(items[3].getBoundingClientRect().width).toBeCloseTo(203, 0); // ~2/3 of 304 + + grid.remove(); + }); + + it('handles content distribution with gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '1fr 2fr 1fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '400px'; + grid.style.columnGap = '20px'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = ['1fr', '2fr', '1fr'][i]; + item.style.backgroundColor = ['#9575CD', '#7E57C2', '#673AB7'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // 400px - 40px (gaps) = 360px, distributed as 1:2:1 + expect(items[0].getBoundingClientRect().width).toBe(90); // 1/4 of 360 + expect(items[1].getBoundingClientRect().width).toBe(180); // 2/4 of 360 + expect(items[2].getBoundingClientRect().width).toBe(90); // 1/4 of 360 + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/advanced-sizing/fit-content.ts b/integration_tests/specs/css/css-grid/advanced-sizing/fit-content.ts new file mode 100644 index 0000000000..26ad296ec1 --- /dev/null +++ b/integration_tests/specs/css/css-grid/advanced-sizing/fit-content.ts @@ -0,0 +1,217 @@ +describe('CSS Grid fit-content()', () => { + it('sizes track with fit-content()', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'fit-content(200px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + const item = document.createElement('div'); + item.textContent = 'fit-content(200px)'; + item.style.backgroundColor = '#42A5F5'; + item.style.padding = '10px'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Should fit content, clamped to 200px + expect(item.getBoundingClientRect().width).toBeLessThan(200); + + grid.remove(); + }); + + it('uses fit-content with small content', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'fit-content(300px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '350px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + const item = document.createElement('div'); + item.textContent = 'Small'; + item.style.backgroundColor = '#2196F3'; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Should shrink to content size + expect(item.getBoundingClientRect().width).toBeLessThan(300); + + grid.remove(); + }); + + it('uses fit-content with large content', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'fit-content(150px)'; + grid.style.gridTemplateRows = 'auto'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + const item = document.createElement('div'); + item.textContent = 'This is very long content that exceeds the fit-content limit'; + item.style.backgroundColor = '#BA68C8'; + item.style.padding = '10px'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Should clamp to max (150px) + expect(item.getBoundingClientRect().width).toBeLessThanOrEqual(150); + + grid.remove(); + }); + + it('uses multiple fit-content tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'fit-content(100px) fit-content(150px) fit-content(120px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + const texts = ['Short', 'Medium text', 'Longer']; + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = texts[i]; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBeLessThanOrEqual(100); + expect(items[1].getBoundingClientRect().width).toBeLessThanOrEqual(150); + expect(items[2].getBoundingClientRect().width).toBeLessThanOrEqual(120); + + grid.remove(); + }); + + it('combines fit-content with fixed and fr units', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px fit-content(150px) 1fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '400px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + const item1 = document.createElement('div'); + item1.textContent = 'Fixed'; + item1.style.backgroundColor = '#66BB6A'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'Fit'; + item2.style.backgroundColor = '#4CAF50'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + grid.appendChild(item2); + + const item3 = document.createElement('div'); + item3.textContent = '1fr'; + item3.style.backgroundColor = '#43A047'; + item3.style.display = 'flex'; + item3.style.alignItems = 'center'; + item3.style.justifyContent = 'center'; + item3.style.color = 'white'; + grid.appendChild(item3); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBe(100); + expect(items[1].getBoundingClientRect().width).toBeLessThanOrEqual(150); + expect(items[2].getBoundingClientRect().width).toBeGreaterThan(0); + + grid.remove(); + }); + + it('uses fit-content in rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '200px'; + grid.style.gridTemplateRows = 'fit-content(100px) fit-content(80px)'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + const item1 = document.createElement('div'); + item1.textContent = 'Short content'; + item1.style.backgroundColor = '#9575CD'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'Another row'; + item2.style.backgroundColor = '#7E57C2'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().height).toBeLessThan(100); + expect(items[1].getBoundingClientRect().height).toBeLessThan(80); + + grid.remove(); + }); + + it('handles fit-content with percentage', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'fit-content(50%)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e0f2f1'; + + const item = document.createElement('div'); + item.textContent = 'fit-content(50%)'; + item.style.backgroundColor = '#4DB6AC'; + item.style.padding = '10px'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Should clamp to 50% of 300px = 150px + expect(item.getBoundingClientRect().width).toBeLessThanOrEqual(150); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/advanced-sizing/fr-units.ts b/integration_tests/specs/css/css-grid/advanced-sizing/fr-units.ts new file mode 100644 index 0000000000..ac6d1444b1 --- /dev/null +++ b/integration_tests/specs/css/css-grid/advanced-sizing/fr-units.ts @@ -0,0 +1,343 @@ +describe('CSS Grid fractional (fr) units', () => { + it('distributes space with single fr unit', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '1fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + const item = document.createElement('div'); + item.textContent = '1fr'; + item.style.backgroundColor = '#42A5F5'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(item.getBoundingClientRect().width).toBe(300); + + grid.remove(); + }); + + it('distributes space equally with multiple fr units', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '1fr 1fr 1fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = '1fr'; + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBe(100); + }); + + grid.remove(); + }); + + it('distributes space proportionally with different fr values', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '1fr 2fr 3fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '360px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}fr`; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Total: 1+2+3 = 6fr, 360px / 6 = 60px per fr + expect(items[0].getBoundingClientRect().width).toBe(60); // 1fr + expect(items[1].getBoundingClientRect().width).toBe(120); // 2fr + expect(items[2].getBoundingClientRect().width).toBe(180); // 3fr + + grid.remove(); + }); + + it('combines fr units with fixed sizes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px 1fr 2fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '400px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = ['100px', '1fr', '2fr'][i]; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '12px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // 400px - 100px = 300px remaining, divided into 3fr + expect(items[0].getBoundingClientRect().width).toBe(100); // Fixed + expect(items[1].getBoundingClientRect().width).toBe(100); // 1fr = 100px + expect(items[2].getBoundingClientRect().width).toBe(200); // 2fr = 200px + + grid.remove(); + }); + + it('combines fr units with percentages', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '20% 1fr 2fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = ['20%', '1fr', '2fr'][i]; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '12px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // 20% of 300px = 60px, remaining 240px divided into 3fr + expect(items[0].getBoundingClientRect().width).toBe(60); // 20% + expect(items[1].getBoundingClientRect().width).toBe(80); // 1fr + expect(items[2].getBoundingClientRect().width).toBe(160); // 2fr + + grid.remove(); + }); + + it('handles fr units with gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '1fr 1fr 1fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '320px'; + grid.style.columnGap = '10px'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = '1fr'; + item.style.backgroundColor = ['#9575CD', '#7E57C2', '#673AB7'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // 320px - 20px (gaps) = 300px, divided into 3fr + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBe(100); + }); + + grid.remove(); + }); + + it('distributes fr units in rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '150px'; + grid.style.gridTemplateRows = '1fr 2fr 1fr'; + grid.style.height = '200px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e0f2f1'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = ['1fr', '2fr', '1fr'][i]; + item.style.backgroundColor = ['#4DB6AC', '#26A69A', '#009688'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Total: 4fr, 200px / 4 = 50px per fr + expect(items[0].getBoundingClientRect().height).toBe(50); // 1fr + expect(items[1].getBoundingClientRect().height).toBe(100); // 2fr + expect(items[2].getBoundingClientRect().height).toBe(50); // 1fr + + grid.remove(); + }); + + it('uses fr units in grid-auto-rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '50px'; + grid.style.gridAutoRows = '1fr'; + grid.style.height = '250px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047', '#388E3C', '#2E7D32', '#1B5E20'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // First row: 50px explicit + expect(items[0].getBoundingClientRect().height).toBe(50); + // Remaining space (200px) divided by 2 implicit rows = 100px each + expect(items[2].getBoundingClientRect().height).toBe(100); + expect(items[4].getBoundingClientRect().height).toBe(100); + + grid.remove(); + }); + + it('handles fractional fr values', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '0.5fr 1fr 1.5fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fce4ec'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = ['0.5fr', '1fr', '1.5fr'][i]; + item.style.backgroundColor = ['#F06292', '#EC407A', '#E91E63'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Total: 3fr, 300px / 3 = 100px per fr + expect(items[0].getBoundingClientRect().width).toBe(50); // 0.5fr + expect(items[1].getBoundingClientRect().width).toBe(100); // 1fr + expect(items[2].getBoundingClientRect().width).toBe(150); // 1.5fr + + grid.remove(); + }); + + it('handles fr units with auto tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'auto 1fr 2fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '350px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff9c4'; + + const item1 = document.createElement('div'); + item1.textContent = 'Auto'; + item1.style.backgroundColor = '#FFEB3B'; + item1.style.padding = '10px'; + item1.style.whiteSpace = 'nowrap'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = '1fr'; + item2.style.backgroundColor = '#FDD835'; + item2.style.display = 'flex'; + item2.style.alignItems = 'center'; + item2.style.justifyContent = 'center'; + item2.style.color = 'white'; + grid.appendChild(item2); + + const item3 = document.createElement('div'); + item3.textContent = '2fr'; + item3.style.backgroundColor = '#FBC02D'; + item3.style.display = 'flex'; + item3.style.alignItems = 'center'; + item3.style.justifyContent = 'center'; + item3.style.color = 'white'; + grid.appendChild(item3); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Auto takes minimum space, remaining divided into 3fr + expect(items[0].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[1].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[2].getBoundingClientRect().width).toBeGreaterThan(0); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/advanced-sizing/intrinsic-sizing.ts b/integration_tests/specs/css/css-grid/advanced-sizing/intrinsic-sizing.ts new file mode 100644 index 0000000000..a587dda65f --- /dev/null +++ b/integration_tests/specs/css/css-grid/advanced-sizing/intrinsic-sizing.ts @@ -0,0 +1,195 @@ +describe('CSS Grid intrinsic sizing', () => { + it('uses min-content for track sizing', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'min-content min-content'; + grid.style.gridTemplateRows = '80px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#f5f5f5'; + + const item1 = document.createElement('div'); + item1.textContent = 'Short'; + item1.style.backgroundColor = '#42A5F5'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'Longer text'; + item2.style.backgroundColor = '#66BB6A'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Both should shrink to min-content + expect(items[0].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[1].getBoundingClientRect().width).toBeGreaterThan(items[0].getBoundingClientRect().width); + + grid.remove(); + }); + + it('uses max-content for track sizing', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'max-content max-content'; + grid.style.gridTemplateRows = '80px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e3f2fd'; + + const item1 = document.createElement('div'); + item1.textContent = 'Short'; + item1.style.backgroundColor = '#2196F3'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'Much longer text here'; + item2.style.backgroundColor = '#1E88E5'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Both should expand to max-content (no wrapping) + expect(items[0].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[1].getBoundingClientRect().width).toBeGreaterThan(items[0].getBoundingClientRect().width); + + grid.remove(); + }); + + it('combines min-content and max-content', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'min-content max-content'; + grid.style.gridTemplateRows = '80px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#f3e5f5'; + + const item1 = document.createElement('div'); + item1.textContent = 'Text wraps'; + item1.style.backgroundColor = '#BA68C8'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'No wrap here'; + item2.style.backgroundColor = '#AB47BC'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[1].getBoundingClientRect().width).toBeGreaterThan(items[0].getBoundingClientRect().width); + + grid.remove(); + }); + + it('uses intrinsic sizing with auto tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'auto min-content max-content'; + grid.style.gridTemplateRows = '80px'; + grid.style.gap = '5px'; + grid.style.backgroundColor = '#fff3e0'; + + const texts = ['Auto', 'Min', 'Max content']; + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = texts[i]; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[1].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[2].getBoundingClientRect().width).toBeGreaterThan(0); + + grid.remove(); + }); + + it('handles intrinsic sizing with images', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'min-content max-content'; + grid.style.gridTemplateRows = 'auto'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e8f5e9'; + + const item1 = document.createElement('div'); + item1.style.width = '50px'; + item1.style.height = '50px'; + item1.style.backgroundColor = '#66BB6A'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.style.width = '100px'; + item2.style.height = '60px'; + item2.style.backgroundColor = '#4CAF50'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBe(50); + expect(items[1].getBoundingClientRect().width).toBe(100); + + grid.remove(); + }); + + it('uses intrinsic sizing in rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '200px'; + grid.style.gridTemplateRows = 'min-content max-content'; + grid.style.gap = '5px'; + grid.style.backgroundColor = '#ede7f6'; + + const item1 = document.createElement('div'); + item1.textContent = 'Min content height'; + item1.style.backgroundColor = '#9575CD'; + item1.style.padding = '5px'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'Max content height with more text'; + item2.style.backgroundColor = '#7E57C2'; + item2.style.padding = '5px'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().height).toBeGreaterThan(0); + expect(items[1].getBoundingClientRect().height).toBeGreaterThan(0); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/advanced-sizing/minmax-advanced.ts b/integration_tests/specs/css/css-grid/advanced-sizing/minmax-advanced.ts new file mode 100644 index 0000000000..a28987097c --- /dev/null +++ b/integration_tests/specs/css/css-grid/advanced-sizing/minmax-advanced.ts @@ -0,0 +1,221 @@ +describe('CSS Grid minmax() advanced', () => { + it('handles minmax with fit-content max', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(100px, fit-content(200px))'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + const item = document.createElement('div'); + item.textContent = 'minmax with fit-content'; + item.style.backgroundColor = '#42A5F5'; + item.style.padding = '10px'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // The column should fill the grid container (300px) + expect(item.getBoundingClientRect().width).toBeCloseTo(300, 0); + + // Or at minimum, verify it respects the 100px floor + expect(item.getBoundingClientRect().width).toBeGreaterThanOrEqual(100); + + grid.remove(); + }); + + it('handles nested minmax in repeat', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, minmax(80px, 1fr))'; + grid.style.gridTemplateRows = '70px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBe(100); + }); + + grid.remove(); + }); + + it('handles minmax with calc() values', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(calc(100px - 20px), calc(150px + 50px))'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + const item = document.createElement('div'); + item.textContent = 'minmax with calc'; + item.style.backgroundColor = '#BA68C8'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // min: 80px, max: 200px + expect(item.getBoundingClientRect().width).toBeGreaterThanOrEqual(80); + expect(item.getBoundingClientRect().width).toBeLessThanOrEqual(200); + + grid.remove(); + }); + + it('handles minmax with conflicting min/max', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(200px, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + const item = document.createElement('div'); + item.textContent = 'minmax(200px, 100px)'; + item.style.backgroundColor = '#FFB74D'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // When min > max, min wins + expect(item.getBoundingClientRect().width).toBe(200); + + grid.remove(); + }); + + it('handles minmax with spanning items', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, minmax(70px, 1fr))'; + grid.style.gridTemplateRows = 'repeat(2, 80px)'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + const item1 = document.createElement('div'); + item1.textContent = 'Span 2'; + item1.style.gridColumn = 'span 2'; + item1.style.backgroundColor = '#66BB6A'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = '1'; + item2.style.backgroundColor = '#4CAF50'; + item2.style.display = 'flex'; + item2.style.alignItems = 'center'; + item2.style.justifyContent = 'center'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBe(200); + expect(items[1].getBoundingClientRect().width).toBe(100); + + grid.remove(); + }); + + it('handles minmax with max-content min', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(max-content, 1fr)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + const item = document.createElement('div'); + item.textContent = 'Short text'; + item.style.backgroundColor = '#9575CD'; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Should be at least max-content width + expect(item.getBoundingClientRect().width).toBeGreaterThan(0); + + grid.remove(); + }); + + it('handles minmax with intrinsic sizes and gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, minmax(min-content, max-content))'; + grid.style.gridTemplateRows = '80px'; + grid.style.columnGap = '10px'; + grid.style.backgroundColor = '#e0f2f1'; + + const item1 = document.createElement('div'); + item1.textContent = 'Short'; + item1.style.backgroundColor = '#4DB6AC'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'Much longer text here'; + item2.style.backgroundColor = '#26A69A'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + item2.style.fontSize = '11px'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[1].getBoundingClientRect().width).toBeGreaterThan(items[0].getBoundingClientRect().width); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/advanced-sizing/minmax-basic.ts b/integration_tests/specs/css/css-grid/advanced-sizing/minmax-basic.ts new file mode 100644 index 0000000000..79e71a746b --- /dev/null +++ b/integration_tests/specs/css/css-grid/advanced-sizing/minmax-basic.ts @@ -0,0 +1,284 @@ +describe('CSS Grid minmax() basic', () => { + it('uses minmax with fixed min and max', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(100px, 200px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + const item = document.createElement('div'); + item.textContent = 'minmax(100px, 200px)'; + item.style.backgroundColor = '#42A5F5'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Should clamp to max (200px) since grid is 300px + expect(item.getBoundingClientRect().width).toBe(200); + + grid.remove(); + }); + + it('uses minmax with min and 1fr', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(100px, 1fr) minmax(100px, 1fr)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 2; i++) { + const item = document.createElement('div'); + item.textContent = 'minmax(100px, 1fr)'; + item.style.backgroundColor = ['#2196F3', '#1E88E5'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '10px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Should distribute equally at 150px each + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBe(150); + }); + + grid.remove(); + }); + + it('respects minimum size', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(150px, 1fr) 50px'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '180px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + const item1 = document.createElement('div'); + item1.textContent = 'minmax(150px, 1fr)'; + item1.style.backgroundColor = '#BA68C8'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + item1.style.fontSize = '10px'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = '50px'; + item2.style.backgroundColor = '#AB47BC'; + item2.style.display = 'flex'; + item2.style.alignItems = 'center'; + item2.style.justifyContent = 'center'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Should enforce minimum even if grid is smaller + expect(items[0].getBoundingClientRect().width).toBeGreaterThanOrEqual(150); + expect(items[1].getBoundingClientRect().width).toBe(50); + + grid.remove(); + }); + + it('uses minmax with percentage values', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(20%, 50%)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + const item = document.createElement('div'); + item.textContent = 'minmax(20%, 50%)'; + item.style.backgroundColor = '#FFB74D'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Should use max (50% of 300px = 150px) + expect(item.getBoundingClientRect().width).toBe(150); + + grid.remove(); + }); + + it('uses minmax with auto as min', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(auto, 200px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + const item = document.createElement('div'); + item.textContent = 'minmax(auto, 200px)'; + item.style.backgroundColor = '#66BB6A'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Auto as min means min-content, max is 200px + expect(item.getBoundingClientRect().width).toBeLessThanOrEqual(200); + + grid.remove(); + }); + + it('uses minmax with auto as max', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(100px, auto)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + const item = document.createElement('div'); + item.textContent = 'minmax(100px, auto)'; + item.style.backgroundColor = '#9575CD'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Auto as max means max-content, at least 100px + expect(item.getBoundingClientRect().width).toBeGreaterThanOrEqual(100); + + grid.remove(); + }); + + it('combines multiple minmax tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(50px, 1fr) minmax(100px, 2fr) minmax(80px, 1fr)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '400px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e0f2f1'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `Track ${i + 1}`; + item.style.backgroundColor = ['#4DB6AC', '#26A69A', '#009688'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Total fr: 1+2+1 = 4fr, but mins: 50+100+80 = 230px + // Remaining: 400-230 = 170px, distributed as 1fr:2fr:1fr + expect(items[0].getBoundingClientRect().width).toBeGreaterThanOrEqual(50); + expect(items[1].getBoundingClientRect().width).toBeGreaterThanOrEqual(100); + expect(items[2].getBoundingClientRect().width).toBeGreaterThanOrEqual(80); + + grid.remove(); + }); + + it('uses minmax with min-content and max-content', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(min-content, max-content)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fce4ec'; + + const item = document.createElement('div'); + item.textContent = 'minmax(min-content, max-content)'; + item.style.backgroundColor = '#F06292'; + item.style.padding = '10px'; + item.style.color = 'white'; + item.style.fontSize = '10px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(item.getBoundingClientRect().width).toBeGreaterThan(0); + + grid.remove(); + }); + + it('uses minmax in row sizing', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '150px'; + grid.style.gridTemplateRows = 'minmax(50px, 100px) minmax(60px, 1fr)'; + grid.style.height = '200px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff9c4'; + + for (let i = 0; i < 2; i++) { + const item = document.createElement('div'); + item.textContent = `Row ${i + 1}`; + item.style.backgroundColor = ['#FFEB3B', '#FDD835'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Row 1: clamped to max (100px) + expect(items[0].getBoundingClientRect().height).toBe(100); + // Row 2: takes remaining space (100px), which is > 60px min + expect(items[1].getBoundingClientRect().height).toBe(100); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/advanced-sizing/spanning-intrinsic.ts b/integration_tests/specs/css/css-grid/advanced-sizing/spanning-intrinsic.ts new file mode 100644 index 0000000000..1964a93579 --- /dev/null +++ b/integration_tests/specs/css/css-grid/advanced-sizing/spanning-intrinsic.ts @@ -0,0 +1,265 @@ +describe('CSS Grid spanning with intrinsic sizes', () => { + it('handles spanning items with min-content tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'min-content min-content min-content'; + grid.style.gridTemplateRows = '80px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#f5f5f5'; + + const item1 = document.createElement('div'); + item1.textContent = 'Span 2 columns'; + item1.style.gridColumn = 'span 2'; + item1.style.backgroundColor = '#42A5F5'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + item1.style.fontSize = '11px'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'Single'; + item2.style.backgroundColor = '#66BB6A'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[1].getBoundingClientRect().width).toBeGreaterThan(0); + + grid.remove(); + }); + + it('handles spanning items with max-content tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'max-content max-content'; + grid.style.gridTemplateRows = 'repeat(2, 80px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e3f2fd'; + + const item1 = document.createElement('div'); + item1.textContent = 'Regular column'; + item1.style.backgroundColor = '#2196F3'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + item1.style.fontSize = '11px'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'Short'; + item2.style.backgroundColor = '#1E88E5'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + grid.appendChild(item2); + + const item3 = document.createElement('div'); + item3.textContent = 'Spanning item across both columns'; + item3.style.gridColumn = 'span 2'; + item3.style.backgroundColor = '#1976D2'; + item3.style.padding = '10px'; + item3.style.color = 'white'; + item3.style.fontSize = '11px'; + grid.appendChild(item3); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[2].getBoundingClientRect().width).toBeGreaterThan(items[0].getBoundingClientRect().width); + + grid.remove(); + }); + + it('handles spanning with mixed intrinsic and fixed tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px min-content max-content'; + grid.style.gridTemplateRows = '80px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#f3e5f5'; + + const item1 = document.createElement('div'); + item1.textContent = 'Fixed'; + item1.style.backgroundColor = '#BA68C8'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'Spanning min and max'; + item2.style.gridColumn = '2 / 4'; + item2.style.backgroundColor = '#AB47BC'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + item2.style.fontSize = '11px'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBe(100); + expect(items[1].getBoundingClientRect().width).toBeGreaterThan(0); + + grid.remove(); + }); + + it('handles spanning with auto tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'auto auto auto'; + grid.style.gridTemplateRows = 'repeat(2, 80px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#fff3e0'; + + const item1 = document.createElement('div'); + item1.textContent = 'Item 1'; + item1.style.backgroundColor = '#FFB74D'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'Item 2'; + item2.style.backgroundColor = '#FFA726'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + grid.appendChild(item2); + + const item3 = document.createElement('div'); + item3.textContent = 'Item 3'; + item3.style.backgroundColor = '#FF9800'; + item3.style.padding = '10px'; + item3.style.color = 'white'; + grid.appendChild(item3); + + const item4 = document.createElement('div'); + item4.textContent = 'Spanning item that influences track sizing'; + item4.style.gridColumn = 'span 3'; + item4.style.backgroundColor = '#FB8C00'; + item4.style.padding = '10px'; + item4.style.color = 'white'; + item4.style.fontSize = '11px'; + grid.appendChild(item4); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[3].getBoundingClientRect().width).toBeGreaterThan(items[0].getBoundingClientRect().width); + + grid.remove(); + }); + + it('handles spanning with minmax and intrinsic sizes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, minmax(min-content, max-content))'; + grid.style.gridTemplateRows = 'repeat(2, 80px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e8f5e9'; + + const item1 = document.createElement('div'); + item1.textContent = 'Short'; + item1.style.backgroundColor = '#66BB6A'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'Medium text'; + item2.style.backgroundColor = '#4CAF50'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + grid.appendChild(item2); + + const item3 = document.createElement('div'); + item3.textContent = 'Longer text here'; + item3.style.backgroundColor = '#43A047'; + item3.style.padding = '10px'; + item3.style.color = 'white'; + item3.style.fontSize = '11px'; + grid.appendChild(item3); + + const item4 = document.createElement('div'); + item4.textContent = 'Spanning item'; + item4.style.gridColumn = 'span 2'; + item4.style.backgroundColor = '#388E3C'; + item4.style.padding = '10px'; + item4.style.color = 'white'; + grid.appendChild(item4); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[3].getBoundingClientRect().width).toBeGreaterThan(items[0].getBoundingClientRect().width); + + grid.remove(); + }); + + it('handles nested grid with spanning and intrinsic sizing', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'min-content max-content'; + grid.style.gridTemplateRows = '100px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#ede7f6'; + + const item1 = document.createElement('div'); + item1.textContent = 'Min'; + item1.style.backgroundColor = '#9575CD'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.style.gridColumn = '1 / 3'; + item2.style.display = 'grid'; + item2.style.gridTemplateColumns = '1fr 1fr'; + item2.style.gap = '5px'; + item2.style.backgroundColor = '#7E57C2'; + item2.style.padding = '10px'; + + const nested1 = document.createElement('div'); + nested1.textContent = 'Nested 1'; + nested1.style.backgroundColor = '#673AB7'; + nested1.style.padding = '5px'; + nested1.style.color = 'white'; + nested1.style.fontSize = '10px'; + item2.appendChild(nested1); + + const nested2 = document.createElement('div'); + nested2.textContent = 'Nested 2'; + nested2.style.backgroundColor = '#5E35B1'; + nested2.style.padding = '5px'; + nested2.style.color = 'white'; + nested2.style.fontSize = '10px'; + item2.appendChild(nested2); + + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(item1.getBoundingClientRect().width).toBeGreaterThan(0); + expect(item2.getBoundingClientRect().width).toBeGreaterThan(0); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/advanced-sizing/track-breadth.ts b/integration_tests/specs/css/css-grid/advanced-sizing/track-breadth.ts new file mode 100644 index 0000000000..f327f7c17a --- /dev/null +++ b/integration_tests/specs/css/css-grid/advanced-sizing/track-breadth.ts @@ -0,0 +1,191 @@ +describe('CSS Grid track breadth calculation', () => { + it('calculates track breadth with fixed sizes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px 150px 200px'; + grid.style.gridTemplateRows = '80px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = ['100px', '150px', '200px'][i]; + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBe(100); + expect(items[1].getBoundingClientRect().width).toBe(150); + expect(items[2].getBoundingClientRect().width).toBe(200); + + grid.remove(); + }); + + it('calculates breadth with content-based sizing', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'auto auto'; + grid.style.gridTemplateRows = '80px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e3f2fd'; + + const item1 = document.createElement('div'); + item1.textContent = 'Short'; + item1.style.backgroundColor = '#2196F3'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'Much longer content here'; + item2.style.backgroundColor = '#1E88E5'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[1].getBoundingClientRect().width).toBeGreaterThan(items[0].getBoundingClientRect().width); + + grid.remove(); + }); + + it('calculates breadth with percentage constraints', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '25% 50% 25%'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '400px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = ['25%', '50%', '25%'][i]; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBe(100); + expect(items[1].getBoundingClientRect().width).toBe(200); + expect(items[2].getBoundingClientRect().width).toBe(100); + + grid.remove(); + }); + + it('handles breadth with mixed units', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px 30% auto 1fr'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '500px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + const item1 = document.createElement('div'); + item1.textContent = '100px'; + item1.style.backgroundColor = '#FFB74D'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + item1.style.fontSize = '11px'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = '30%'; + item2.style.backgroundColor = '#FFA726'; + item2.style.display = 'flex'; + item2.style.alignItems = 'center'; + item2.style.justifyContent = 'center'; + item2.style.color = 'white'; + item2.style.fontSize = '11px'; + grid.appendChild(item2); + + const item3 = document.createElement('div'); + item3.textContent = 'Auto'; + item3.style.backgroundColor = '#FF9800'; + item3.style.padding = '10px'; + item3.style.color = 'white'; + item3.style.fontSize = '11px'; + grid.appendChild(item3); + + const item4 = document.createElement('div'); + item4.textContent = '1fr'; + item4.style.backgroundColor = '#FB8C00'; + item4.style.display = 'flex'; + item4.style.alignItems = 'center'; + item4.style.justifyContent = 'center'; + item4.style.color = 'white'; + item4.style.fontSize = '11px'; + grid.appendChild(item4); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // 100px + 150px (30%) = 250px used, remaining shared between auto and 1fr + expect(items[0].getBoundingClientRect().width).toBe(100); + expect(items[1].getBoundingClientRect().width).toBe(150); + expect(items[2].getBoundingClientRect().width).toBeGreaterThan(0); + expect(items[3].getBoundingClientRect().width).toBeGreaterThan(0); + + grid.remove(); + }); + + it('handles breadth with minmax constraints', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'minmax(100px, 200px) minmax(150px, 1fr)'; + grid.style.gridTemplateRows = '80px'; + grid.style.width = '400px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 2; i++) { + const item = document.createElement('div'); + item.textContent = `Track ${i + 1}`; + item.style.backgroundColor = ['#66BB6A', '#4CAF50'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBeGreaterThanOrEqual(100); + expect(items[0].getBoundingClientRect().width).toBeLessThanOrEqual(200); + expect(items[1].getBoundingClientRect().width).toBeGreaterThanOrEqual(150); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/alignment/align-content.ts b/integration_tests/specs/css/css-grid/alignment/align-content.ts new file mode 100644 index 0000000000..717652eb42 --- /dev/null +++ b/integration_tests/specs/css/css-grid/alignment/align-content.ts @@ -0,0 +1,425 @@ +describe('CSS Grid align-content', () => { + it('aligns content with start', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 60px)'; + grid.style.height = '200px'; + grid.style.alignContent = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726', '#BA68C8'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should start at top edge + expect(items[0].getBoundingClientRect().top).toBe(grid.getBoundingClientRect().top); + + grid.remove(); + }); + + it('aligns content with end', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 60px)'; + grid.style.height = '200px'; + grid.style.alignContent = 'end'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2', '#1565C0'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Last row should end at bottom edge + expect(items[2].getBoundingClientRect().bottom).toBe(grid.getBoundingClientRect().bottom); + + grid.remove(); + }); + + it('aligns content with center', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 50px)'; + grid.style.height = '180px'; + grid.style.alignContent = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0', '#8E24AA'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + const topSpace = items[0].getBoundingClientRect().top - gridRect.top; + const bottomSpace = gridRect.bottom - items[2].getBoundingClientRect().bottom; + + // Should be centered with equal space on top and bottom + expect(Math.abs(topSpace - bottomSpace)).toBeLessThan(1); + + grid.remove(); + }); + + it('aligns content with space-between', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(3, 50px)'; + grid.style.height = '220px'; + grid.style.alignContent = 'space-between'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800', '#FB8C00', '#F57C00', '#EF6C00'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + + // First row at top, last row at bottom + expect(items[0].getBoundingClientRect().top).toBe(gridRect.top); + expect(items[4].getBoundingClientRect().bottom).toBe(gridRect.bottom); + + // Equal spacing between rows + const gap1 = items[2].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom; + const gap2 = items[4].getBoundingClientRect().top - items[2].getBoundingClientRect().bottom; + expect(Math.abs(gap1 - gap2)).toBeLessThan(1); + + grid.remove(); + }); + + it('aligns content with space-around', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 50px)'; + grid.style.height = '180px'; + grid.style.alignContent = 'space-around'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047', '#388E3C'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + + // Space on top/bottom should be half of space between rows + const topSpace = items[0].getBoundingClientRect().top - gridRect.top; + const bottomSpace = gridRect.bottom - items[2].getBoundingClientRect().bottom; + const middleSpace = items[2].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom; + + expect(Math.abs(topSpace - bottomSpace)).toBeLessThan(1); + expect(Math.abs(middleSpace - topSpace * 2)).toBeLessThan(2); + + grid.remove(); + }); + + it('aligns content with space-evenly', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 50px)'; + grid.style.height = '180px'; + grid.style.alignContent = 'space-evenly'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#9575CD', '#7E57C2', '#673AB7', '#5E35B1'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + + // All gaps should be equal + const topSpace = items[0].getBoundingClientRect().top - gridRect.top; + const bottomSpace = gridRect.bottom - items[2].getBoundingClientRect().bottom; + const middleSpace = items[2].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom; + + expect(Math.abs(topSpace - bottomSpace)).toBeLessThan(1); + expect(Math.abs(topSpace - middleSpace)).toBeLessThan(1); + + grid.remove(); + }); + + it('aligns content with stretch', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 1fr)'; + grid.style.height = '200px'; + grid.style.alignContent = 'stretch'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e0f2f1'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#4DB6AC', '#26A69A', '#009688', '#00897B'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Rows should stretch to fill available space + expect(items[0].getBoundingClientRect().height).toBe(100); + expect(items[2].getBoundingClientRect().height).toBe(100); + + grid.remove(); + }); + + it('handles align-content with row gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 50px)'; + grid.style.height = '180px'; + grid.style.alignContent = 'center'; + grid.style.rowGap = '10px'; + grid.style.backgroundColor = '#fce4ec'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#F06292', '#EC407A', '#E91E63', '#D81B60'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Check gap is maintained + const gap = items[2].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom; + expect(gap).toBe(10); + + grid.remove(); + }); + + it('handles align-content with auto-sized tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'auto auto'; + grid.style.height = '200px'; + grid.style.alignContent = 'space-between'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff9c4'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = ['Short', 'Text', 'Longer text', 'More'][i]; + item.style.backgroundColor = ['#FFEB3B', '#FDD835', '#FBC02D', '#F9A825'][i]; + item.style.padding = '10px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.fontSize = '11px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + + // First row at top, last row at bottom + expect(items[0].getBoundingClientRect().top).toBe(gridRect.top); + expect(items[2].getBoundingClientRect().bottom).toBe(gridRect.bottom); + + grid.remove(); + }); + + it('handles align-content with single row', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.height = '180px'; + grid.style.alignContent = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#b2dfdb'; + + for (let i = 0; i < 2; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#26A69A', '#009688'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + const gridCenter = (gridRect.top + gridRect.bottom) / 2; + const rowCenter = (items[0].getBoundingClientRect().top + items[0].getBoundingClientRect().bottom) / 2; + + // Single row should be centered + expect(Math.abs(gridCenter - rowCenter)).toBeLessThan(1); + + grid.remove(); + }); + + it('handles space-between with two rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 50px)'; + grid.style.height = '200px'; + grid.style.alignContent = 'space-between'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#c5cae9'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#7986CB', '#5C6BC0', '#3F51B5', '#3949AB'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + + // With two rows, first at top, last at bottom + expect(items[0].getBoundingClientRect().top).toBe(gridRect.top); + expect(items[2].getBoundingClientRect().bottom).toBe(gridRect.bottom); + + grid.remove(); + }); + + it('handles align-content with mixed row sizes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '40px 60px 50px'; + grid.style.height = '250px'; + grid.style.alignContent = 'space-evenly'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#d1c4e9'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#9575CD', '#7E57C2', '#673AB7', '#5E35B1', '#512DA8', '#4527A0'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().height).toBe(40); + expect(items[2].getBoundingClientRect().height).toBe(60); + expect(items[4].getBoundingClientRect().height).toBe(50); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/alignment/align-items.ts b/integration_tests/specs/css/css-grid/alignment/align-items.ts new file mode 100644 index 0000000000..4b89d6d0e4 --- /dev/null +++ b/integration_tests/specs/css/css-grid/alignment/align-items.ts @@ -0,0 +1,283 @@ +describe('CSS Grid align-items', () => { + it('aligns items with start', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // All items should align to top of grid area + items.forEach(item => { + expect(item.getBoundingClientRect().top).toBe(grid.getBoundingClientRect().top); + }); + + grid.remove(); + }); + + it('aligns items with end', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'end'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // All items should align to bottom of grid area + items.forEach(item => { + expect(item.getBoundingClientRect().bottom).toBe(grid.getBoundingClientRect().bottom); + }); + + grid.remove(); + }); + + it('aligns items with center', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should be vertically centered + const gridCenter = (grid.getBoundingClientRect().top + grid.getBoundingClientRect().bottom) / 2; + items.forEach(item => { + const itemCenter = (item.getBoundingClientRect().top + item.getBoundingClientRect().bottom) / 2; + expect(Math.abs(itemCenter - gridCenter)).toBeLessThan(1); + }); + + grid.remove(); + }); + + it('stretches items by default', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'stretch'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should stretch to fill height + items.forEach(item => { + expect(item.getBoundingClientRect().height).toBe(100); + }); + + grid.remove(); + }); + + it('does not stretch items with explicit height', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'stretch'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.height = '60px'; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should not stretch due to explicit height + items.forEach(item => { + expect(item.getBoundingClientRect().height).toBe(60); + }); + + grid.remove(); + }); + + it('aligns items with baseline', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'baseline'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = 'Text'; + item.style.fontSize = ['16px', '20px', '24px'][i]; + item.style.backgroundColor = ['#9575CD', '#7E57C2', '#673AB7'][i]; + item.style.padding = '5px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(3); + + grid.remove(); + }); + + it('combines with row gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 80px)'; + grid.style.alignItems = 'center'; + grid.style.rowGap = '10px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e0f2f1'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.height = '50px'; + item.style.backgroundColor = ['#4DB6AC', '#26A69A', '#009688', '#00897B'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Distance from Item 1 top to Item 3 top: 80.00px + const distance = items[2].getBoundingClientRect().top - items[0].getBoundingClientRect().top - 80; + expect(distance).toBe(0); + grid.remove(); + }); + + it('aligns items with first baseline', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'first baseline'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = 'Text'; + item.style.fontSize = ['12px', '16px', '20px'][i]; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800'][i]; + item.style.padding = '5px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(3); + + grid.remove(); + }); + + it('aligns items with last baseline', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'last baseline'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#b2ebf2'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = 'Text'; + item.style.fontSize = ['12px', '16px', '20px'][i]; + item.style.backgroundColor = ['#4DD0E1', '#26C6DA', '#00BCD4'][i]; + item.style.padding = '5px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(3); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/alignment/align-self.ts b/integration_tests/specs/css/css-grid/alignment/align-self.ts new file mode 100644 index 0000000000..8e2ea913f7 --- /dev/null +++ b/integration_tests/specs/css/css-grid/alignment/align-self.ts @@ -0,0 +1,274 @@ +describe('CSS Grid align-self', () => { + it('aligns individual item with start', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (start)'; + item.style.alignSelf = 'start'; + item.style.fontSize = '11px'; + } + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 2 should be at top of grid area + expect(items[1].getBoundingClientRect().top).toBe(grid.getBoundingClientRect().top); + + grid.remove(); + }); + + it('aligns individual item with end', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (end)'; + item.style.alignSelf = 'end'; + item.style.fontSize = '11px'; + } + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 2 should be at bottom of grid area + expect(items[1].getBoundingClientRect().bottom).toBe(grid.getBoundingClientRect().bottom); + + grid.remove(); + }); + + it('aligns individual item with center', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (center)'; + item.style.alignSelf = 'center'; + item.style.fontSize = '10px'; + } + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 2 should be vertically centered + const gridCenter = (grid.getBoundingClientRect().top + grid.getBoundingClientRect().bottom) / 2; + const item2Center = (items[1].getBoundingClientRect().top + items[1].getBoundingClientRect().bottom) / 2; + expect(Math.abs(item2Center - gridCenter)).toBeLessThan(1); + + grid.remove(); + }); + + it('stretches individual item', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (stretch)'; + item.style.alignSelf = 'stretch'; + item.style.fontSize = '10px'; + } + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800'][i]; + item.style.padding = '10px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 2 should stretch to fill grid area height + expect(items[1].getBoundingClientRect().height).toBe(100); + + grid.remove(); + }); + + it('aligns with baseline', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = 'Text'; + if (i === 1) { + item.style.alignSelf = 'baseline'; + } + item.style.fontSize = ['16px', '20px', '24px'][i]; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047'][i]; + item.style.padding = '5px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(3); + + grid.remove(); + }); + + it('overrides with multiple items', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(4, 80px)'; + grid.style.gridTemplateRows = '90px'; + grid.style.alignItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + const alignments = ['start', 'end', 'stretch', 'center']; + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = alignments[i]; + item.style.alignSelf = alignments[i]; + item.style.backgroundColor = ['#9575CD', '#7E57C2', '#673AB7', '#5E35B1'][i]; + item.style.padding = '8px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '10px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Stretch item should be tallest + expect(items[2].getBoundingClientRect().height).toBe(90); + + grid.remove(); + }); + + it('aligns with first baseline', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e0f2f1'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = 'Text'; + if (i === 1) { + item.style.alignSelf = 'first baseline'; + } + item.style.fontSize = ['14px', '18px', '22px'][i]; + item.style.backgroundColor = ['#4DB6AC', '#26A69A', '#009688'][i]; + item.style.padding = '5px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(3); + + grid.remove(); + }); + + it('handles auto alignment', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.alignItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fce4ec'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (auto)'; + item.style.alignSelf = 'auto'; + item.style.fontSize = '10px'; + } + item.style.backgroundColor = ['#F06292', '#EC407A', '#E91E63'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Auto should inherit from align-items (center) + expect(items.length).toBe(3); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/alignment/auto-margins.ts b/integration_tests/specs/css/css-grid/alignment/auto-margins.ts new file mode 100644 index 0000000000..6e264913e5 --- /dev/null +++ b/integration_tests/specs/css/css-grid/alignment/auto-margins.ts @@ -0,0 +1,324 @@ +describe('CSS Grid auto margins and alignment interaction', () => { + it('auto margins override justify-items', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 120px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = ['Start', 'Auto left', 'Auto both'][i]; + item.style.width = '80px'; + if (i === 1) { + item.style.marginLeft = 'auto'; + } else if (i === 2) { + item.style.marginLeft = 'auto'; + item.style.marginRight = 'auto'; + } + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '10px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 1: aligned start (justify-items) + expect(items[0].getBoundingClientRect().left).toBe(grid.getBoundingClientRect().left); + // Item 2: margin-left auto pushes to right + expect(items[1].getBoundingClientRect().right).toBe(grid.getBoundingClientRect().left + 240); + // Item 3: centered with auto margins + const item3Center = (items[2].getBoundingClientRect().left + items[2].getBoundingClientRect().right) / 2; + const area3Center = grid.getBoundingClientRect().left + 300; // Third column center + expect(Math.abs(item3Center - area3Center)).toBeLessThan(1); + + grid.remove(); + }); + + it('auto margins override align-items', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '120px'; + grid.style.gridTemplateRows = 'repeat(3, 100px)'; + grid.style.alignItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = ['Start', 'Auto top', 'Auto both'][i]; + item.style.height = '60px'; + if (i === 1) { + item.style.marginTop = 'auto'; + } else if (i === 2) { + item.style.marginTop = 'auto'; + item.style.marginBottom = 'auto'; + } + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '10px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 1: aligned start (align-items) + expect(items[0].getBoundingClientRect().top).toBe(grid.getBoundingClientRect().top); + // Item 2: margin-top auto pushes to bottom + expect(items[1].getBoundingClientRect().bottom).toBe(grid.getBoundingClientRect().top + 200); + // Item 3: centered with auto margins + const item3Center = (items[2].getBoundingClientRect().top + items[2].getBoundingClientRect().bottom) / 2; + const area3Center = grid.getBoundingClientRect().top + 250; // Third row center + expect(Math.abs(item3Center - area3Center)).toBeLessThan(1); + + grid.remove(); + }); + + it('auto margins override justify-self', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 150px)'; + grid.style.gridTemplateRows = '90px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + const item1 = document.createElement('div'); + item1.textContent = 'justify-self: end'; + item1.style.justifySelf = 'end'; + item1.style.width = '100px'; + item1.style.backgroundColor = '#BA68C8'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + item1.style.fontSize = '10px'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = 'margin-left: auto'; + item2.style.justifySelf = 'start'; // Should be overridden + item2.style.marginLeft = 'auto'; + item2.style.width = '100px'; + item2.style.backgroundColor = '#AB47BC'; + item2.style.display = 'flex'; + item2.style.alignItems = 'center'; + item2.style.justifyContent = 'center'; + item2.style.color = 'white'; + item2.style.fontSize = '10px'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 1: respect justify-self + expect(items[0].getBoundingClientRect().right).toBe(grid.getBoundingClientRect().left + 150); + // Item 2: auto margin overrides justify-self + expect(items[1].getBoundingClientRect().right).toBe(grid.getBoundingClientRect().left + 300); + + grid.remove(); + }); + + it('combines auto margins in both axes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '200px'; + grid.style.gridTemplateRows = '180px'; + grid.style.justifyItems = 'start'; + grid.style.alignItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + const item = document.createElement('div'); + item.textContent = 'Centered'; + item.style.width = '120px'; + item.style.height = '100px'; + item.style.margin = 'auto'; + item.style.backgroundColor = '#FFB74D'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const itemRect = item.getBoundingClientRect(); + const gridRect = grid.getBoundingClientRect(); + + // Item should be centered both ways + // Auto margins center within the grid area (the item’s containing block), + // not the full border-box width of a block-level grid container. + const cellRight = gridRect.left + 200; + const cellBottom = gridRect.top + 180; + const leftMargin = itemRect.left - gridRect.left; + const rightMargin = cellRight - itemRect.right; + const topMargin = itemRect.top - gridRect.top; + const bottomMargin = cellBottom - itemRect.bottom; + + expect(Math.abs(leftMargin - rightMargin)).toBeLessThan(1); + expect(Math.abs(topMargin - bottomMargin)).toBeLessThan(1); + + grid.remove(); + }); + + it('auto margins with spanning items', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 80px)'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + const item = document.createElement('div'); + item.textContent = 'Span 2x2'; + item.style.gridColumn = 'span 2'; + item.style.gridRow = 'span 2'; + item.style.width = '140px'; + item.style.height = '120px'; + item.style.margin = 'auto'; + item.style.backgroundColor = '#66BB6A'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const itemRect = item.getBoundingClientRect(); + // Item should be centered in 2x2 area + expect(itemRect.width).toBe(140); + expect(itemRect.height).toBe(120); + + grid.remove(); + }); + + it('auto margins with mixed alignment values', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 110px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.placeItems = 'end start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = ['Normal', 'Auto H', 'Auto V'][i]; + item.style.width = '80px'; + item.style.height = '70px'; + if (i === 1) { + item.style.marginLeft = 'auto'; + item.style.marginRight = 'auto'; + } else if (i === 2) { + item.style.marginTop = 'auto'; + item.style.marginBottom = 'auto'; + } + item.style.backgroundColor = ['#9575CD', '#7E57C2', '#673AB7'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '10px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(3); + + grid.remove(); + }); + + it('handles asymmetric auto margins', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '180px'; + grid.style.gridTemplateRows = '150px'; + grid.style.justifyItems = 'start'; + grid.style.alignItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e0f2f1'; + + const item = document.createElement('div'); + item.textContent = 'Left auto only'; + item.style.width = '120px'; + item.style.height = '90px'; + item.style.marginLeft = 'auto'; + item.style.marginRight = '0'; + item.style.backgroundColor = '#4DB6AC'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '10px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Item should be pushed to the right edge + const itemRight = item.getBoundingClientRect().right; + const gridRight = grid.getBoundingClientRect().right; + expect(Math.abs(itemRight - gridRight)).toBe(180); + + grid.remove(); + }); + + it('auto margins with percentage-based item sizes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '240px'; + grid.style.gridTemplateRows = '180px'; + grid.style.placeItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fce4ec'; + + const item = document.createElement('div'); + item.textContent = '50% size'; + item.style.width = '50%'; + item.style.height = '50%'; + item.style.margin = 'auto'; + item.style.backgroundColor = '#F06292'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Item should be centered with 50% size + expect(item.getBoundingClientRect().width).toBe(120); + expect(item.getBoundingClientRect().height).toBe(90); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/alignment/justify-content.ts b/integration_tests/specs/css/css-grid/alignment/justify-content.ts new file mode 100644 index 0000000000..65772c833b --- /dev/null +++ b/integration_tests/specs/css/css-grid/alignment/justify-content.ts @@ -0,0 +1,424 @@ +describe('CSS Grid justify-content', () => { + it('aligns content with start', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 80px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '350px'; + grid.style.justifyContent = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should start at left edge + expect(items[0].getBoundingClientRect().left).toBe(grid.getBoundingClientRect().left); + + grid.remove(); + }); + + it('aligns content with end', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 80px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '350px'; + grid.style.justifyContent = 'end'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Last item should end at right edge + expect(items[2].getBoundingClientRect().right).toBe(grid.getBoundingClientRect().right); + + grid.remove(); + }); + + it('aligns content with center', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 70px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '300px'; + grid.style.justifyContent = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + const firstItemLeft = items[0].getBoundingClientRect().left - gridRect.left; + const lastItemRight = gridRect.right - items[2].getBoundingClientRect().right; + + // Should be centered with equal space on both sides + expect(Math.abs(firstItemLeft - lastItemRight)).toBeLessThan(1); + + grid.remove(); + }); + + it('aligns content with space-between', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 70px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '300px'; + grid.style.justifyContent = 'space-between'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + + // First item at start, last at end + expect(items[0].getBoundingClientRect().left).toBe(gridRect.left); + expect(items[2].getBoundingClientRect().right).toBe(gridRect.right); + + // Equal spacing between items + const gap1 = items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right; + const gap2 = items[2].getBoundingClientRect().left - items[1].getBoundingClientRect().right; + expect(Math.abs(gap1 - gap2)).toBeLessThan(1); + + grid.remove(); + }); + + it('aligns content with space-around', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 60px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '300px'; + grid.style.justifyContent = 'space-around'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + + // Space on sides should be half of space between items + const leftSpace = items[0].getBoundingClientRect().left - gridRect.left; + const rightSpace = gridRect.right - items[2].getBoundingClientRect().right; + const middleSpace = items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right; + + expect(Math.abs(leftSpace - rightSpace)).toBeLessThan(1); + expect(Math.abs(middleSpace - leftSpace * 2)).toBeLessThan(2); + + grid.remove(); + }); + + it('aligns content with space-evenly', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 60px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '300px'; + grid.style.justifyContent = 'space-evenly'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#9575CD', '#7E57C2', '#673AB7'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + + // All gaps should be equal + const leftSpace = items[0].getBoundingClientRect().left - gridRect.left; + const rightSpace = gridRect.right - items[2].getBoundingClientRect().right; + const gap1 = items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right; + const gap2 = items[2].getBoundingClientRect().left - items[1].getBoundingClientRect().right; + + expect(Math.abs(leftSpace - rightSpace)).toBeLessThan(1); + expect(Math.abs(leftSpace - gap1)).toBeLessThan(1); + expect(Math.abs(gap1 - gap2)).toBeLessThan(1); + + grid.remove(); + }); + + it('aligns content with stretch', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 1fr)'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '300px'; + grid.style.justifyContent = 'stretch'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e0f2f1'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#4DB6AC', '#26A69A', '#009688'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should stretch to fill available space + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBe(100); + }); + + grid.remove(); + }); + + it('handles justify-content with gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 70px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '300px'; + grid.style.justifyContent = 'center'; + grid.style.columnGap = '10px'; + grid.style.backgroundColor = '#fce4ec'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#F06292', '#EC407A', '#E91E63'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Check gaps are maintained + const gap1 = items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right; + const gap2 = items[2].getBoundingClientRect().left - items[1].getBoundingClientRect().right; + expect(gap1).toBe(10); + expect(gap2).toBe(10); + + grid.remove(); + }); + + it('handles justify-content with auto-sized tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'auto auto auto'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '300px'; + grid.style.justifyContent = 'space-between'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff9c4'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = ['Short', 'Medium text', 'Long text here'][i]; + item.style.backgroundColor = ['#FFEB3B', '#FDD835', '#FBC02D'][i]; + item.style.padding = '5px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.fontSize = '11px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + + // First at start, last at end + expect(items[0].getBoundingClientRect().left).toBe(gridRect.left); + expect(items[2].getBoundingClientRect().right).toBe(gridRect.right); + + grid.remove(); + }); + + it('handles justify-content with single item', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '300px'; + grid.style.justifyContent = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f0f4c3'; + + const item = document.createElement('div'); + item.textContent = 'Solo'; + item.style.backgroundColor = '#9CCC65'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Single item should be centered + const gridCenter = (grid.getBoundingClientRect().left + grid.getBoundingClientRect().right) / 2; + const itemCenter = (item.getBoundingClientRect().left + item.getBoundingClientRect().right) / 2; + expect(Math.abs(gridCenter - itemCenter)).toBeLessThan(1); + + grid.remove(); + }); + + it('handles justify-content with mixed track sizes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '60px 100px 80px'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '340px'; + grid.style.justifyContent = 'space-evenly'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f8bbd0'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#F06292', '#EC407A', '#E91E63'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBe(60); + expect(items[1].getBoundingClientRect().width).toBe(100); + expect(items[2].getBoundingClientRect().width).toBe(80); + + grid.remove(); + }); + + it('handles space-between with two items', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 80px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.width = '300px'; + grid.style.justifyContent = 'space-between'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#dce775'; + + for (let i = 0; i < 2; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#AFB42B', '#9E9D24'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const gridRect = grid.getBoundingClientRect(); + + // With two items, first at start, last at end + expect(items[0].getBoundingClientRect().left).toBe(gridRect.left); + expect(items[1].getBoundingClientRect().right).toBe(gridRect.right); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/alignment/justify-items.ts b/integration_tests/specs/css/css-grid/alignment/justify-items.ts new file mode 100644 index 0000000000..7ba8f49e37 --- /dev/null +++ b/integration_tests/specs/css/css-grid/alignment/justify-items.ts @@ -0,0 +1,285 @@ +describe('CSS Grid justify-items', () => { + it('aligns items with start', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should align to left edge of their grid areas + expect(items[0].getBoundingClientRect().left).toBe(grid.getBoundingClientRect().left); + expect(items[1].getBoundingClientRect().left).toBe(grid.getBoundingClientRect().left + 100); + + grid.remove(); + }); + + it('aligns items with end', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'end'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should align to right edge of their grid areas + expect(items[0].getBoundingClientRect().right).toBe(grid.getBoundingClientRect().left + 100); + expect(items[1].getBoundingClientRect().right).toBe(grid.getBoundingClientRect().left + 200); + + grid.remove(); + }); + + it('aligns items with center', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should be centered in their grid areas + const item0Center = (items[0].getBoundingClientRect().left + items[0].getBoundingClientRect().right) / 2; + const area0Center = grid.getBoundingClientRect().left + 50; + expect(Math.abs(item0Center - area0Center)).toBeLessThan(1); + + grid.remove(); + }); + + it('aligns items with stretch (default)', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'stretch'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should stretch to fill their grid areas + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBe(100); + }); + + grid.remove(); + }); + + it('does not stretch items with explicit width', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'stretch'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.width = '60px'; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should not stretch due to explicit width + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBe(60); + }); + + grid.remove(); + }); + + it('aligns items with different sizes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + const widths = [40, 60, 80]; + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${widths[i]}px`; + item.style.width = `${widths[i]}px`; + item.style.backgroundColor = ['#9575CD', '#7E57C2', '#673AB7'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Each item should be centered in its grid area + items.forEach((item, i) => { + expect(item.getBoundingClientRect().width).toBe(widths[i]); + }); + + grid.remove(); + }); + + it('combines with column gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'center'; + grid.style.columnGap = '10px'; + grid.style.backgroundColor = '#e0f2f1'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.width = '70px'; + item.style.backgroundColor = ['#4DB6AC', '#26A69A', '#009688'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Check gaps are correct + const gap = items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().left - 100; + expect(gap).toBe(10); + + grid.remove(); + }); + + it('aligns items with baseline', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'baseline'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ffccbc'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = 'Text'; + item.style.fontSize = ['14px', '18px', '22px'][i]; + item.style.backgroundColor = ['#FF8A65', '#FF7043', '#FF5722'][i]; + item.style.padding = '5px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(3); + + grid.remove(); + }); + + it('handles legacy alignment values', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'legacy'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#c8e6c9'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#81C784', '#66BB6A', '#4CAF50'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(3); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/alignment/justify-self.ts b/integration_tests/specs/css/css-grid/alignment/justify-self.ts new file mode 100644 index 0000000000..2f85f12cb3 --- /dev/null +++ b/integration_tests/specs/css/css-grid/alignment/justify-self.ts @@ -0,0 +1,252 @@ +describe('CSS Grid justify-self', () => { + it('aligns individual item with start', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + const item1 = document.createElement('div'); + item1.textContent = '1'; + item1.style.backgroundColor = '#42A5F5'; + item1.style.padding = '10px'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = '2 (start)'; + item2.style.justifySelf = 'start'; + item2.style.backgroundColor = '#66BB6A'; + item2.style.padding = '10px'; + item2.style.color = 'white'; + item2.style.fontSize = '11px'; + grid.appendChild(item2); + + const item3 = document.createElement('div'); + item3.textContent = '3'; + item3.style.backgroundColor = '#FFA726'; + item3.style.padding = '10px'; + item3.style.color = 'white'; + grid.appendChild(item3); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 2 should be at start of its grid area + expect(items[1].getBoundingClientRect().left).toBe(grid.getBoundingClientRect().left + 100); + + grid.remove(); + }); + + it('aligns individual item with end', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (end)'; + item.style.justifySelf = 'end'; + item.style.fontSize = '11px'; + } + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 2 should be at end of its grid area + expect(items[1].getBoundingClientRect().right).toBe(grid.getBoundingClientRect().left + 200); + + grid.remove(); + }); + + it('aligns individual item with center', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (center)'; + item.style.justifySelf = 'center'; + item.style.fontSize = '10px'; + } + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 2 should be centered + const item2Center = (items[1].getBoundingClientRect().left + items[1].getBoundingClientRect().right) / 2; + const area2Center = grid.getBoundingClientRect().left + 150; // Second column center + expect(Math.abs(item2Center - area2Center)).toBeLessThan(1); + + grid.remove(); + }); + + it('stretches individual item', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (stretch)'; + item.style.justifySelf = 'stretch'; + item.style.fontSize = '10px'; + } + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800'][i]; + item.style.padding = '10px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 2 should stretch to fill grid area + expect(items[1].getBoundingClientRect().width).toBe(100); + + grid.remove(); + }); + + it('overrides with multiple items', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(4, 80px)'; + grid.style.gridTemplateRows = '70px'; + grid.style.justifyItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + const alignments = ['start', 'end', 'stretch', 'center']; + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = alignments[i]; + item.style.justifySelf = alignments[i]; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047', '#388E3C'][i]; + item.style.padding = '8px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '10px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(4); + // Stretch item should be widest + expect(items[2].getBoundingClientRect().width).toBe(80); + + grid.remove(); + }); + + it('aligns with baseline', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = 'Text'; + if (i === 1) { + item.style.justifySelf = 'baseline'; + } + item.style.fontSize = ['14px', '18px', '22px'][i]; + item.style.backgroundColor = ['#9575CD', '#7E57C2', '#673AB7'][i]; + item.style.padding = '5px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(3); + + grid.remove(); + }); + + it('handles auto alignment', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e1bee7'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (auto)'; + item.style.justifySelf = 'auto'; + item.style.fontSize = '10px'; + } + item.style.backgroundColor = ['#CE93D8', '#BA68C8', '#AB47BC'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Auto should inherit from justify-items (center) + expect(items.length).toBe(3); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/alignment/place-content.ts b/integration_tests/specs/css/css-grid/alignment/place-content.ts new file mode 100644 index 0000000000..38795a5b77 --- /dev/null +++ b/integration_tests/specs/css/css-grid/alignment/place-content.ts @@ -0,0 +1,162 @@ +describe('CSS Grid place-content shorthand', () => { + it('sets both align-content and justify-content with one value', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 80px)'; + grid.style.gridTemplateRows = 'repeat(2, 60px)'; + grid.style.width = '250px'; + grid.style.height = '200px'; + grid.style.placeContent = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726', '#BA68C8'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Grid should be centered both vertically and horizontally + expect(getComputedStyle(grid).alignContent).toBe('center'); + expect(getComputedStyle(grid).justifyContent).toBe('center'); + + grid.remove(); + }); + + it('sets align-content and justify-content with two values', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 80px)'; + grid.style.gridTemplateRows = 'repeat(2, 60px)'; + grid.style.width = '250px'; + grid.style.height = '200px'; + grid.style.placeContent = 'start end'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2', '#1565C0'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(getComputedStyle(grid).alignContent).toBe('start'); + expect(getComputedStyle(grid).justifyContent).toBe('end'); + + grid.remove(); + }); + + it('uses space-between for both axes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 70px)'; + grid.style.gridTemplateRows = 'repeat(2, 50px)'; + grid.style.width = '220px'; + grid.style.height = '170px'; + grid.style.placeContent = 'space-between'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0', '#8E24AA'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(getComputedStyle(grid).alignContent).toBe('space-between'); + expect(getComputedStyle(grid).justifyContent).toBe('space-between'); + + grid.remove(); + }); + + it('uses space-around for both axes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 70px)'; + grid.style.gridTemplateRows = 'repeat(2, 50px)'; + grid.style.width = '210px'; + grid.style.height = '160px'; + grid.style.placeContent = 'space-around'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800', '#FB8C00'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(getComputedStyle(grid).alignContent).toBe('space-around'); + expect(getComputedStyle(grid).justifyContent).toBe('space-around'); + + grid.remove(); + }); + + it('uses space-evenly with gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 60px)'; + grid.style.gridTemplateRows = 'repeat(2, 50px)'; + grid.style.width = '200px'; + grid.style.height = '170px'; + grid.style.placeContent = 'space-evenly'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047', '#388E3C'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(getComputedStyle(grid).alignContent).toBe('space-evenly'); + expect(getComputedStyle(grid).justifyContent).toBe('space-evenly'); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/alignment/place-items.ts b/integration_tests/specs/css/css-grid/alignment/place-items.ts new file mode 100644 index 0000000000..854d8fb9bc --- /dev/null +++ b/integration_tests/specs/css/css-grid/alignment/place-items.ts @@ -0,0 +1,148 @@ +describe('CSS Grid place-items shorthand', () => { + it('sets both align-items and justify-items with one value', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.placeItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Items should be centered both ways + expect(getComputedStyle(grid).alignItems).toBe('center'); + expect(getComputedStyle(grid).justifyItems).toBe('center'); + + grid.remove(); + }); + + it('sets align-items and justify-items with two values', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.placeItems = 'start end'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(getComputedStyle(grid).alignItems).toBe('start'); + expect(getComputedStyle(grid).justifyItems).toBe('end'); + + grid.remove(); + }); + + it('uses stretch for both axes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.placeItems = 'stretch'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Items should stretch to fill grid areas + items.forEach(item => { + expect(item.getBoundingClientRect().width).toBe(100); + expect(item.getBoundingClientRect().height).toBe(100); + }); + + grid.remove(); + }); + + it('uses end for both axes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.placeItems = 'end'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(getComputedStyle(grid).alignItems).toBe('end'); + expect(getComputedStyle(grid).justifyItems).toBe('end'); + + grid.remove(); + }); + + it('combines with grid gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 90px)'; + grid.style.gridTemplateRows = '90px'; + grid.style.placeItems = 'center'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047'][i]; + item.style.padding = '8px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(getComputedStyle(grid).alignItems).toBe('center'); + expect(getComputedStyle(grid).justifyItems).toBe('center'); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/alignment/place-self.ts b/integration_tests/specs/css/css-grid/alignment/place-self.ts new file mode 100644 index 0000000000..8bfbe8f5da --- /dev/null +++ b/integration_tests/specs/css/css-grid/alignment/place-self.ts @@ -0,0 +1,177 @@ +describe('CSS Grid place-self shorthand', () => { + it('sets both align-self and justify-self with one value', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (center)'; + item.style.placeSelf = 'center'; + item.style.fontSize = '11px'; + } + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 2 should be centered both ways + expect(getComputedStyle(items[1]).alignSelf).toBe('center'); + expect(getComputedStyle(items[1]).justifySelf).toBe('center'); + + grid.remove(); + }); + + it('sets align-self and justify-self with two values', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (start/end)'; + item.style.placeSelf = 'start end'; + item.style.fontSize = '10px'; + } + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(getComputedStyle(items[1]).alignSelf).toBe('start'); + expect(getComputedStyle(items[1]).justifySelf).toBe('end'); + + grid.remove(); + }); + + it('uses stretch for both axes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.placeItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (stretch)'; + item.style.placeSelf = 'stretch'; + item.style.fontSize = '10px'; + } + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0'][i]; + item.style.padding = '10px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Item 2 should stretch + expect(items[1].getBoundingClientRect().width).toBe(100); + expect(items[1].getBoundingClientRect().height).toBe(100); + + grid.remove(); + }); + + it('uses auto for both axes', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '100px'; + grid.style.placeItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + if (i === 1) { + item.textContent += ' (auto)'; + item.style.placeSelf = 'auto'; + item.style.fontSize = '11px'; + } + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800'][i]; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Auto should inherit from place-items (center) + expect(getComputedStyle(items[1]).alignSelf).toBe('auto'); + expect(getComputedStyle(items[1]).justifySelf).toBe('auto'); + + grid.remove(); + }); + + it('overrides place-items alignment', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(4, 80px)'; + grid.style.gridTemplateRows = '90px'; + grid.style.placeItems = 'start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + const placements = ['center', 'end', 'stretch', 'start']; + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = placements[i]; + item.style.placeSelf = placements[i]; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047', '#388E3C'][i]; + item.style.padding = '8px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '10px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Stretch item should fill the grid area + expect(items[2].getBoundingClientRect().width).toBe(80); + expect(items[2].getBoundingClientRect().height).toBe(90); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/alignment/safe-unsafe.ts b/integration_tests/specs/css/css-grid/alignment/safe-unsafe.ts new file mode 100644 index 0000000000..5a0647cc70 --- /dev/null +++ b/integration_tests/specs/css/css-grid/alignment/safe-unsafe.ts @@ -0,0 +1,114 @@ +xdescribe('CSS Grid safe and unsafe alignment', () => { + it('handles safe center alignment', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyContent = 'safe center'; + grid.style.alignContent = 'safe center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + const item = document.createElement('div'); + item.textContent = 'Safe center'; + item.style.backgroundColor = '#42A5F5'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Safe alignment should prevent overflow + expect(item.getBoundingClientRect().width).toBe(100); + + grid.remove(); + }); + + it('handles unsafe center alignment', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyContent = 'unsafe center'; + grid.style.alignContent = 'unsafe center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + const item = document.createElement('div'); + item.textContent = 'Unsafe center'; + item.style.backgroundColor = '#2196F3'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '10px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Unsafe allows overflow + expect(item.getBoundingClientRect().width).toBe(100); + + grid.remove(); + }); + + it('handles safe start alignment', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = '80px'; + grid.style.justifyItems = 'safe start'; + grid.style.alignItems = 'safe start'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + const item = document.createElement('div'); + item.textContent = 'Safe start'; + item.style.backgroundColor = '#BA68C8'; + item.style.padding = '10px'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(item.getBoundingClientRect().left).toBe(grid.getBoundingClientRect().left); + + grid.remove(); + }); + + it('handles safe end alignment', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '120px'; + grid.style.gridTemplateRows = '90px'; + grid.style.justifyItems = 'safe end'; + grid.style.alignItems = 'safe end'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + const item = document.createElement('div'); + item.textContent = 'Safe end'; + item.style.backgroundColor = '#FFB74D'; + item.style.padding = '10px'; + item.style.color = 'white'; + grid.appendChild(item); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(item.getBoundingClientRect().right).toBe(grid.getBoundingClientRect().right); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/alignment/writing-modes.ts b/integration_tests/specs/css/css-grid/alignment/writing-modes.ts new file mode 100644 index 0000000000..7cfddf3baa --- /dev/null +++ b/integration_tests/specs/css/css-grid/alignment/writing-modes.ts @@ -0,0 +1,196 @@ +describe('CSS Grid alignment with writing modes', () => { + xit('handles vertical-rl writing mode', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 80px)'; + grid.style.writingMode = 'vertical-rl'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726', '#BA68C8'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(4); + + grid.remove(); + }); + + xit('aligns content in vertical-lr writing mode', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 90px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.writingMode = 'vertical-lr'; + grid.style.justifyContent = 'center'; + grid.style.alignContent = 'center'; + grid.style.width = '250px'; + grid.style.height = '200px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2', '#1565C0'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(4); + + grid.remove(); + }); + + xit('handles rtl direction with justify-content', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 80px)'; + grid.style.gridTemplateRows = '70px'; + grid.style.direction = 'rtl'; + grid.style.justifyContent = 'start'; + grid.style.width = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(3); + + grid.remove(); + }); + + xit('aligns items in vertical writing mode', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 80px)'; + grid.style.writingMode = 'vertical-rl'; + grid.style.justifyItems = 'center'; + grid.style.alignItems = 'center'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.width = '50px'; + item.style.height = '40px'; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800', '#FB8C00'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(4); + + grid.remove(); + }); + + it('handles horizontal-tb (default) writing mode explicitly', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 90px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.writingMode = 'horizontal-tb'; + grid.style.justifyContent = 'center'; + grid.style.alignContent = 'center'; + grid.style.width = '320px'; + grid.style.height = '200px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8eaf6'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#7986CB', '#5C6BC0', '#3F51B5', '#3949AB', '#303F9F', '#283593'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(6); + + grid.remove(); + }); + + xit('combines rtl direction with vertical writing mode', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 90px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.writingMode = 'vertical-rl'; + grid.style.direction = 'rtl'; + grid.style.gap = '5px'; + grid.style.backgroundColor = '#c5e1a5'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#9CCC65', '#8BC34A', '#7CB342', '#689F38'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(4); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/dynamic/add-remove-items.ts b/integration_tests/specs/css/css-grid/dynamic/add-remove-items.ts new file mode 100644 index 0000000000..70f507e3c8 --- /dev/null +++ b/integration_tests/specs/css/css-grid/dynamic/add-remove-items.ts @@ -0,0 +1,302 @@ +describe('CSS Grid dynamic add/remove items', () => { + it('adds new items to grid', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#f5f5f5'; + + // Initial items + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${i * 60}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + + // Add more items dynamically + for (let i = 3; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${i * 60}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(6); + + grid.remove(); + }); + + it('removes items from grid', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e3f2fd'; + + // Create initial items + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.className = `item-${i}`; + item.style.backgroundColor = `hsl(${200 + i * 20}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + + // Remove items + const itemToRemove1 = grid.querySelector('.item-1'); + const itemToRemove2 = grid.querySelector('.item-4'); + itemToRemove1?.remove(); + itemToRemove2?.remove(); + + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(4); + + grid.remove(); + }); + + it('reflows grid when items are added', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#f3e5f5'; + + // Start with 2 items + for (let i = 0; i < 2; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${280 + i * 30}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + + const firstRect1 = grid.children[0].getBoundingClientRect(); + + // Add 2 more items + for (let i = 2; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${280 + i * 30}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + await waitForFrame(); + await snapshot(); + + const firstRect2 = grid.children[0].getBoundingClientRect(); + // First item should remain in same position + expect(firstRect1.top).toBe(firstRect2.top); + expect(firstRect1.left).toBe(firstRect2.left); + + grid.remove(); + }); + + it('inserts item at specific position', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#fff3e0'; + + // Create items + for (let i = 0; i < 5; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${40 + i * 20}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + + // Insert new item at position 2 + const newItem = document.createElement('div'); + newItem.textContent = 'NEW'; + newItem.style.backgroundColor = '#FF5722'; + newItem.style.display = 'flex'; + newItem.style.alignItems = 'center'; + newItem.style.justifyContent = 'center'; + newItem.style.color = 'white'; + newItem.style.fontSize = '11px'; + grid.insertBefore(newItem, grid.children[2]); + + await waitForFrame(); + await snapshot(); + + expect(grid.children[2].textContent).toBe('NEW'); + expect(grid.children.length).toBe(6); + + grid.remove(); + }); + + it('removes all items from grid', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e8f5e9'; + grid.style.minHeight = '150px'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${120 + i * 15}, 60%, 55%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + + // Remove all items + while (grid.firstChild) { + grid.removeChild(grid.firstChild); + } + + await waitForFrame(); + await snapshot(); + + expect(grid.children.length).toBe(0); + // Grid should still maintain its size + expect(grid.getBoundingClientRect().height).toBeGreaterThanOrEqual(150); + + grid.remove(); + }); + + it('replaces items in grid', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.className = `item-${i}`; + item.style.backgroundColor = `hsl(${260 + i * 20}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + + // Replace item at position 3 + const oldItem = grid.children[3]; + const newItem = document.createElement('div'); + newItem.textContent = 'REPLACED'; + newItem.style.backgroundColor = '#E91E63'; + newItem.style.display = 'flex'; + newItem.style.alignItems = 'center'; + newItem.style.justifyContent = 'center'; + newItem.style.color = 'white'; + newItem.style.fontSize = '10px'; + grid.replaceChild(newItem, oldItem); + + await waitForFrame(); + await snapshot(); + + expect(grid.children[3].textContent).toBe('REPLACED'); + expect(grid.children.length).toBe(6); + + grid.remove(); + }); + + it('dynamically adds items with explicit placement', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e0f2f1'; + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${160 + i * 20}, 60%, 50%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + + // Add item with explicit placement + const explicitItem = document.createElement('div'); + explicitItem.textContent = 'Explicit'; + explicitItem.style.gridColumn = '2'; + explicitItem.style.gridRow = '2'; + explicitItem.style.backgroundColor = '#00BCD4'; + explicitItem.style.display = 'flex'; + explicitItem.style.alignItems = 'center'; + explicitItem.style.justifyContent = 'center'; + explicitItem.style.color = 'white'; + explicitItem.style.fontSize = '11px'; + grid.appendChild(explicitItem); + + await waitForFrame(); + await snapshot(); + + // Check explicit item is in correct position + expect(explicitItem.getBoundingClientRect().left).toBe(grid.getBoundingClientRect().left + 110); // 100 + 10 gap + expect(explicitItem.getBoundingClientRect().top).toBe(grid.getBoundingClientRect().top + 80); // 70 + 10 gap + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/dynamic/resize.ts b/integration_tests/specs/css/css-grid/dynamic/resize.ts new file mode 100644 index 0000000000..a59ee2e515 --- /dev/null +++ b/integration_tests/specs/css/css-grid/dynamic/resize.ts @@ -0,0 +1,324 @@ +describe('CSS Grid container resize behavior', () => { + it('reflows grid when container width changes', async () => { + const container = document.createElement('div'); + container.style.width = '400px'; + container.style.height = '300px'; + + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '1fr 1fr'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#f5f5f5'; + grid.style.width = '100%'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${i * 60}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + container.appendChild(grid); + document.body.appendChild(container); + await waitForFrame(); + await snapshot(); + + const initialWidth = grid.children[0].getBoundingClientRect().width; + expect(initialWidth).toBe(195); // (400 - 10) / 2 + + // Resize container + container.style.width = '600px'; + await waitForFrame(); + await snapshot(); + + const newWidth = grid.children[0].getBoundingClientRect().width; + expect(newWidth).toBe(295); // (600 - 10) / 2 + + container.remove(); + }); + + it('reflows grid when container height changes', async () => { + const container = document.createElement('div'); + container.style.width = '300px'; + container.style.height = '200px'; + + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '1fr 1fr'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e3f2fd'; + grid.style.height = '100%'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${200 + i * 20}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + container.appendChild(grid); + document.body.appendChild(container); + await waitForFrame(); + await snapshot(); + + const initialHeight = grid.children[0].getBoundingClientRect().height; + expect(initialHeight).toBe(95); // (200 - 10) / 2 + + // Resize container + container.style.height = '400px'; + await waitForFrame(); + await snapshot(); + + const newHeight = grid.children[0].getBoundingClientRect().height; + expect(newHeight).toBe(195); // (400 - 10) / 2 + + container.remove(); + }); + + it('recalculates percentage track sizing on resize', async () => { + const container = document.createElement('div'); + container.style.width = '400px'; + + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '50% 25% 25%'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + grid.style.width = '100%'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${280 + i * 15}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + container.appendChild(grid); + document.body.appendChild(container); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items[0].getBoundingClientRect().width).toBe(200); // 50% of 400 + expect(items[1].getBoundingClientRect().width).toBe(100); // 25% of 400 + + // Resize container + container.style.width = '600px'; + await waitForFrame(); + await snapshot(); + + expect(items[0].getBoundingClientRect().width).toBe(300); // 50% of 600 + expect(items[1].getBoundingClientRect().width).toBe(150); // 25% of 600 + + container.remove(); + }); + + it('recalculates fr units on container resize', async () => { + const container = document.createElement('div'); + container.style.width = '400px'; + + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px 1fr 2fr'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#fff3e0'; + grid.style.width = '100%'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${40 + i * 15}, 70%, 65%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + container.appendChild(grid); + document.body.appendChild(container); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // 400 - 100 - 20 (gaps) = 280 remaining for 3fr = 93.33 per fr + expect(items[0].getBoundingClientRect().width).toBe(100); + expect(Math.round(items[1].getBoundingClientRect().width)).toBe(93); // 1fr + expect(Math.round(items[2].getBoundingClientRect().width)).toBe(187); // 2fr + + // Resize container + container.style.width = '700px'; + await waitForFrame(); + await snapshot(); + + // 700 - 100 - 20 (gaps) = 580 remaining for 3fr = 193.33 per fr + expect(items[0].getBoundingClientRect().width).toBe(100); + expect(Math.round(items[1].getBoundingClientRect().width)).toBe(193); // 1fr + expect(Math.round(items[2].getBoundingClientRect().width)).toBe(387); // 2fr + + container.remove(); + }); + + it('recalculates auto-fit columns on resize', async () => { + const container = document.createElement('div'); + container.style.width = '350px'; + + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(auto-fit, minmax(100px, 1fr))'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e8f5e9'; + grid.style.width = '100%'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${120 + i * 15}, 60%, 55%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + container.appendChild(grid); + document.body.appendChild(container); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const initialRow1Count = items.filter(item => + item.getBoundingClientRect().top === grid.getBoundingClientRect().top + ).length; + // 350px width: can fit 3 columns at 100px min with 20px gaps + expect(initialRow1Count).toBe(3); + + // Resize container to fit 4 columns + container.style.width = '460px'; + await waitForFrame(); + await snapshot(); + + const newRow1Count = items.filter(item => + item.getBoundingClientRect().top === grid.getBoundingClientRect().top + ).length; + // 460px width: can fit 4 columns at 100px min with 30px gaps + expect(newRow1Count).toBe(4); + + container.remove(); + }); + + it('recalculates percentage gaps on resize', async () => { + const container = document.createElement('div'); + container.style.width = '400px'; + container.style.height = '300px'; + + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.columnGap = '5%'; + grid.style.rowGap = '5%'; + grid.style.backgroundColor = '#ede7f6'; + grid.style.width = '100%'; + grid.style.height = '100%'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${260 + i * 20}, 70%, 65%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + container.appendChild(grid); + document.body.appendChild(container); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Column gap: 5% of 400px = 20px + const initialColGap = items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right; + expect(initialColGap).toBe(20); + // Row gap: 5% of 300px = 15px + const initialRowGap = items[2].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom; + expect(initialRowGap).toBe(15); + + // Resize container + container.style.width = '600px'; + container.style.height = '400px'; + await waitForFrame(); + await snapshot(); + + // Column gap: 5% of 600px = 30px + const newColGap = items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right; + expect(newColGap).toBe(30); + // Row gap: 5% of 400px = 20px + const newRowGap = items[2].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom; + expect(newRowGap).toBe(20); + + container.remove(); + }); + + it('handles minmax() with resize', async () => { + const container = document.createElement('div'); + container.style.width = '300px'; + + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, minmax(80px, 1fr))'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e0f2f1'; + grid.style.width = '100%'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${160 + i * 15}, 60%, 50%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + container.appendChild(grid); + document.body.appendChild(container); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // 300 - 20 (gaps) = 280 / 3 = 93.33px (above min of 80px) + expect(Math.round(items[0].getBoundingClientRect().width)).toBe(93); + + // Resize to narrow container + container.style.width = '200px'; + await waitForFrame(); + await snapshot(); + + // 200 - 20 (gaps) = 180 / 3 = 60px, but min is 80px so columns should be 80px + expect(items[0].getBoundingClientRect().width).toBe(80); + + container.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/dynamic/style-changes.ts b/integration_tests/specs/css/css-grid/dynamic/style-changes.ts new file mode 100644 index 0000000000..71e47755ea --- /dev/null +++ b/integration_tests/specs/css/css-grid/dynamic/style-changes.ts @@ -0,0 +1,316 @@ +describe('CSS Grid dynamic style changes', () => { + it('updates grid-template-columns dynamically', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${i * 60}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const initialWidth = grid.children[0].getBoundingClientRect().width; + expect(initialWidth).toBe(100); + + // Change column template + grid.style.gridTemplateColumns = 'repeat(2, 150px)'; + await waitForFrame(); + await snapshot(); + + const newWidth = grid.children[0].getBoundingClientRect().width; + expect(newWidth).toBe(150); + + grid.remove(); + }); + + it('updates grid-template-rows dynamically', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${200 + i * 20}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const initialHeight = grid.children[0].getBoundingClientRect().height; + expect(initialHeight).toBe(70); + + // Change row template + grid.style.gridTemplateRows = 'repeat(2, 100px)'; + await waitForFrame(); + await snapshot(); + + const newHeight = grid.children[0].getBoundingClientRect().height; + expect(newHeight).toBe(100); + + grid.remove(); + }); + + it('updates gap values dynamically', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${280 + i * 20}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const initialGap = items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right; + expect(initialGap).toBe(10); + + // Change gap + grid.style.gap = '20px'; + await waitForFrame(); + await snapshot(); + + const newGap = items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right; + expect(newGap).toBe(20); + + grid.remove(); + }); + + it('changes grid-auto-flow dynamically', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gridAutoFlow = 'row'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${40 + i * 20}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const initialRow2Pos = items[2].getBoundingClientRect().top; + + // Change to column flow + grid.style.gridAutoFlow = 'column'; + grid.style.gridAutoColumns = '110px'; + await waitForFrame(); + await snapshot(); + + const newRow2Pos = items[2].getBoundingClientRect().top; + // With column flow, item 2 should be in different position + expect(newRow2Pos).not.toBe(initialRow2Pos); + + grid.remove(); + }); + + it('updates item grid-column dynamically', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.className = `item-${i}`; + item.style.backgroundColor = `hsl(${120 + i * 15}, 60%, 55%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const targetItem = grid.querySelector('.item-2') as HTMLElement; + const initialLeft = targetItem.getBoundingClientRect().left; + + // Change item placement + // Move item-2 from column 3 to column 1 + targetItem.style.gridColumn = '1'; + targetItem.style.gridRow = '2'; + + await waitForFrame(); + await snapshot(); + + const newLeft = targetItem.getBoundingClientRect().left; + expect(newLeft).not.toBe(initialLeft); + expect(newLeft).toBe(grid.getBoundingClientRect().left); + grid.remove(); + }); + + it('toggles display property', async () => { + const container = document.createElement('div'); + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${260 + i * 20}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + container.appendChild(grid); + document.body.appendChild(container); + await waitForFrame(); + await snapshot(); + + expect(grid.getBoundingClientRect().width).toBeGreaterThan(0); + + // Hide grid + grid.style.display = 'none'; + await waitForFrame(); + await snapshot(); + + expect(grid.getBoundingClientRect().width).toBe(0); + + // Show grid again + grid.style.display = 'grid'; + await waitForFrame(); + await snapshot(); + + expect(grid.getBoundingClientRect().width).toBeGreaterThan(0); + + container.remove(); + }); + + it('changes justify-content dynamically', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.justifyContent = 'start'; + grid.style.width = '400px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e0f2f1'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${160 + i * 20}, 60%, 50%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const initialLeft = grid.children[0].getBoundingClientRect().left; + expect(initialLeft).toBe(grid.getBoundingClientRect().left); + + // Change alignment + grid.style.justifyContent = 'center'; + await waitForFrame(); + await snapshot(); + + const newLeft = grid.children[0].getBoundingClientRect().left; + expect(newLeft).toBeGreaterThan(initialLeft); + + grid.remove(); + }); + + it('updates grid-auto-rows dynamically', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#fce4ec'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${320 + i * 15}, 70%, 65%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + const initialHeight = items[2].getBoundingClientRect().height; + expect(initialHeight).toBe(70); + + // Change auto rows + grid.style.gridAutoRows = '90px'; + await waitForFrame(); + await snapshot(); + + const newHeight = items[2].getBoundingClientRect().height; + expect(newHeight).toBe(90); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/hit-test.ts b/integration_tests/specs/css/css-grid/hit-test.ts index fd2452fe3b..443e0b14ab 100644 --- a/integration_tests/specs/css/css-grid/hit-test.ts +++ b/integration_tests/specs/css/css-grid/hit-test.ts @@ -68,5 +68,65 @@ describe('CSS Grid hit testing', () => { grid.remove(); }); -}); + it('treats opacity stacking contexts as higher stacking layer', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(4, 70px)'; + grid.style.gridTemplateRows = 'repeat(3, 60px)'; + grid.style.gap = '0'; + + const item1 = document.createElement('div'); + item1.id = 'grid-opacity-A'; + item1.textContent = 'A'; + item1.style.gridArea = '1 / 1 / 2 / 3'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.id = 'grid-opacity-B'; + item2.textContent = 'B'; + item2.style.gridArea = '1 / 2 / 3 / 4'; + item2.style.opacity = '0.8'; + grid.appendChild(item2); + + const item3 = document.createElement('div'); + item3.id = 'grid-opacity-C'; + item3.textContent = 'C'; + item3.style.gridArea = '2 / 1 / 4 / 3'; + grid.appendChild(item3); + + const item4 = document.createElement('div'); + item4.id = 'grid-opacity-D'; + item4.textContent = 'D'; + item4.style.gridArea = '2 / 3 / 4 / 5'; + grid.appendChild(item4); + + document.body.appendChild(grid); + await waitForOnScreen(grid); + + function midpointOfOverlap(r1: DOMRect, r2: DOMRect) { + const left = Math.max(r1.left, r2.left); + const right = Math.min(r1.right, r2.right); + const top = Math.max(r1.top, r2.top); + const bottom = Math.min(r1.bottom, r2.bottom); + expect(right).toBeGreaterThan(left); + expect(bottom).toBeGreaterThan(top); + return {x: left + (right - left) / 2, y: top + (bottom - top) / 2}; + } + + // B overlaps both C and D. Because opacity < 1 creates a stacking context, + // it participates in the auto/0 stacking layer and should paint above + // non-stacking, non-positioned grid items. + const bRect = item2.getBoundingClientRect(); + const cRect = item3.getBoundingClientRect(); + const dRect = item4.getBoundingClientRect(); + + const bc = midpointOfOverlap(bRect, cRect); + expect(document.elementFromPoint(bc.x, bc.y)).toBe(item2); + + const bd = midpointOfOverlap(bRect, dRect); + expect(document.elementFromPoint(bd.x, bd.y)).toBe(item2); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/implicit-grids/auto-columns-extended.ts b/integration_tests/specs/css/css-grid/implicit-grids/auto-columns-extended.ts new file mode 100644 index 0000000000..9be7be2fcb --- /dev/null +++ b/integration_tests/specs/css/css-grid/implicit-grids/auto-columns-extended.ts @@ -0,0 +1,250 @@ +describe('CSS Grid auto-columns extended', () => { + it('creates implicit columns with grid-auto-columns', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = 'repeat(2, 80px)'; + grid.style.gridAutoFlow = 'column'; + grid.style.gridAutoColumns = '120px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726', '#BA68C8', '#26A69A', '#EC407A'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // First column (explicit): 100px + expect(items[0].getBoundingClientRect().width).toBe(100); + // Second column (implicit): 120px + expect(items[2].getBoundingClientRect().width).toBe(120); + // Third column (implicit): 120px + expect(items[4].getBoundingClientRect().width).toBe(120); + + grid.remove(); + }); + + it('uses auto value for grid-auto-columns', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = 'repeat(2, 60px)'; + grid.style.gridAutoFlow = 'column'; + grid.style.gridAutoColumns = 'auto'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + const texts = ['A', 'B', 'Wider', 'Text', 'C', 'D']; + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = texts[i]; + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2', '#1565C0', '#1976D2', '#1E88E5'][i]; + item.style.padding = '10px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Implicit columns should size to content + expect(items[2].getBoundingClientRect().width).toBeGreaterThan(0); + + grid.remove(); + }); + + it('uses minmax() in grid-auto-columns', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gridAutoFlow = 'column'; + grid.style.gridAutoColumns = 'minmax(80px, auto)'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = i < 2 ? 'S' : 'Longer'; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0', '#8E24AA', '#7B1FA2', '#6A1B9A'][i]; + item.style.padding = '10px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Implicit columns should be at least 80px + expect(items[2].getBoundingClientRect().width).toBeGreaterThanOrEqual(80); + expect(items[4].getBoundingClientRect().width).toBeGreaterThanOrEqual(80); + + grid.remove(); + }); + + it('uses multiple values in grid-auto-columns', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = 'repeat(2, 60px)'; + grid.style.gridAutoFlow = 'column'; + grid.style.gridAutoColumns = '90px 110px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 10; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800', '#FB8C00', '#F57C00', '#EF6C00', '#E65100', '#D84315', '#BF360C', '#A1887F'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // First column (explicit): 100px + expect(items[0].getBoundingClientRect().width).toBe(100); + // Second column (implicit, first pattern): 90px + expect(items[2].getBoundingClientRect().width).toBe(90); + // Third column (implicit, second pattern): 110px + expect(items[4].getBoundingClientRect().width).toBe(110); + // Fourth column (implicit, repeats first pattern): 90px + expect(items[6].getBoundingClientRect().width).toBe(90); + + grid.remove(); + }); + + it('uses fr units in grid-auto-columns', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gridAutoFlow = 'column'; + grid.style.gridAutoColumns = '1fr'; + grid.style.width = '400px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047', '#388E3C', '#2E7D32', '#1B5E20'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // First column: 100px explicit + expect(items[0].getBoundingClientRect().width).toBe(100); + // Remaining space (300px) divided by 2 implicit columns = 150px each + expect(items[2].getBoundingClientRect().width).toBe(150); + expect(items[4].getBoundingClientRect().width).toBe(150); + + grid.remove(); + }); + + it('combines grid-auto-columns with gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gridAutoFlow = 'column'; + grid.style.gridAutoColumns = '110px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#9575CD', '#7E57C2', '#673AB7', '#5E35B1', '#512DA8', '#4527A0'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Check column gaps are maintained + const gap1 = items[2].getBoundingClientRect().left - items[0].getBoundingClientRect().right; + const gap2 = items[4].getBoundingClientRect().left - items[2].getBoundingClientRect().right; + expect(gap1).toBe(10); + expect(gap2).toBe(10); + + grid.remove(); + }); + + it('uses percentage values in grid-auto-columns', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = 'repeat(2, 70px)'; + grid.style.gridAutoFlow = 'column'; + grid.style.gridAutoColumns = '25%'; + grid.style.width = '400px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e0f2f1'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#4DB6AC', '#26A69A', '#009688', '#00897B'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // First column: 100px + expect(items[0].getBoundingClientRect().width).toBe(100); + // Second column: 25% of 400px = 100px + expect(items[2].getBoundingClientRect().width).toBe(100); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/implicit-grids/auto-rows.ts b/integration_tests/specs/css/css-grid/implicit-grids/auto-rows.ts new file mode 100644 index 0000000000..2a7c49f573 --- /dev/null +++ b/integration_tests/specs/css/css-grid/implicit-grids/auto-rows.ts @@ -0,0 +1,247 @@ +describe('CSS Grid auto-rows', () => { + it('creates implicit rows with grid-auto-rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '50px'; + grid.style.gridAutoRows = '80px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + // Create 6 items (will overflow into implicit rows) + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#42A5F5', '#66BB6A', '#FFA726', '#BA68C8', '#26A69A', '#EC407A'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // First row (explicit): 50px + expect(items[0].getBoundingClientRect().height).toBe(50); + expect(items[1].getBoundingClientRect().height).toBe(50); + // Second row (implicit): 80px + expect(items[2].getBoundingClientRect().height).toBe(80); + expect(items[3].getBoundingClientRect().height).toBe(80); + // Third row (implicit): 80px + expect(items[4].getBoundingClientRect().height).toBe(80); + expect(items[5].getBoundingClientRect().height).toBe(80); + + grid.remove(); + }); + + it('uses auto value for grid-auto-rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '50px'; + grid.style.gridAutoRows = 'auto'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = i < 2 ? 'Short' : 'Taller content here'; + item.style.backgroundColor = ['#2196F3', '#1E88E5', '#1976D2', '#1565C0'][i]; + item.style.padding = '10px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Implicit row should size to content + expect(items[2].getBoundingClientRect().height).toBeGreaterThan(0); + + grid.remove(); + }); + + it('uses minmax() in grid-auto-rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '50px'; + grid.style.gridAutoRows = 'minmax(60px, auto)'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = i < 2 ? 'Small' : 'Larger content text'; + item.style.backgroundColor = ['#BA68C8', '#AB47BC', '#9C27B0', '#8E24AA', '#7B1FA2', '#6A1B9A'][i]; + item.style.padding = '5px'; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + item.style.fontSize = '11px'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Implicit rows should be at least 60px + expect(items[2].getBoundingClientRect().height).toBeGreaterThanOrEqual(60); + expect(items[4].getBoundingClientRect().height).toBeGreaterThanOrEqual(60); + + grid.remove(); + }); + + it('uses multiple values in grid-auto-rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '50px'; + grid.style.gridAutoRows = '60px 80px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 8; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#FFB74D', '#FFA726', '#FF9800', '#FB8C00', '#F57C00', '#EF6C00', '#E65100', '#D84315'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // First row (explicit): 50px + expect(items[0].getBoundingClientRect().height).toBe(50); + // Second row (implicit, first pattern): 60px + expect(items[2].getBoundingClientRect().height).toBe(60); + // Third row (implicit, second pattern): 80px + expect(items[4].getBoundingClientRect().height).toBe(80); + // Fourth row (implicit, repeats first pattern): 60px + expect(items[6].getBoundingClientRect().height).toBe(60); + + grid.remove(); + }); + + it('uses fr units in grid-auto-rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '50px'; + grid.style.gridAutoRows = '1fr'; + grid.style.height = '250px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#66BB6A', '#4CAF50', '#43A047', '#388E3C', '#2E7D32', '#1B5E20'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // First row: 50px explicit + expect(items[0].getBoundingClientRect().height).toBe(50); + // Remaining space (200px) divided by 2 implicit rows = 100px each + expect(items[2].getBoundingClientRect().height).toBe(100); + expect(items[4].getBoundingClientRect().height).toBe(100); + + grid.remove(); + }); + + it('combines grid-auto-rows with gaps', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '50px'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#9575CD', '#7E57C2', '#673AB7', '#5E35B1', '#512DA8', '#4527A0'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Check row gaps are maintained + const gap1 = items[2].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom; + const gap2 = items[4].getBoundingClientRect().top - items[2].getBoundingClientRect().bottom; + expect(gap1).toBe(10); + expect(gap2).toBe(10); + + grid.remove(); + }); + + it('uses percentage values in grid-auto-rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '50px'; + grid.style.gridAutoRows = '20%'; + grid.style.height = '300px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e0f2f1'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = ['#4DB6AC', '#26A69A', '#009688', '#00897B'][i]; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // First row: 50px + expect(items[0].getBoundingClientRect().height).toBe(50); + // Second row: 20% of 300px = 60px + expect(items[2].getBoundingClientRect().height).toBe(60); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/implicit-grids/implicit-creation.ts b/integration_tests/specs/css/css-grid/implicit-grids/implicit-creation.ts new file mode 100644 index 0000000000..30c4ce5ff0 --- /dev/null +++ b/integration_tests/specs/css/css-grid/implicit-grids/implicit-creation.ts @@ -0,0 +1,274 @@ +describe('CSS Grid implicit track creation', () => { + it('creates implicit rows when items exceed explicit rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 8; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${i * 45}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Row 1 (explicit) + expect(items[0].getBoundingClientRect().top).toBe(grid.getBoundingClientRect().top); + // Row 2 (implicit) + expect(items[2].getBoundingClientRect().top).toBe(grid.getBoundingClientRect().top + 60); + // Row 3 (implicit) + expect(items[4].getBoundingClientRect().top).toBe(grid.getBoundingClientRect().top + 130); + + grid.remove(); + }); + + it('creates implicit columns with column auto-flow', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = 'repeat(2, 60px)'; + grid.style.gridAutoFlow = 'column'; + grid.style.gridAutoColumns = '110px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 8; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${200 + i * 20}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Column 1 (explicit) + expect(items[0].getBoundingClientRect().left).toBe(grid.getBoundingClientRect().left); + // Column 2 (implicit) + expect(items[2].getBoundingClientRect().left).toBe(grid.getBoundingClientRect().left + 100); + // Column 3 (implicit) + expect(items[4].getBoundingClientRect().left).toBe(grid.getBoundingClientRect().left + 210); + + grid.remove(); + }); + + it('creates implicit tracks when item placed beyond explicit grid', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 60px)'; + grid.style.gridAutoRows = '50px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + // Place item explicitly in row 5 + const item1 = document.createElement('div'); + item1.textContent = '1'; + item1.style.gridRow = '5'; + item1.style.gridColumn = '1'; + item1.style.backgroundColor = '#BA68C8'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + grid.appendChild(item1); + + // Add regular item + const item2 = document.createElement('div'); + item2.textContent = '2'; + item2.style.backgroundColor = '#9C27B0'; + item2.style.display = 'flex'; + item2.style.alignItems = 'center'; + item2.style.justifyContent = 'center'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Item in row 5 should create implicit rows 3, 4, 5 + // Row 5 starts after rows 1-4: 60 + 60 + 50 + 50 = 220. + expect(item1.getBoundingClientRect().top).toBe(grid.getBoundingClientRect().top + 220); + + grid.remove(); + }); + + it('creates negative implicit tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(2, 60px)'; + grid.style.gridAutoRows = '50px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#fff3e0'; + + // Place item at negative row (before explicit grid) + const item1 = document.createElement('div'); + item1.textContent = '1'; + item1.style.gridRow = '-5'; + item1.style.gridColumn = '1'; + item1.style.backgroundColor = '#FFB74D'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = '2'; + item2.style.backgroundColor = '#FF9800'; + item2.style.display = 'flex'; + item2.style.alignItems = 'center'; + item2.style.justifyContent = 'center'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(2); + + grid.remove(); + }); + + it('creates implicit tracks with spanning items', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + // Spanning item that extends into implicit rows + const item1 = document.createElement('div'); + item1.textContent = 'Span'; + item1.style.gridRow = '1 / span 3'; + item1.style.gridColumn = '1'; + item1.style.backgroundColor = '#66BB6A'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + grid.appendChild(item1); + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${120 + i * 15}, 60%, 50%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Spanning item should span 60 + 70 + 70 = 200px + expect(item1.getBoundingClientRect().height).toBe(200); + + grid.remove(); + }); + + it('creates implicit tracks in both dimensions', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = '60px'; + grid.style.gridAutoColumns = '90px'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + // Place item beyond explicit grid in both dimensions + const item1 = document.createElement('div'); + item1.textContent = '1'; + item1.style.gridRow = '3'; + item1.style.gridColumn = '3'; + item1.style.backgroundColor = '#9575CD'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + grid.appendChild(item1); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Item should be at position created by implicit tracks + // grid-column: 3/grid-row: 3 position the item in the 3rd track (between lines 3 and 4), + // so its offset is the sum of the preceding two tracks. + expect(item1.getBoundingClientRect().left).toBe(grid.getBoundingClientRect().left + 190); // 100 + 90 + expect(item1.getBoundingClientRect().top).toBe(grid.getBoundingClientRect().top + 130); // 60 + 70 + + grid.remove(); + }); + + it('handles empty implicit tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e0f2f1'; + + // Item in row 1 + const item1 = document.createElement('div'); + item1.textContent = '1'; + item1.style.gridRow = '1'; + item1.style.gridColumn = '1'; + item1.style.backgroundColor = '#4DB6AC'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + grid.appendChild(item1); + + // Item in row 4 (rows 2 and 3 are empty implicit) + const item2 = document.createElement('div'); + item2.textContent = '2'; + item2.style.gridRow = '4'; + item2.style.gridColumn = '2'; + item2.style.backgroundColor = '#009688'; + item2.style.display = 'flex'; + item2.style.alignItems = 'center'; + item2.style.justifyContent = 'center'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Item 2 should be placed after row1(60) + gap(10) + row2(70) + gap(10) + row3(70) + gap(10) = 230. + expect(item2.getBoundingClientRect().top).toBe(grid.getBoundingClientRect().top + 230); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/implicit-grids/implicit-named-lines.ts b/integration_tests/specs/css/css-grid/implicit-grids/implicit-named-lines.ts new file mode 100644 index 0000000000..fad52d0a09 --- /dev/null +++ b/integration_tests/specs/css/css-grid/implicit-grids/implicit-named-lines.ts @@ -0,0 +1,242 @@ +describe('CSS Grid implicit tracks with named lines', () => { + it('uses implicit named lines from repeat()', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, [col-start] 100px [col-end])'; + grid.style.gridTemplateRows = '[row-start] 60px [row-end]'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f5f5f5'; + + const item1 = document.createElement('div'); + item1.textContent = '1'; + item1.style.gridColumn = 'col-start 1 / col-end 1'; + item1.style.gridRow = 'row-start / row-end'; + item1.style.backgroundColor = '#42A5F5'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = '2'; + item2.style.gridColumn = 'col-start 2 / col-end 2'; + item2.style.gridRow = 'row-start / row-end'; + item2.style.backgroundColor = '#66BB6A'; + item2.style.display = 'flex'; + item2.style.alignItems = 'center'; + item2.style.justifyContent = 'center'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(item1.getBoundingClientRect().width).toBe(100); + expect(item2.getBoundingClientRect().width).toBe(100); + expect(item2.getBoundingClientRect().left).toBe(item1.getBoundingClientRect().right); + + grid.remove(); + }); + + it('places items using line numbers in implicit grid', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e3f2fd'; + + // Place item in implicit row using line number + const item1 = document.createElement('div'); + item1.textContent = '1'; + item1.style.gridColumn = '1 / 2'; + item1.style.gridRow = '3 / 4'; // Implicit row + item1.style.backgroundColor = '#2196F3'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + grid.appendChild(item1); + + const item2 = document.createElement('div'); + item2.textContent = '2'; + item2.style.gridColumn = '2 / 3'; + item2.style.gridRow = '1 / 2'; // Explicit row + item2.style.backgroundColor = '#1E88E5'; + item2.style.display = 'flex'; + item2.style.alignItems = 'center'; + item2.style.justifyContent = 'center'; + item2.style.color = 'white'; + grid.appendChild(item2); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // grid-row: 3 / 4 places the item in the 3rd row track (between lines 3 and 4), + // so the top offset is the sum of the preceding two tracks: 60 + 70 = 130. + expect(item1.getBoundingClientRect().top).toBe(grid.getBoundingClientRect().top + 130); + + grid.remove(); + }); + + it('uses negative line numbers with implicit tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${280 + i * 15}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + // Place item using negative line number + const lastItem = document.createElement('div'); + lastItem.textContent = 'Last'; + lastItem.style.gridColumn = '-2 / -1'; + lastItem.style.gridRow = '-1'; + lastItem.style.backgroundColor = '#BA68C8'; + lastItem.style.display = 'flex'; + lastItem.style.alignItems = 'center'; + lastItem.style.justifyContent = 'center'; + lastItem.style.color = 'white'; + lastItem.style.fontSize = '11px'; + grid.appendChild(lastItem); + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Last item should be in last column + expect(lastItem.getBoundingClientRect().left).toBe(grid.getBoundingClientRect().left + 100); + + grid.remove(); + }); + + it('resolves named lines with auto-placement into implicit grid', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '[start] 100px [middle] 100px [end]'; + grid.style.gridTemplateRows = '[row-start] 60px [row-end]'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#fff3e0'; + + const item1 = document.createElement('div'); + item1.textContent = '1'; + item1.style.gridColumn = 'start / middle'; + item1.style.gridRow = 'row-start / row-end'; + item1.style.backgroundColor = '#FFB74D'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + grid.appendChild(item1); + + // Auto-placed items will flow into implicit rows + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 2}`; + item.style.backgroundColor = `hsl(${40 + i * 10}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + expect(item1.getBoundingClientRect().width).toBe(100); + + grid.remove(); + }); + + it('spans across explicit and implicit tracks with named lines', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, [col] 100px)'; + grid.style.gridTemplateRows = '[row] 60px'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#e8f5e9'; + + const spanItem = document.createElement('div'); + spanItem.textContent = 'Span'; + spanItem.style.gridColumn = 'col 1 / col 2'; + spanItem.style.gridRow = '1 / 3'; // Spans explicit and implicit + spanItem.style.backgroundColor = '#66BB6A'; + spanItem.style.display = 'flex'; + spanItem.style.alignItems = 'center'; + spanItem.style.justifyContent = 'center'; + spanItem.style.color = 'white'; + grid.appendChild(spanItem); + + for (let i = 0; i < 3; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${120 + i * 15}, 60%, 50%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Spanning item: 60px explicit + 70px implicit = 130px + expect(spanItem.getBoundingClientRect().height).toBe(130); + + grid.remove(); + }); + + it('handles auto-generated line names in implicit tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = 'repeat(auto-fill, [row-line] 60px)'; + grid.style.gridAutoRows = '70px'; + grid.style.height = '250px'; + grid.style.gap = '0'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${260 + i * 20}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + expect(items.length).toBe(6); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/implicit-grids/implicit-with-gaps.ts b/integration_tests/specs/css/css-grid/implicit-grids/implicit-with-gaps.ts new file mode 100644 index 0000000000..b931d76a0b --- /dev/null +++ b/integration_tests/specs/css/css-grid/implicit-grids/implicit-with-gaps.ts @@ -0,0 +1,266 @@ +describe('CSS Grid implicit tracks with gaps', () => { + it('applies row gaps to implicit rows', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.gridAutoRows = '70px'; + grid.style.rowGap = '15px'; + grid.style.columnGap = '10px'; + grid.style.backgroundColor = '#f5f5f5'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${i * 60}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Gap between row 1 and row 2 + const gap1 = items[2].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom; + expect(gap1).toBe(15); + // Gap between row 2 and row 3 + const gap2 = items[4].getBoundingClientRect().top - items[2].getBoundingClientRect().bottom; + expect(gap2).toBe(15); + + grid.remove(); + }); + + it('applies column gaps to implicit columns', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = 'repeat(2, 60px)'; + grid.style.gridAutoFlow = 'column'; + grid.style.gridAutoColumns = '110px'; + grid.style.rowGap = '10px'; + grid.style.columnGap = '15px'; + grid.style.backgroundColor = '#e3f2fd'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${200 + i * 30}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Gap between column 1 and column 2 + const gap1 = items[2].getBoundingClientRect().left - items[0].getBoundingClientRect().right; + expect(gap1).toBe(15); + // Gap between column 2 and column 3 + const gap2 = items[4].getBoundingClientRect().left - items[2].getBoundingClientRect().right; + expect(gap2).toBe(15); + + grid.remove(); + }); + + it('applies gaps with mixed explicit and implicit tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '60px 65px'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '12px'; + grid.style.backgroundColor = '#f3e5f5'; + + for (let i = 0; i < 8; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${280 + i * 20}, 70%, 60%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Gap between explicit rows + const explicitGap = items[2].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom; + expect(explicitGap).toBe(12); + // Gap between explicit row 2 and implicit row 3 + const mixedGap = items[4].getBoundingClientRect().top - items[2].getBoundingClientRect().bottom; + expect(mixedGap).toBe(12); + // Gap between implicit rows + const implicitGap = items[6].getBoundingClientRect().top - items[4].getBoundingClientRect().bottom; + expect(implicitGap).toBe(12); + + grid.remove(); + }); + + it('uses gap shorthand with implicit tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '20px 10px'; // row-gap column-gap + grid.style.backgroundColor = '#fff3e0'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${40 + i * 15}, 70%, 65%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Row gap + const rowGap = items[2].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom; + expect(rowGap).toBe(20); + // Column gap + const colGap = items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right; + expect(colGap).toBe(10); + + grid.remove(); + }); + + it('calculates fr units with gaps in implicit grid', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = '100px'; + grid.style.gridTemplateRows = 'repeat(2, 60px)'; + grid.style.gridAutoFlow = 'column'; + grid.style.gridAutoColumns = '1fr'; + grid.style.width = '450px'; + grid.style.columnGap = '10px'; + grid.style.rowGap = '5px'; + grid.style.backgroundColor = '#e8f5e9'; + + for (let i = 0; i < 6; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${120 + i * 15}, 60%, 55%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // First column: 100px + expect(items[0].getBoundingClientRect().width).toBe(100); + // Remaining: 450 - 100 - 10 - 10 = 330px for 2 columns = 165px each + expect(items[2].getBoundingClientRect().width).toBe(165); + expect(items[4].getBoundingClientRect().width).toBe(165); + + grid.remove(); + }); + + it('handles percentage gaps with implicit tracks', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(2, 100px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.gridAutoRows = '70px'; + grid.style.rowGap = '5%'; + grid.style.columnGap = '5%'; + grid.style.width = '300px'; + grid.style.height = '400px'; + grid.style.backgroundColor = '#ede7f6'; + + for (let i = 0; i < 4; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${260 + i * 20}, 70%, 65%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + const items = Array.from(grid.children) as HTMLElement[]; + // Column gap: 5% of 300px = 15px + const colGap = items[1].getBoundingClientRect().left - items[0].getBoundingClientRect().right; + expect(colGap).toBe(15); + // Row gap: 5% of 400px = 20px + const rowGap = items[2].getBoundingClientRect().top - items[0].getBoundingClientRect().bottom; + expect(rowGap).toBe(20); + + grid.remove(); + }); + + it('applies gaps with spanning items in implicit grid', async () => { + const grid = document.createElement('div'); + grid.style.display = 'grid'; + grid.style.gridTemplateColumns = 'repeat(3, 100px)'; + grid.style.gridTemplateRows = '60px'; + grid.style.gridAutoRows = '70px'; + grid.style.gap = '10px'; + grid.style.backgroundColor = '#e0f2f1'; + + // Spanning item across implicit rows + const item1 = document.createElement('div'); + item1.textContent = 'Span'; + item1.style.gridColumn = '1 / span 2'; + item1.style.gridRow = '2 / span 2'; + item1.style.backgroundColor = '#4DB6AC'; + item1.style.display = 'flex'; + item1.style.alignItems = 'center'; + item1.style.justifyContent = 'center'; + item1.style.color = 'white'; + grid.appendChild(item1); + + for (let i = 0; i < 5; i++) { + const item = document.createElement('div'); + item.textContent = `${i + 1}`; + item.style.backgroundColor = `hsl(${160 + i * 15}, 60%, 50%)`; + item.style.display = 'flex'; + item.style.alignItems = 'center'; + item.style.justifyContent = 'center'; + item.style.color = 'white'; + grid.appendChild(item); + } + + document.body.appendChild(grid); + await waitForFrame(); + await snapshot(); + + // Spanning item: 2 rows (70px each) + 1 gap (10px) = 150px + expect(item1.getBoundingClientRect().height).toBe(150); + // Spanning item: 2 columns (100px each) + 1 gap (10px) = 210px + expect(item1.getBoundingClientRect().width).toBe(210); + + grid.remove(); + }); +}); diff --git a/integration_tests/specs/css/css-grid/placement/overlapping-items.ts b/integration_tests/specs/css/css-grid/placement/overlapping-items.ts index 152b7b27d3..cb64d89a7a 100644 --- a/integration_tests/specs/css/css-grid/placement/overlapping-items.ts +++ b/integration_tests/specs/css/css-grid/placement/overlapping-items.ts @@ -153,7 +153,7 @@ describe('CSS Grid overlapping items', () => { grid.remove(); }); - xit('handles negative z-index', async () => { + it('handles negative z-index', async () => { const grid = document.createElement('div'); grid.style.display = 'grid'; grid.style.gridTemplateColumns = 'repeat(3, 90px)'; @@ -207,6 +207,12 @@ describe('CSS Grid overlapping items', () => { expect(getComputedStyle(items[1]).zIndex).toBe('0'); expect(getComputedStyle(items[2]).zIndex).toBe('auto'); + // Negative z-index grid items are painted behind the grid container's background + // when the container doesn't establish a stacking context (z-index: auto). + const item1Rect = items[0].getBoundingClientRect(); + const hit = document.elementFromPoint(item1Rect.left + 5, item1Rect.top + 5); + expect(hit).toBe(grid); + grid.remove(); }); @@ -319,7 +325,7 @@ describe('CSS Grid overlapping items', () => { grid.remove(); }); - xit('overlaps with partial grid areas', async () => { + it('overlaps with partial grid areas', async () => { const grid = document.createElement('div'); grid.style.display = 'grid'; grid.style.gridTemplateColumns = 'repeat(4, 70px)'; diff --git a/webf/lib/src/css/computed_style_declaration.dart b/webf/lib/src/css/computed_style_declaration.dart index b5682808ff..8d9199eac1 100644 --- a/webf/lib/src/css/computed_style_declaration.dart +++ b/webf/lib/src/css/computed_style_declaration.dart @@ -1271,10 +1271,14 @@ String? _valueForGridProperty(String propertyName, CSSRenderStyle style) { return _alignItemsToCss(style.alignItems); case 'align-self': return _alignSelfToCss(style.alignSelf); + case 'align-content': + return _alignContentToCss(style.alignContent); case 'justify-items': return _gridAxisAlignmentToCss(style.justifyItems); case 'justify-self': return _gridAxisAlignmentToCss(style.justifySelf); + case 'justify-content': + return _justifyContentToCss(style.justifyContent); case 'place-items': final alignItems = _alignItemsToCss(style.alignItems); final justifyItems = _gridAxisAlignmentToCss(style.justifyItems); diff --git a/webf/lib/src/css/flexbox.dart b/webf/lib/src/css/flexbox.dart index 55d7cc9416..ce507f0158 100644 --- a/webf/lib/src/css/flexbox.dart +++ b/webf/lib/src/css/flexbox.dart @@ -327,8 +327,9 @@ mixin CSSFlexboxMixin on RenderStyle { case 'normal': return JustifyContent.stretch; case 'flex-end': - case 'end': return JustifyContent.flexEnd; + case 'end': + return JustifyContent.end; case 'center': return JustifyContent.center; case 'space-between': @@ -338,7 +339,9 @@ mixin CSSFlexboxMixin on RenderStyle { case 'space-evenly': return JustifyContent.spaceEvenly; case 'flex-start': + return JustifyContent.flexStart; case 'start': + return JustifyContent.start; default: return JustifyContent.flexStart; } @@ -347,11 +350,13 @@ mixin CSSFlexboxMixin on RenderStyle { static AlignItems resolveAlignItems(String alignItems) { switch (alignItems) { case 'flex-start': - case 'start': return AlignItems.flexStart; + case 'start': + return AlignItems.start; case 'flex-end': - case 'end': return AlignItems.flexEnd; + case 'end': + return AlignItems.end; case 'center': return AlignItems.center; case 'baseline': @@ -368,11 +373,13 @@ mixin CSSFlexboxMixin on RenderStyle { static AlignContent resolveAlignContent(String alignContent) { switch (alignContent) { case 'flex-start': - case 'start': return AlignContent.flexStart; + case 'start': + return AlignContent.start; case 'flex-end': - case 'end': return AlignContent.flexEnd; + case 'end': + return AlignContent.end; case 'center': return AlignContent.center; case 'space-around': @@ -390,11 +397,13 @@ mixin CSSFlexboxMixin on RenderStyle { static AlignSelf resolveAlignSelf(String alignSelf) { switch (alignSelf) { case 'flex-start': - case 'start': return AlignSelf.flexStart; + case 'start': + return AlignSelf.start; case 'flex-end': - case 'end': return AlignSelf.flexEnd; + case 'end': + return AlignSelf.end; case 'center': return AlignSelf.center; case 'stretch': diff --git a/webf/lib/src/css/grid.dart b/webf/lib/src/css/grid.dart index 6aa4fd556f..48fcbf2304 100644 --- a/webf/lib/src/css/grid.dart +++ b/webf/lib/src/css/grid.dart @@ -374,6 +374,9 @@ class CSSGridParser { final GridTrackSize? maxTrack = _parseSingleTrack(maxToken, renderStyle, propertyName, axis, leadingNames: const [], trailingNames: const []); if (minTrack != null && maxTrack != null) { + if (minTrack is GridFitContent || maxTrack is GridFitContent) { + return null; + } return GridMinMax( minTrack, maxTrack, diff --git a/webf/lib/src/rendering/flex.dart b/webf/lib/src/rendering/flex.dart index 17f79925ab..a239918797 100644 --- a/webf/lib/src/rendering/flex.dart +++ b/webf/lib/src/rendering/flex.dart @@ -4852,7 +4852,8 @@ class RenderFlexLayout extends RenderLayoutBox { @override bool hitTestChildren(BoxHitTestResult result, {Offset? position}) { - return defaultHitTestChildren(result, position: position); + if (position == null) return false; + return super.hitTestChildren(result, position: position); } @override diff --git a/webf/lib/src/rendering/flow.dart b/webf/lib/src/rendering/flow.dart index 05c8e6ba81..4af1f74596 100644 --- a/webf/lib/src/rendering/flow.dart +++ b/webf/lib/src/rendering/flow.dart @@ -202,6 +202,25 @@ class RenderFlowLayout extends RenderLayoutBox { @override void performPaint(PaintingContext context, Offset offset) { + bool isNegativeZIndexStackingChild(RenderBox child) { + if (child is! RenderBoxModel) return false; + final int? zi = child.renderStyle.zIndex; + if (zi == null || zi >= 0) return false; + return child.renderStyle.establishesStackingContext; + } + + bool shouldSkipNegativeZIndexChildren() { + if (renderStyle.establishesStackingContext) return false; + for (final RenderBox child in paintingOrder) { + if (!identical(child.parent, this)) continue; + if (isNegativeZIndexStackingChild(child)) return true; + break; + } + return false; + } + + final bool skipNegatives = shouldSkipNegativeZIndexChildren(); + // If using inline formatting context, delegate painting to it first. if (establishIFC && _inlineFormattingContext != null) { // Calculate content offset (adjust for padding and border) @@ -219,6 +238,7 @@ class RenderFlowLayout extends RenderLayoutBox { // Paint positioned direct children in proper stacking order. for (final RenderBox child in paintingOrder) { if (child is RenderBoxModel && child.renderStyle.isSelfPositioned()) { + if (skipNegatives && isNegativeZIndexStackingChild(child)) continue; final RenderLayoutParentData pd = child.parentData as RenderLayoutParentData; if (child.hasSize) context.paintChild(child, pd.offset + offset); } @@ -252,6 +272,7 @@ class RenderFlowLayout extends RenderLayoutBox { } if (!shouldPaint) continue; + if (skipNegatives && isNegativeZIndexStackingChild(child)) continue; final RenderLayoutParentData childParentData = child.parentData as RenderLayoutParentData; if (!child.hasSize) continue; @@ -1884,7 +1905,7 @@ class RenderFlowLayout extends RenderLayoutBox { } // Fallback to default behavior for regular flow layout - return defaultHitTestChildren(result, position: position); + return super.hitTestChildren(result, position: position); } diff --git a/webf/lib/src/rendering/grid.dart b/webf/lib/src/rendering/grid.dart index ee3ad8fae4..b9f446a6c6 100644 --- a/webf/lib/src/rendering/grid.dart +++ b/webf/lib/src/rendering/grid.dart @@ -798,12 +798,6 @@ class RenderGridLayout extends RenderLayoutBox { } } - double? _preferredChildWidth(RenderStyle? childStyle) { - if (childStyle == null) return null; - if (childStyle.width.isAuto) return null; - return childStyle.width.computedValue; - } - List _materializeTrackList( List tracks, double? innerAvailable, @@ -2144,7 +2138,13 @@ class RenderGridLayout extends RenderLayoutBox { } while (rowSizes.length < rowIndex + rowSpan) { - rowSizes.add(0); + final int targetRow = rowSizes.length; + double rowSize = 0.0; + if ((rowsDef.isEmpty || targetRow >= definedRowCount) && autoRowDefs.isNotEmpty) { + final int implicitOrdinal = rowsDef.isEmpty ? targetRow : math.max(0, targetRow - definedRowCount); + rowSize = _resolveAutoTrackAt(autoRowDefs, implicitOrdinal, innerMaxHeight) ?? 0.0; + } + rowSizes.add(rowSize); } while (implicitRowHeights.length < rowIndex + rowSpan) { implicitRowHeights.add(0); @@ -2185,6 +2185,8 @@ class RenderGridLayout extends RenderLayoutBox { if (track is GridAuto) return _IntrinsicTrackKind.auto; if (track is GridMinContent) return _IntrinsicTrackKind.minContent; if (track is GridMaxContent) return _IntrinsicTrackKind.maxContent; + // fit-content() uses max-content sizing, clamped by its limit. + if (track is GridFitContent) return _IntrinsicTrackKind.maxContent; return _IntrinsicTrackKind.none; } @@ -2200,12 +2202,15 @@ class RenderGridLayout extends RenderLayoutBox { final List autoColumnsMask = List.filled(colSizes.length, false); final List minContentColumnsMask = List.filled(colSizes.length, false); final List maxContentColumnsMask = List.filled(colSizes.length, false); + final List fitContentColumnsMask = List.filled(colSizes.length, false); + final List fitContentColumnLimits = List.filled(colSizes.length, 0.0); final List rangeMinColSizes = List.filled(colSizes.length, 0.0); final List rangeMaxColSizes = List.filled(colSizes.length, 0.0); final List flexFactors = List.filled(colSizes.length, 0.0); final List flexMinColSizes = List.filled(colSizes.length, 0.0); bool hasIntrinsicColumns = false; bool hasAutoColumns = false; + bool hasFitContentColumns = false; bool hasRangeColumns = false; bool hasFlexColumns = false; for (int c = 0; c < colSizes.length; c++) { @@ -2229,6 +2234,16 @@ class RenderGridLayout extends RenderLayoutBox { break; } + if (track is GridFitContent) { + fitContentColumnsMask[c] = true; + hasFitContentColumns = true; + // Reset any eagerly-resolved size so intrinsic sizing can determine the max-content contribution. + colSizes[c] = 0.0; + final double limit = _resolveLengthValue(track.limit, contentAvailableWidth); + // Treat negative / non-finite limits as 0 for clamping purposes. + fitContentColumnLimits[c] = (limit.isFinite && limit > 0) ? limit : 0.0; + } + // For `minmax(, )`, allow the intrinsic contribution to determine // the track size (up to the fixed max) instead of eagerly sizing to the max. if (track is GridMinMax && @@ -2277,7 +2292,8 @@ class RenderGridLayout extends RenderLayoutBox { } } - final bool needsColumnSizingResolution = hasIntrinsicColumns || hasRangeColumns || hasFlexColumns; + final bool needsColumnSizingResolution = + hasIntrinsicColumns || hasFitContentColumns || hasRangeColumns || hasFlexColumns; if (needsColumnSizingResolution) { // Track each auto column's min-content contribution so that we can // clamp max-content sizing to the available inline size and allow @@ -2285,8 +2301,13 @@ class RenderGridLayout extends RenderLayoutBox { final List autoMinColSizes = List.filled(colSizes.length, 0.0); void resolveFlexibleAndRangeTracks() { - if (adjustedInnerWidth == null || !adjustedInnerWidth!.isFinite || adjustedInnerWidth! <= 0) return; - final double available = adjustedInnerWidth!; + double? availableTrackSpace = adjustedInnerWidth; + if (contentAvailableWidth != null && contentAvailableWidth.isFinite) { + availableTrackSpace = + math.max(0.0, contentAvailableWidth - colGap * math.max(0, colSizes.length - 1)); + } + if (availableTrackSpace == null || !availableTrackSpace.isFinite || availableTrackSpace <= 0) return; + final double available = availableTrackSpace; double fixedNonFlexNonRange = 0.0; double rangeBaseSum = 0.0; @@ -2587,6 +2608,19 @@ class RenderGridLayout extends RenderLayoutBox { childForIntrinsic = pd.nextSibling; } + // Clamp fit-content() tracks to their specified limit after intrinsic max-content sizing. + if (hasFitContentColumns) { + for (int c = 0; c < colSizes.length; c++) { + if (!fitContentColumnsMask[c]) continue; + final double limit = fitContentColumnLimits[c]; + if (limit <= 0) continue; + final double value = colSizes[c]; + if (value.isFinite && value > limit) { + colSizes[c] = limit; + } + } + } + // Ensure auto tracks are at least their min-content contributions. if (hasAutoColumns) { for (int c = 0; c < colSizes.length; c++) { @@ -2659,6 +2693,160 @@ class RenderGridLayout extends RenderLayoutBox { } } + // Resolve fit-content() row tracks from intrinsic contributions (shrink-wrap), + // clamped by the fit-content() limit per spec. + if (rowSizes.isNotEmpty && resolvedRowDefs.isNotEmpty) { + final int explicitFitRowCount = math.min(rowSizes.length, resolvedRowDefs.length); + final List fitContentRowsMask = List.filled(rowSizes.length, false); + final List fitContentRowLimits = List.filled(rowSizes.length, 0.0); + final List fitContentRowMinSizes = List.filled(rowSizes.length, 0.0); + final List fitContentRowMaxSizes = List.filled(rowSizes.length, 0.0); + + bool hasFitContentRows = false; + for (int r = 0; r < explicitFitRowCount; r++) { + final GridTrackSize track = resolvedRowDefs[r]; + if (track is! GridFitContent) continue; + hasFitContentRows = true; + fitContentRowsMask[r] = true; + // Reset eager sizing so intrinsic contributions can determine track size. + rowSizes[r] = 0.0; + final double limit = _resolveLengthValue(track.limit, contentAvailableHeight); + fitContentRowLimits[r] = (limit.isFinite && limit > 0) ? limit : 0.0; + } + + if (hasFitContentRows) { + RenderBox? childForIntrinsic = firstChild; + while (childForIntrinsic != null) { + final GridLayoutParentData pd = childForIntrinsic.parentData as GridLayoutParentData; + if (_isPositionedGridChild(childForIntrinsic)) { + childForIntrinsic = pd.nextSibling; + continue; + } + + final int rowIndex = pd.rowStart; + final int rowSpan = math.max(1, pd.rowSpan); + if (rowSpan != 1 || rowIndex < 0 || rowIndex >= fitContentRowsMask.length || !fitContentRowsMask[rowIndex]) { + childForIntrinsic = pd.nextSibling; + continue; + } + + final int colIndex = pd.columnStart; + final int colSpan = math.max(1, pd.columnSpan).clamp(1, math.max(1, colSizes.length - colIndex)); + double cellWidth = 0; + for (int c = colIndex; c < math.min(colIndex + colSpan, colSizes.length); c++) { + cellWidth += colSizes[c]; + if (c < colIndex + colSpan - 1) cellWidth += colGap; + } + + final CSSRenderStyle? childStyle = _unwrapGridChildBoxModel(childForIntrinsic)?.renderStyle; + final _GridResolvedMargins childMargins = _resolveGridChildMargins( + childStyle, + cellWidth.isFinite ? cellWidth : null, + ); + final double availableW = cellWidth.isFinite ? math.max(0, cellWidth - childMargins.horizontal) : double.infinity; + + double minH = childForIntrinsic.getMinIntrinsicHeight(availableW); + double maxH = childForIntrinsic.getMaxIntrinsicHeight(availableW); + if (!minH.isFinite || minH < 0) minH = 0; + if (!maxH.isFinite || maxH < 0) maxH = minH; + minH += childMargins.vertical; + maxH += childMargins.vertical; + + if (minH > fitContentRowMinSizes[rowIndex]) { + fitContentRowMinSizes[rowIndex] = minH; + } + if (maxH > fitContentRowMaxSizes[rowIndex]) { + fitContentRowMaxSizes[rowIndex] = maxH; + } + + childForIntrinsic = pd.nextSibling; + } + + for (int r = 0; r < explicitFitRowCount; r++) { + if (!fitContentRowsMask[r]) continue; + double minSize = fitContentRowMinSizes[r]; + double maxSize = fitContentRowMaxSizes[r]; + if (!minSize.isFinite || minSize < 0) minSize = 0.0; + if (!maxSize.isFinite || maxSize < 0) maxSize = 0.0; + if (maxSize < minSize) maxSize = minSize; + + final double limit = fitContentRowLimits[r]; + rowSizes[r] = math.min(maxSize, math.max(minSize, limit)); + } + } + } + + // Resolve flexible (fr) row tracks, including implicit grid-auto-rows tracks. + if (rowSizes.isNotEmpty && contentAvailableHeight != null && contentAvailableHeight.isFinite) { + GridTrackSize rowTrackAt(int index) { + if (index >= 0 && index < resolvedRowDefs.length) { + return resolvedRowDefs[index]; + } + final int implicitIndex = math.max(0, index - resolvedRowDefs.length); + return autoRowDefs.isNotEmpty ? autoRowDefs[implicitIndex % autoRowDefs.length] : const GridAuto(); + } + + double flexFactorForRowTrack(GridTrackSize track) { + if (track is GridFraction) return track.fr; + if (track is GridMinMax && track.maxTrack is GridFraction) { + return (track.maxTrack as GridFraction).fr; + } + return 0.0; + } + + final int rowCount = rowSizes.length; + final double availableTrackSpace = + math.max(0.0, contentAvailableHeight - rowGap * math.max(0, rowCount - 1)); + + double fixed = 0.0; + double frSum = 0.0; + final List frFactors = List.filled(rowCount, 0.0); + final List minFlexSizes = List.filled(rowCount, 0.0); + + for (int r = 0; r < rowCount; r++) { + final GridTrackSize track = rowTrackAt(r); + final double fr = flexFactorForRowTrack(track); + if (fr > 0) { + frFactors[r] = fr; + frSum += fr; + // Reset any eagerly-resolved value so distribution considers all flex tracks. + rowSizes[r] = 0.0; + + if (track is GridMinMax && track.maxTrack is GridFraction) { + final double minSize = + _resolveTrackSize(track.minTrack, availableTrackSpace, percentageBasis: contentAvailableHeight); + if (minSize.isFinite && minSize > 0) { + minFlexSizes[r] = minSize; + } + } + continue; + } + + final double size = rowSizes[r]; + if (size.isFinite && size > 0) { + fixed += size; + } + } + + if (frSum > 0) { + final double remaining = math.max(0.0, availableTrackSpace - fixed); + if (remaining > 0) { + for (int r = 0; r < rowCount; r++) { + final double fr = frFactors[r]; + if (fr <= 0) continue; + final double portion = remaining * (fr / frSum); + rowSizes[r] = math.max(portion, minFlexSizes[r]); + } + } else { + for (int r = 0; r < rowCount; r++) { + final double fr = frFactors[r]; + if (fr <= 0) continue; + rowSizes[r] = minFlexSizes[r]; + } + } + } + } + // Pass 2: layout children with resolved column widths. if (implicitRowHeights.isNotEmpty) { implicitRowHeights = List.filled(implicitRowHeights.length, 0.0, growable: true); @@ -2682,20 +2870,6 @@ class RenderGridLayout extends RenderLayoutBox { final int rowSpan = math.max(1, pd.rowSpan); final int colSpan = math.max(1, pd.columnSpan).clamp(1, math.max(1, colSizes.length - colIndex)); - if (colSpan == 1 && - colIndex < resolvedColumnDefs.length && - resolvedColumnDefs[colIndex] is GridFitContent) { - final double? preferred = _preferredChildWidth(childGridStyle); - if (preferred != null && preferred.isFinite) { - final GridFitContent fitTrack = resolvedColumnDefs[colIndex] as GridFitContent; - final double limit = _resolveLengthValue(fitTrack.limit, adjustedInnerWidth); - final double target = math.max(limit, preferred); - if (target > colSizes[colIndex]) { - colSizes[colIndex] = target; - } - } - } - double xOffset = xStart; for (int c = 0; c < colIndex; c++) { xOffset += colSizes[c]; @@ -2907,6 +3081,7 @@ class RenderGridLayout extends RenderLayoutBox { usedContentWidth += colSizes[c]; if (c < colSizes.length - 1) usedContentWidth += colGap; } + int collapsedAutoFitColumnCount = 0; if (explicitAutoFitColumns != null && explicitAutoFitColumnUsage != null) { double collapsedWidth = 0; int collapsedCount = 0; @@ -2923,6 +3098,7 @@ class RenderGridLayout extends RenderLayoutBox { if (collapsedWidth > 0) { usedContentWidth = math.max(0.0, usedContentWidth - collapsedWidth); justificationColumnCount = math.max(0, justificationColumnCount - collapsedCount); + collapsedAutoFitColumnCount = collapsedCount; } } double usedContentHeight = 0; @@ -3005,17 +3181,51 @@ class RenderGridLayout extends RenderLayoutBox { double verticalFree = math.max(0.0, size.height - verticalPaddingBorder - usedContentHeight); bool relayoutForStretchedTracks = false; - if (horizontalFree > 0 && - renderStyle.justifyContent == JustifyContent.stretch && - justificationColumnCount > 0) { - GridTrackSize columnTrackAt(int index) { - if (index >= 0 && index < resolvedColumnDefs.length) { - return resolvedColumnDefs[index]; + GridTrackSize columnTrackAt(int index) { + if (index >= 0 && index < resolvedColumnDefs.length) { + return resolvedColumnDefs[index]; + } + final int implicitIndex = math.max(0, index - explicitColumnCount); + return autoColDefs.isNotEmpty ? autoColDefs[implicitIndex % autoColDefs.length] : const GridAuto(); + } + + double flexFactorForColumnTrack(GridTrackSize track) { + if (track is GridFraction) return track.fr; + if (track is GridMinMax && track.maxTrack is GridFraction) { + return (track.maxTrack as GridFraction).fr; + } + return 0.0; + } + + // When `auto-fit` collapses empty tracks, the resolved track sizing can gain + // new free space after placement. Per CSS Grid, flexible (fr) tracks should + // absorb this space as part of track sizing (not via justify-content). + if (horizontalFree > 0 && collapsedAutoFitColumnCount > 0 && justificationColumnCount > 0) { + double frSum = 0.0; + final List frFactors = + List.filled(justificationColumnCount, 0.0, growable: false); + for (int c = 0; c < justificationColumnCount; c++) { + final double fr = flexFactorForColumnTrack(columnTrackAt(c)); + if (fr > 0) { + frFactors[c] = fr; + frSum += fr; } - final int implicitIndex = math.max(0, index - explicitColumnCount); - return autoColDefs.isNotEmpty ? autoColDefs[implicitIndex % autoColDefs.length] : const GridAuto(); } + if (frSum > 0) { + for (int c = 0; c < justificationColumnCount; c++) { + final double fr = frFactors[c]; + if (fr <= 0) continue; + colSizes[c] += horizontalFree * (fr / frSum); + } + usedContentWidth += horizontalFree; + horizontalFree = 0; + relayoutForStretchedTracks = true; + } + } + if (horizontalFree > 0 && + renderStyle.justifyContent == JustifyContent.stretch && + justificationColumnCount > 0) { bool isStretchableColumn(GridTrackSize track) { if (track is GridAuto) return true; if (track is GridMinMax) return track.maxTrack is GridAuto; @@ -3355,7 +3565,8 @@ class RenderGridLayout extends RenderLayoutBox { @override bool hitTestChildren(BoxHitTestResult result, {Offset? position}) { - return defaultHitTestChildren(result, position: position); + if (position == null) return false; + return super.hitTestChildren(result, position: position); } @override diff --git a/webf/lib/src/rendering/layout_box.dart b/webf/lib/src/rendering/layout_box.dart index c518928f9c..6026ff0618 100644 --- a/webf/lib/src/rendering/layout_box.dart +++ b/webf/lib/src/rendering/layout_box.dart @@ -20,6 +20,100 @@ abstract class RenderLayoutBox extends RenderBoxModel _cachedPaintingOrder = null; } + bool _isNegativeZIndexStackingChild(RenderBox child) { + if (child is! RenderBoxModel) return false; + final int? zi = child.renderStyle.zIndex; + if (zi == null || zi >= 0) return false; + // Only treat elements that establish a stacking context as z-index participants. + return child.renderStyle.establishesStackingContext; + } + + bool _shouldPaintNegativeZIndexChildrenUnderBackground() { + if (renderStyle.establishesStackingContext) return false; + for (final RenderBox child in paintingOrder) { + if (!identical(child.parent, this)) continue; + if (_isNegativeZIndexStackingChild(child)) return true; + // Negative z-index children are sorted first in paintingOrder; once we hit a non-negative + // direct child, we can stop scanning. + break; + } + return false; + } + + void _paintNegativeZIndexChildrenUnderBackground(PaintingContext context, Offset offset) { + final Offset scrollPaintOffset = paintScrollOffset; + + Offset accumulateOffsetFromDescendant(RenderObject descendant, RenderObject ancestor) { + Offset sum = Offset.zero; + RenderObject? cur = descendant; + while (cur != null && cur != ancestor) { + final Object? pd = (cur is RenderBox) ? (cur.parentData) : null; + if (pd is ContainerBoxParentData) { + sum += pd.offset; + } else if (pd is RenderLayoutParentData) { + sum += pd.offset; + } + cur = cur.parent; + } + return sum; + } + + void paintNegatives(PaintingContext context, Offset offset) { + final Offset contentOffset = offset + scrollPaintOffset; + for (final RenderBox child in paintingOrder) { + if (isPositionPlaceholder(child)) continue; + if (!_isNegativeZIndexStackingChild(child)) break; + if (!child.hasSize) continue; + + final RenderLayoutParentData childParentData = child.parentData as RenderLayoutParentData; + final bool direct = identical(child.parent, this); + final Offset localOffset = direct ? childParentData.offset : accumulateOffsetFromDescendant(child, this); + context.paintChild(child, localOffset + contentOffset); + } + } + + // Ensure negative children painted under background still honor overflow clipping. + if (clipX || clipY) { + final EdgeInsets borderEdge = EdgeInsets.fromLTRB( + renderStyle.effectiveBorderLeftWidth.computedValue, + renderStyle.effectiveBorderTopWidth.computedValue, + renderStyle.effectiveBorderRightWidth.computedValue, + renderStyle.effectiveBorderBottomWidth.computedValue, + ); + final double cw = math.max(0.0, size.width - borderEdge.left - borderEdge.right); + final double ch = math.max(0.0, size.height - borderEdge.top - borderEdge.bottom); + final Rect clipRect = Offset(borderEdge.left, borderEdge.top) & Size(cw, ch); + if (cw <= 0.0 || ch <= 0.0) return; + + final bool needsCompositing = this.needsCompositing; + final decoration = renderStyle.decoration; + if (decoration != null && decoration.hasBorderRadius) { + final BorderRadius radius = decoration.borderRadius!; + final Rect rect = Offset.zero & size; + final RRect borderRRect = radius.toRRect(rect); + final double? borderTop = renderStyle.borderTopWidth?.computedValue; + RRect clipRRect = borderTop != null ? borderRRect.deflate(borderTop) : borderRRect; + if (renderStyle.isSelfRenderReplaced()) { + clipRRect = clipRRect.deflate(renderStyle.paddingTop.computedValue); + } + context.pushClipRRect(needsCompositing, offset, clipRect, clipRRect, paintNegatives); + } else { + context.pushClipRect(needsCompositing, offset, clipRect, paintNegatives); + } + return; + } + + paintNegatives(context, offset); + } + + @override + void paintDecoration(PaintingContext context, Offset offset, PaintingContextCallback callback) { + if (_shouldPaintNegativeZIndexChildrenUnderBackground()) { + _paintNegativeZIndexChildrenUnderBackground(context, offset); + } + super.paintDecoration(context, offset, callback); + } + @override void visitChildrenForSemantics(RenderObjectVisitor visitor) { // Assign stable child indices for scroll semantics (indexInParent / scrollIndex). @@ -264,7 +358,11 @@ abstract class RenderLayoutBox extends RenderBoxModel final int? zi = rs.zIndex; final bool positioned = rs.position != CSSPositionType.static; // z-index: 0 (including flex/grid items) or positioned with z-index:auto - if (zi == 0 || (positioned && zi == null)) return true; + // Also include non-positioned stacking-context roots (e.g. opacity < 1, transform, filter), + // which participate in the auto/0 stacking layer per CSS stacking rules. + if (zi == 0 || (positioned && zi == null) || (!positioned && zi == null && rs.establishesStackingContext)) { + return true; + } } // Unwrap single-child wrappers if (node is RenderObjectWithChildMixin) { @@ -299,6 +397,10 @@ abstract class RenderLayoutBox extends RenderBoxModel } else if (positioned && zi == null) { // Positioned with z-index: auto positionedAutoOrZero.add(child); + } else if (!positioned && zi == null && rs.establishesStackingContext) { + // Non-positioned stacking context roots (e.g. opacity < 1) paint above normal flow + // in the auto/0 stacking layer. + positionedAutoOrZero.add(child); } else { // Non-positioned descendants: if subtree contains any z-index:0 or auto-positioned participants, // elevate this container to the auto/0 layer so those participants paint in the correct phase @@ -431,6 +533,46 @@ abstract class RenderLayoutBox extends RenderBoxModel return boxConstraints; } + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + Offset accumulateOffsetFromDescendant(RenderObject descendant, RenderObject ancestor) { + Offset sum = Offset.zero; + RenderObject? cur = descendant; + while (cur != null && cur != ancestor) { + final Object? pd = (cur is RenderBox) ? (cur.parentData) : null; + if (pd is ContainerBoxParentData) sum += pd.offset; + cur = cur.parent; + } + return sum; + } + + final bool skipNegatives = _shouldPaintNegativeZIndexChildrenUnderBackground(); + for (int i = paintingOrder.length - 1; i >= 0; i--) { + final RenderBox child = paintingOrder[i]; + if (isPositionPlaceholder(child)) continue; + if (skipNegatives && _isNegativeZIndexStackingChild(child)) continue; + if (!child.hasSize) continue; + + final RenderLayoutParentData childParentData = child.parentData as RenderLayoutParentData; + final bool direct = identical(child.parent, this); + final Offset localOffset = direct ? childParentData.offset : accumulateOffsetFromDescendant(child, this); + + final bool isHit = result.addWithPaintOffset( + offset: localOffset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + if (child is RenderBoxModel && child.renderStyle.position == CSSPositionType.fixed) { + transformed -= getTotalScrollOffset(); + } + return child.hitTest(result, position: transformed); + }, + ); + if (isHit) return true; + } + + return false; + } + @override void performPaint(PaintingContext context, Offset offset) { Offset accumulateOffsetFromDescendant(RenderObject descendant, RenderObject ancestor) { @@ -449,9 +591,11 @@ abstract class RenderLayoutBox extends RenderBoxModel return sum; } + final bool skipNegatives = _shouldPaintNegativeZIndexChildrenUnderBackground(); for (int i = 0; i < paintingOrder.length; i++) { RenderBox child = paintingOrder[i]; if (isPositionPlaceholder(child)) continue; + if (skipNegatives && _isNegativeZIndexStackingChild(child)) continue; final RenderLayoutParentData childParentData = child.parentData as RenderLayoutParentData; if (!child.hasSize) continue; diff --git a/webf/lib/src/rendering/widget.dart b/webf/lib/src/rendering/widget.dart index b495f30196..d6d94129f4 100644 --- a/webf/lib/src/rendering/widget.dart +++ b/webf/lib/src/rendering/widget.dart @@ -193,7 +193,9 @@ class RenderWidget extends RenderBoxModel final rs = node.renderStyle; final int? zi = rs.zIndex; final bool positioned = rs.position != CSSPositionType.static; - if (zi == 0 || (positioned && zi == null)) return true; + if (zi == 0 || (positioned && zi == null) || (!positioned && zi == null && rs.establishesStackingContext)) { + return true; + } } if (node is RenderObjectWithChildMixin) { final RenderBox? c = (node as dynamic).child as RenderBox?; @@ -227,6 +229,8 @@ class RenderWidget extends RenderBoxModel positionedAutoOrZero.add(c); } else if (positioned && zi == null) { positionedAutoOrZero.add(c); + } else if (!positioned && zi == null && rs.establishesStackingContext) { + positionedAutoOrZero.add(c); } else { // If subtree contains any z-index:0 or auto-positioned participants, // elevate this container to the auto/0 layer for global ordering. @@ -323,6 +327,96 @@ class RenderWidget extends RenderBoxModel _cachedPaintingOrder = null; } + bool _isNegativeZIndexStackingChild(RenderBox child) { + if (child is! RenderBoxModel) return false; + final int? zi = child.renderStyle.zIndex; + if (zi == null || zi >= 0) return false; + return child.renderStyle.establishesStackingContext; + } + + bool _shouldPaintNegativeZIndexChildrenUnderBackground() { + if (renderStyle.establishesStackingContext) return false; + for (final RenderBox child in paintingOrder) { + if (!identical(child.parent, this)) continue; + if (_isNegativeZIndexStackingChild(child)) return true; + break; + } + return false; + } + + void _paintNegativeZIndexChildrenUnderBackground(PaintingContext context, Offset offset) { + final Offset scrollPaintOffset = paintScrollOffset; + + Offset accumulateOffsetFromDescendant(RenderObject descendant, RenderObject ancestor) { + Offset sum = Offset.zero; + RenderObject? cur = descendant; + while (cur != null && cur != ancestor) { + final Object? pd = (cur is RenderBox) ? (cur.parentData) : null; + if (pd is ContainerBoxParentData) { + sum += pd.offset; + } else if (pd is RenderLayoutParentData) { + sum += pd.offset; + } + cur = cur.parent; + } + return sum; + } + + void paintNegatives(PaintingContext context, Offset offset) { + final Offset contentOffset = offset + scrollPaintOffset; + for (final RenderBox child in paintingOrder) { + if (isPositionPlaceholder(child)) continue; + if (!_isNegativeZIndexStackingChild(child)) break; + if (!child.hasSize) continue; + + final RenderLayoutParentData pd = child.parentData as RenderLayoutParentData; + final bool direct = identical(child.parent, this); + final Offset localOffset = direct ? pd.offset : accumulateOffsetFromDescendant(child, this); + context.paintChild(child, contentOffset + localOffset); + } + } + + if (clipX || clipY) { + final EdgeInsets borderEdge = EdgeInsets.fromLTRB( + renderStyle.effectiveBorderLeftWidth.computedValue, + renderStyle.effectiveBorderTopWidth.computedValue, + renderStyle.effectiveBorderRightWidth.computedValue, + renderStyle.effectiveBorderBottomWidth.computedValue, + ); + final double cw = math.max(0.0, size.width - borderEdge.left - borderEdge.right); + final double ch = math.max(0.0, size.height - borderEdge.top - borderEdge.bottom); + final Rect clipRect = Offset(borderEdge.left, borderEdge.top) & Size(cw, ch); + if (cw <= 0.0 || ch <= 0.0) return; + + final bool needsCompositing = this.needsCompositing; + final decoration = renderStyle.decoration; + if (decoration != null && decoration.hasBorderRadius) { + final BorderRadius radius = decoration.borderRadius!; + final Rect rect = Offset.zero & size; + final RRect borderRRect = radius.toRRect(rect); + final double? borderTop = renderStyle.borderTopWidth?.computedValue; + RRect clipRRect = borderTop != null ? borderRRect.deflate(borderTop) : borderRRect; + if (renderStyle.isSelfRenderReplaced()) { + clipRRect = clipRRect.deflate(renderStyle.paddingTop.computedValue); + } + context.pushClipRRect(needsCompositing, offset, clipRect, clipRRect, paintNegatives); + } else { + context.pushClipRect(needsCompositing, offset, clipRect, paintNegatives); + } + return; + } + + paintNegatives(context, offset); + } + + @override + void paintDecoration(PaintingContext context, Offset offset, PaintingContextCallback callback) { + if (_shouldPaintNegativeZIndexChildrenUnderBackground()) { + _paintNegativeZIndexChildrenUnderBackground(context, offset); + } + super.paintDecoration(context, offset, callback); + } + // Intrinsic sizing for WidgetElement containers: forward to the primary // non-positioned child, including paddings and borders. @override @@ -572,8 +666,8 @@ class RenderWidget extends RenderBoxModel } } - Offset accumulateOffsetFromDescendant( - RenderObject descendant, RenderObject ancestor) { + final bool skipNegatives = _shouldPaintNegativeZIndexChildrenUnderBackground(); + Offset accumulateOffsetFromDescendant(RenderObject descendant, RenderObject ancestor) { Offset sum = Offset.zero; RenderObject? cur = descendant; while (cur != null && cur != ancestor) { @@ -590,8 +684,8 @@ class RenderWidget extends RenderBoxModel for (final RenderBox child in paintingOrder) { if (isPositionPlaceholder(child)) continue; - final RenderLayoutParentData pd = - child.parentData as RenderLayoutParentData; + if (skipNegatives && _isNegativeZIndexStackingChild(child)) continue; + final RenderLayoutParentData pd = child.parentData as RenderLayoutParentData; if (!child.hasSize) continue; bool restoreFlag = false; @@ -644,34 +738,51 @@ class RenderWidget extends RenderBoxModel return hitTestIntrinsicChild(result, firstChild, position!); } - RenderBox? child = lastChild; - while (child != null) { - final RenderLayoutParentData childParentData = - child.parentData as RenderLayoutParentData; + if (position == null) return false; + + final bool skipNegatives = _shouldPaintNegativeZIndexChildrenUnderBackground(); + + Offset accumulateOffsetFromDescendant(RenderObject descendant, RenderObject ancestor) { + Offset sum = Offset.zero; + RenderObject? cur = descendant; + while (cur != null && cur != ancestor) { + final Object? pd = (cur is RenderBox) ? (cur.parentData) : null; + if (pd is ContainerBoxParentData) { + sum += pd.offset; + } else if (pd is RenderLayoutParentData) { + sum += pd.offset; + } + cur = cur.parent; + } + return sum; + } + + for (int i = paintingOrder.length - 1; i >= 0; i--) { + final RenderBox child = paintingOrder[i]; + if (isPositionPlaceholder(child)) continue; + if (skipNegatives && _isNegativeZIndexStackingChild(child)) continue; + if (!child.hasSize) continue; + + final RenderLayoutParentData pd = child.parentData as RenderLayoutParentData; + final bool direct = identical(child.parent, this); + final Offset localOffset = direct ? pd.offset : accumulateOffsetFromDescendant(child, this); final bool isHit = result.addWithPaintOffset( - offset: childParentData.offset, - position: position!, + offset: localOffset, + position: position, hitTest: (BoxHitTestResult result, Offset transformed) { - assert(transformed == position - childParentData.offset); - if (child is RenderBoxModel) { - CSSPositionType positionType = child.renderStyle.position; + final CSSPositionType positionType = child.renderStyle.position; if (positionType == CSSPositionType.fixed) { // Keep hit testing in sync with RenderBoxModel.paintBoxModel. final Offset o = child.getFixedScrollCompensation(); if (o.dx != 0.0 || o.dy != 0.0) transformed -= o; } } - - return child!.hitTest(result, position: transformed); + return child.hitTest(result, position: transformed); }, ); - if (isHit) { - return true; - } - - child = childParentData.previousSibling; + if (isHit) return true; } return false;