1- import {
2- whichElement ,
3- isBlockContainer ,
4- type ElementWithStyle ,
5- hasIntrinsicAspectRatio ,
6- getOwnerWindow ,
7- } from './utils' ;
1+ import { whichElement , isBlockContainer , getOwnerWindow } from './utils' ;
82
9- export function getStyle ( element : Element , pseudoElt ?: string ) {
3+ type Style = CSSStyleDeclaration ;
4+
5+ export function getStyle ( element : Element , pseudoElt ?: string ) : Style {
106 return getOwnerWindow ( element ) . getComputedStyle ( element , pseudoElt ) ;
117}
128
13- function toCSSText ( styles : Record < string , string > ) {
14- let cssText = '' ;
15- const properties = Object . keys ( styles ) ;
9+ export function compareRule ( selector : string , text : string ) {
10+ return `${ selector } {${ text } }` ;
11+ }
12+
13+ function compare ( cssText : string , property : Lowercase < string > , value : string ) {
14+ return value ? cssText + property + ': ' + value + ';' : cssText ;
15+ }
16+
17+ function isBorderChanged ( targetStyle : Style , originStyle : Style ) {
18+ const border = originStyle . borderStyle ;
19+ return border !== targetStyle . borderStyle && border !== 'none' && border !== 'hidden' ;
20+ }
21+
22+ function isSizeChanged ( targetStyle : Style , originStyle : Style ) {
23+ // Changing `padding`, `margin` or `border` alters the element’s size.
24+ return (
25+ originStyle . boxSizing === 'border-box' &&
26+ ( originStyle . padding !== targetStyle . padding || originStyle . borderWidth !== targetStyle . borderWidth )
27+ ) ;
28+ }
29+
30+ /** Whether the element has intrinsic aspect ratio */
31+ function isIntrinsicAspectRatio ( el : Element , style : Style ) {
32+ if ( whichElement ( el , 'img' ) || whichElement ( el , 'video' ) ) return true ;
33+ // SVG element’s aspect ratio is dictated by its `viewBox` by default.
34+ if ( whichElement ( el , 'svg' ) && el . getAttribute ( 'viewBox' ) ) return true ;
35+ return ! ! style . aspectRatio && style . aspectRatio !== 'auto' ;
36+ }
37+
38+ function diff ( cssText : string , properties : ArrayLike < string > , targetStyle : Style , originStyle : Style ) {
1639 for ( let i = 0 ; i < properties . length ; i ++ ) {
17- const property = properties [ i ] ;
18- cssText += `${ property } :${ styles [ property ] } ;` ;
40+ const property = properties [ i ] as Lowercase < string > ;
41+ const value = originStyle . getPropertyValue ( property ) ;
42+ if ( value && value !== targetStyle . getPropertyValue ( property ) ) {
43+ cssText = compare ( cssText , property , value ) ;
44+ }
1945 }
2046 return cssText ;
2147}
2248
2349// When accessing `CSSStyleDeclaration` by index, the property name doesn’t include `counter`.
2450const CSS_PROPERTIES_ADDED = [ 'counter-reset' , 'counter-set' , 'counter-increment' ] as const ;
25- const BORDER_PATTERN = / ^ b o r d e r / ;
26- // Changing `padding`, `margin` or `border` alters the element’s size.
27- const SIZE_PATTERN = / ^ ( p a d d i n g | m a r g i n | b o r d e r ) / ;
2851
29- function getCSSText ( targetStyle : CSSStyleDeclaration , originStyle : CSSStyleDeclaration , origin : Element ) {
30- const styles : Record < string , string > = { } ;
31- // If `border-style` is neither `none` nor `hidden`, the browser falls back the corresponding border-width to its initial value—medium
52+ function getCSSText ( targetStyle : Style , originStyle : Style , origin : Element ) {
53+ let cssText = '' ;
54+ cssText = diff ( cssText , originStyle , targetStyle , originStyle ) ;
55+ cssText = diff ( cssText , CSS_PROPERTIES_ADDED , targetStyle , originStyle ) ;
56+ // If `border-style` is neither `none` nor `hidden`, the browser falls back the corresponding `border-width` to its initial value—medium
3257 // (3 px per spec, though engines variously resolve it to 2 px or 3 px).
33- let isBorderChanged = false ;
58+ if ( isBorderChanged ( targetStyle , originStyle ) ) {
59+ cssText = compare ( cssText , 'border-width' , originStyle . borderWidth ) ;
60+ }
3461 // For elements with an aspect ratio, always supply both width and height
3562 // to prevent incorrect auto-sizing based on that ratio.
36- let isSizeChanged = hasIntrinsicAspectRatio ( origin as ElementWithStyle ) ;
37-
38- for ( let index = 0 ; index < originStyle . length ; index ++ ) {
39- const property = originStyle [ index ] ;
40- const value = originStyle . getPropertyValue ( property ) ;
41- if ( value && value !== targetStyle . getPropertyValue ( property ) ) {
42- styles [ property ] = value ;
43- isBorderChanged ||= BORDER_PATTERN . test ( property ) ;
44- isSizeChanged ||= isBorderChanged || SIZE_PATTERN . test ( property ) ;
45- }
46- }
47-
48- for ( let index = 0 ; index < CSS_PROPERTIES_ADDED . length ; index ++ ) {
49- const property = CSS_PROPERTIES_ADDED [ index ] ;
50- const value = originStyle . getPropertyValue ( property ) ;
51- if ( value && value !== targetStyle . getPropertyValue ( property ) ) {
52- styles [ property ] = value ;
53- }
63+ if ( isSizeChanged ( targetStyle , originStyle ) || isIntrinsicAspectRatio ( origin , originStyle ) ) {
64+ cssText = compare ( cssText , 'width' , originStyle . width ) ;
65+ cssText = compare ( cssText , 'height' , originStyle . height ) ;
5466 }
55-
56- if ( isBorderChanged ) {
57- styles [ 'border-width' ] = originStyle . borderWidth ;
58- }
59- if ( isSizeChanged ) {
60- styles . width = originStyle . width ;
61- styles . height = originStyle . height ;
62- } else if ( originStyle . display === 'table' ) {
63- // The `table` layout is always influenced by content;
64- // whether `table-layout` is `auto` or `fixed`, we must give the table an explicit width to ensure accuracy.
65- styles . width = originStyle . width ;
67+ // The `table` layout is always influenced by content;
68+ // whether `table-layout` is `auto` or `fixed`, we must give the table an explicit width to ensure accuracy.
69+ else if ( originStyle . display === 'table' ) {
70+ cssText = compare ( cssText , 'width' , originStyle . width ) ;
6671 }
67- return toCSSText ( styles ) ;
72+ return cssText ;
6873}
6974
7075/** Clone element style; identical inline styles are omitted. */
71- export function getElementStyle < T extends ElementWithStyle > ( target : T , origin : T , originStyle : CSSStyleDeclaration ) {
76+ export function getElementStyle < T extends Element > ( target : T , origin : T , originStyle : Style ) {
7277 return getCSSText ( getStyle ( target ) , originStyle , origin ) ;
7378}
7479
@@ -86,7 +91,7 @@ export const PSEUDO_ELECTORS = [
8691export function getPseudoElementStyle < T extends Element > (
8792 target : T ,
8893 origin : T ,
89- originStyle : CSSStyleDeclaration ,
94+ originStyle : Style ,
9095 pseudoElt : ( typeof PSEUDO_ELECTORS ) [ number ]
9196) {
9297 if ( pseudoElt === '::placeholder' ) {
0 commit comments