@@ -2,18 +2,21 @@ import {
22 type NormalizedStyle ,
33 camelize ,
44 canSetValueDirectly ,
5+ getEscapedCssVarName ,
56 includeBooleanAttr ,
67 isArray ,
78 isOn ,
89 isString ,
910 normalizeClass ,
11+ normalizeCssVarValue ,
1012 normalizeStyle ,
1113 parseStringStyle ,
1214 stringifyStyle ,
1315 toDisplayString ,
1416} from '@vue/shared'
1517import { on } from './event'
1618import {
19+ type GenericComponentInstance ,
1720 MismatchTypes ,
1821 currentInstance ,
1922 getAttributeMismatch ,
@@ -23,6 +26,7 @@ import {
2326 isValidHtmlOrSvgAttribute ,
2427 mergeProps ,
2528 patchStyle ,
29+ queuePostFlushCb ,
2630 shouldSetAsProp ,
2731 toClassSet ,
2832 toStyleMap ,
@@ -38,7 +42,7 @@ import {
3842 isVaporComponent ,
3943} from '../component'
4044import { isHydrating , logMismatchError } from './hydration'
41- import type { Block } from '../block'
45+ import { type Block , normalizeBlock } from '../block'
4246import type { VaporElement } from '../apiDefineVaporCustomElement'
4347
4448type TargetElement = Element & {
@@ -224,18 +228,44 @@ function setClassIncremental(el: any, value: any): void {
224228 }
225229}
226230
231+ /**
232+ * dev only
233+ * if a component uses style v-bind, or an el's style contains style vars (potentially
234+ * fallthrough from parent components), then style matching checks must be deferred until
235+ * after hydration (when instance.block is set). At this point, it can be confirmed
236+ * whether the el is at the root level of the component.
237+ */
238+ function shouldDeferCheckStyleMismatch ( el : TargetElement ) : boolean {
239+ return (
240+ __DEV__ &&
241+ ( ! ! currentInstance ! . getCssVars ||
242+ Object . values ( ( el as HTMLElement ) . style ) . some ( v => v . startsWith ( '--' ) ) )
243+ )
244+ }
245+
227246export function setStyle ( el : TargetElement , value : any ) : void {
228247 if ( el . $root ) {
229248 setStyleIncremental ( el , value )
230249 } else {
231250 const normalizedValue = normalizeStyle ( value )
232251 if (
233252 ( __DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__ ) &&
234- isHydrating &&
235- ! styleHasMismatch ( el , value , normalizedValue , false )
253+ isHydrating
236254 ) {
237- el . $sty = normalizedValue
238- return
255+ if ( shouldDeferCheckStyleMismatch ( el ) ) {
256+ const instance = currentInstance as VaporComponentInstance
257+ queuePostFlushCb ( ( ) => {
258+ if ( ! styleHasMismatch ( el , value , normalizedValue , false , instance ) ) {
259+ el . $sty = normalizedValue
260+ return
261+ }
262+ patchStyle ( el , el . $sty , ( el . $sty = normalizedValue ) )
263+ } )
264+ return
265+ } else if ( ! styleHasMismatch ( el , value , normalizedValue , false ) ) {
266+ el . $sty = normalizedValue
267+ return
268+ }
239269 }
240270
241271 patchStyle ( el , el . $sty , ( el . $sty = normalizedValue ) )
@@ -248,13 +278,21 @@ function setStyleIncremental(el: any, value: any): NormalizedStyle | undefined {
248278 ? parseStringStyle ( value )
249279 : ( normalizeStyle ( value ) as NormalizedStyle | undefined )
250280
251- if (
252- ( __DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__ ) &&
253- isHydrating &&
254- ! styleHasMismatch ( el , value , normalizedValue , true )
255- ) {
256- el [ cacheKey ] = normalizedValue
257- return
281+ if ( ( __DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__ ) && isHydrating ) {
282+ if ( shouldDeferCheckStyleMismatch ( el ) ) {
283+ const instance = currentInstance as VaporComponentInstance
284+ queuePostFlushCb ( ( ) => {
285+ if ( ! styleHasMismatch ( el , value , normalizedValue , true , instance ) ) {
286+ el [ cacheKey ] = normalizedValue
287+ return
288+ }
289+ patchStyle ( el , el [ cacheKey ] , ( el [ cacheKey ] = normalizedValue ) )
290+ } )
291+ return
292+ } else if ( ! styleHasMismatch ( el , value , normalizedValue , true ) ) {
293+ el [ cacheKey ] = normalizedValue
294+ return
295+ }
258296 }
259297
260298 patchStyle ( el , el [ cacheKey ] , ( el [ cacheKey ] = normalizedValue ) )
@@ -554,6 +592,7 @@ function styleHasMismatch(
554592 value : any ,
555593 normalizedValue : string | NormalizedStyle | undefined ,
556594 isIncremental : boolean ,
595+ instance = currentInstance ,
557596) : boolean {
558597 const actual = el . getAttribute ( 'style' )
559598 const actualStyleMap = toStyleMap ( actual || '' )
@@ -565,7 +604,10 @@ function styleHasMismatch(
565604 expectedStyleMap . set ( 'display' , 'none' )
566605 }
567606
568- // TODO: handle css vars
607+ // handle css vars
608+ if ( instance ) {
609+ resolveCssVars ( instance as VaporComponentInstance , el , expectedStyleMap )
610+ }
569611
570612 let hasMismatch : boolean = false
571613 if ( isIncremental ) {
@@ -588,6 +630,44 @@ function styleHasMismatch(
588630 return false
589631}
590632
633+ /**
634+ * dev only
635+ */
636+ function resolveCssVars (
637+ instance : VaporComponentInstance ,
638+ block : Block ,
639+ expectedMap : Map < string , string > ,
640+ ) : void {
641+ if ( __DEV__ && ! instance . isMounted ) {
642+ throw new Error (
643+ 'resolveCssVars should NOT be called before component is mounted' ,
644+ )
645+ }
646+
647+ const rootBlocks = normalizeBlock ( instance )
648+ if (
649+ ( instance as GenericComponentInstance ) . getCssVars &&
650+ normalizeBlock ( block ) . every ( b => rootBlocks . includes ( b ) )
651+ ) {
652+ const cssVars = ( instance as GenericComponentInstance ) . getCssVars ! ( )
653+ for ( const key in cssVars ) {
654+ const value = normalizeCssVarValue ( cssVars [ key ] )
655+ expectedMap . set ( `--${ getEscapedCssVarName ( key , false ) } ` , value )
656+ }
657+ }
658+
659+ if (
660+ normalizeBlock ( block ) . every ( b => rootBlocks . includes ( b ) ) &&
661+ instance . parent
662+ ) {
663+ resolveCssVars (
664+ instance . parent as VaporComponentInstance ,
665+ instance . block ,
666+ expectedMap ,
667+ )
668+ }
669+ }
670+
591671function attributeHasMismatch ( el : any , key : string , value : any ) : boolean {
592672 if ( isValidHtmlOrSvgAttribute ( el , key ) ) {
593673 const { actual, expected } = getAttributeMismatch ( el , key , value )
0 commit comments