@@ -9,6 +9,74 @@ import { VueTemplateModel } from './VueTemplateModel';
99import httpVueLoader from './httpVueLoader' ;
1010import { TemplateModel } from './Template' ;
1111
12+ function normalizeScopeId ( value ) {
13+ return String ( value ) . replace ( / [ ^ a - z A - Z 0 - 9 _ - ] / g, '-' ) ;
14+ }
15+
16+ function getScopeId ( model , cssId ) {
17+ const base = cssId || model . cid ;
18+ return `data-s-${ normalizeScopeId ( base ) } ` ;
19+ }
20+
21+ function applyScopeId ( vm , scopeId ) {
22+ if ( ! scopeId || ! vm || ! vm . $el ) {
23+ return ;
24+ }
25+ vm . $el . setAttribute ( scopeId , '' ) ;
26+ }
27+
28+ function scopeStyleElement ( styleElt , scopeId ) {
29+ const scopeSelector = `[${ scopeId } ]` ;
30+
31+ function scopeRules ( rules , insertRule , deleteRule ) {
32+ for ( let i = 0 ; i < rules . length ; ++ i ) {
33+ const rule = rules [ i ] ;
34+ if ( rule . type === 1 && rule . selectorText ) {
35+ const scopedSelectors = [ ] ;
36+ rule . selectorText . split ( / \s * , \s * / ) . forEach ( ( sel ) => {
37+ scopedSelectors . push ( `${ scopeSelector } ${ sel } ` ) ;
38+ const segments = sel . match ( / ( [ ^ : ] + ) ( .+ ) ? / ) ;
39+ if ( segments ) {
40+ scopedSelectors . push ( `${ segments [ 1 ] } ${ scopeSelector } ${ segments [ 2 ] || '' } ` ) ;
41+ }
42+ } ) ;
43+ const scopedRule = scopedSelectors . join ( ',' ) + rule . cssText . substr ( rule . selectorText . length ) ;
44+ deleteRule ( i ) ;
45+ insertRule ( scopedRule , i ) ;
46+ continue ;
47+ }
48+ if ( rule . cssRules && rule . cssRules . length && rule . insertRule && rule . deleteRule ) {
49+ scopeRules ( rule . cssRules , rule . insertRule . bind ( rule ) , rule . deleteRule . bind ( rule ) ) ;
50+ }
51+ }
52+ }
53+
54+ function process ( ) {
55+ const sheet = styleElt . sheet ;
56+ if ( ! sheet ) {
57+ return ;
58+ }
59+ scopeRules ( sheet . cssRules , sheet . insertRule . bind ( sheet ) , sheet . deleteRule . bind ( sheet ) ) ;
60+ }
61+
62+ try {
63+ process ( ) ;
64+ } catch ( ex ) {
65+ if ( typeof DOMException !== 'undefined' && ex instanceof DOMException && ex . code === DOMException . INVALID_ACCESS_ERR ) {
66+ styleElt . sheet . disabled = true ;
67+ styleElt . addEventListener ( 'load' , function onStyleLoaded ( ) {
68+ styleElt . removeEventListener ( 'load' , onStyleLoaded ) ;
69+ setTimeout ( ( ) => {
70+ process ( ) ;
71+ styleElt . sheet . disabled = false ;
72+ } ) ;
73+ } ) ;
74+ return ;
75+ }
76+ throw ex ;
77+ }
78+ }
79+
1280export function vueTemplateRender ( createElement , model , parentView ) {
1381 return createElement ( createComponentObject ( model , parentView ) ) ;
1482}
@@ -32,6 +100,10 @@ function createComponentObject(model, parentView) {
32100
33101 const css = model . get ( 'css' ) || ( vuefile . STYLE && vuefile . STYLE . content ) ;
34102 const cssId = ( vuefile . STYLE && vuefile . STYLE . id ) ;
103+ const scopedFromTemplate = ( vuefile . STYLE && vuefile . STYLE . scoped ) ;
104+ const scoped = model . get ( 'scoped' ) ;
105+ const useScoped = scoped !== null && scoped !== undefined ? scoped : scopedFromTemplate ;
106+ const scopeId = useScoped && css ? getScopeId ( model , cssId ) : null ;
35107
36108 if ( css ) {
37109 if ( cssId ) {
@@ -42,14 +114,27 @@ function createComponentObject(model, parentView) {
42114 style . id = prefixedCssId ;
43115 document . head . appendChild ( style ) ;
44116 }
45- if ( style . innerHTML !== css ) {
46- style . innerHTML = css ;
117+ if ( scopeId ) {
118+ if ( style . innerHTML !== css || style . getAttribute ( 'data-ipyvue-scope' ) !== scopeId ) {
119+ style . innerHTML = css ;
120+ scopeStyleElement ( style , scopeId ) ;
121+ style . setAttribute ( 'data-ipyvue-scope' , scopeId ) ;
122+ }
123+ } else {
124+ if ( style . innerHTML !== css ) {
125+ style . innerHTML = css ;
126+ }
127+ style . removeAttribute ( 'data-ipyvue-scope' ) ;
47128 }
48129 } else {
49130 const style = document . createElement ( 'style' ) ;
50131 style . id = model . cid ;
51132 style . innerHTML = css ;
52133 document . head . appendChild ( style ) ;
134+ if ( scopeId ) {
135+ scopeStyleElement ( style , scopeId ) ;
136+ style . setAttribute ( 'data-ipyvue-scope' , scopeId ) ;
137+ }
53138 parentView . once ( 'remove' , ( ) => {
54139 document . head . removeChild ( style ) ;
55140 } ) ;
@@ -106,15 +191,18 @@ function createComponentObject(model, parentView) {
106191 ? template
107192 : vuefile . TEMPLATE ,
108193 beforeMount ( ) {
194+ applyScopeId ( this , scopeId ) ;
109195 callVueFn ( 'beforeMount' , this ) ;
110196 } ,
111197 mounted ( ) {
198+ applyScopeId ( this , scopeId ) ;
112199 callVueFn ( 'mounted' , this ) ;
113200 } ,
114201 beforeUpdate ( ) {
115202 callVueFn ( 'beforeUpdate' , this ) ;
116203 } ,
117204 updated ( ) {
205+ applyScopeId ( this , scopeId ) ;
118206 callVueFn ( 'updated' , this ) ;
119207 } ,
120208 beforeDestroy ( ) {
@@ -130,7 +218,7 @@ function createComponentObject(model, parentView) {
130218function createDataMapping ( model ) {
131219 return model . keys ( )
132220 . filter ( prop => ! prop . startsWith ( '_' )
133- && ! [ 'events' , 'template' , 'components' , 'layout' , 'css' , 'data' , 'methods' ] . includes ( prop ) )
221+ && ! [ 'events' , 'template' , 'components' , 'layout' , 'css' , 'scoped' , ' data', 'methods' ] . includes ( prop ) )
134222 . reduce ( ( result , prop ) => {
135223 result [ prop ] = _ . cloneDeep ( model . get ( prop ) ) ; // eslint-disable-line no-param-reassign
136224 return result ;
@@ -140,7 +228,7 @@ function createDataMapping(model) {
140228function addModelListeners ( model , vueModel ) {
141229 model . keys ( )
142230 . filter ( prop => ! prop . startsWith ( '_' )
143- && ! [ 'v_model' , 'components' , 'layout' , 'css' , 'data' , 'methods' ] . includes ( prop ) )
231+ && ! [ 'v_model' , 'components' , 'layout' , 'css' , 'scoped' , ' data', 'methods' ] . includes ( prop ) )
144232 // eslint-disable-next-line no-param-reassign
145233 . forEach ( prop => model . on ( `change:${ prop } ` , ( ) => {
146234 if ( _ . isEqual ( model . get ( prop ) , vueModel [ prop ] ) ) {
@@ -166,7 +254,7 @@ function addModelListeners(model, vueModel) {
166254
167255function createWatches ( model , parentView , templateWatchers ) {
168256 const modelWatchers = model . keys ( ) . filter ( prop => ! prop . startsWith ( '_' )
169- && ! [ 'events' , 'template' , 'components' , 'layout' , 'css' , 'data' , 'methods' ] . includes ( prop ) )
257+ && ! [ 'events' , 'template' , 'components' , 'layout' , 'css' , 'scoped' , ' data', 'methods' ] . includes ( prop ) )
170258 . reduce ( ( result , prop ) => ( {
171259 ...result ,
172260 [ prop ] : {
@@ -349,8 +437,10 @@ function readVueFile(fileContent) {
349437 }
350438 if ( component . styles && component . styles . length > 0 ) {
351439 const { content } = component . styles [ 0 ] ;
352- const { id } = component . styles [ 0 ] . attrs ;
353- result . STYLE = { content, id } ;
440+ const { attrs = { } } = component . styles [ 0 ] ;
441+ const { id } = attrs ;
442+ const scoped = Object . prototype . hasOwnProperty . call ( attrs , 'scoped' ) ;
443+ result . STYLE = { content, id, scoped } ;
354444 }
355445
356446 return result ;
0 commit comments