UNPKG

10.9 kBJavaScriptView Raw
1import * as is from '../is';
2import * as util from '../util';
3
4function styleCache( key, fn, ele ){
5 var _p = ele._private;
6 var cache = _p.styleCache = _p.styleCache || [];
7 var val;
8
9 if( (val = cache[key]) != null ){
10 return val;
11 } else {
12 val = cache[key] = fn( ele );
13
14 return val;
15 }
16}
17
18function cacheStyleFunction( key, fn ){
19 key = util.hashString( key );
20
21 return function cachedStyleFunction( ele ){
22 return styleCache( key, fn, ele );
23 };
24}
25
26function cachePrototypeStyleFunction( key, fn ){
27 key = util.hashString( key );
28
29 let selfFn = ele => fn.call( ele );
30
31 return function cachedPrototypeStyleFunction(){
32 var ele = this[0];
33
34 if( ele ){
35 return styleCache( key, selfFn, ele );
36 }
37 };
38}
39
40let elesfn = ({
41
42 recalculateRenderedStyle: function( useCache ){
43 let cy = this.cy();
44 let renderer = cy.renderer();
45 let styleEnabled = cy.styleEnabled();
46
47 if( renderer && styleEnabled ){
48 renderer.recalculateRenderedStyle( this, useCache );
49 }
50
51 return this;
52 },
53
54 dirtyStyleCache: function(){
55 let cy = this.cy();
56 let dirty = ele => ele._private.styleCache = null;
57
58 if( cy.hasCompoundNodes() ){
59 let eles;
60
61 eles = this.spawnSelf()
62 .merge( this.descendants() )
63 .merge( this.parents() )
64 ;
65
66 eles.merge( eles.connectedEdges() );
67
68 eles.forEach( dirty );
69 } else {
70 this.forEach( ele => {
71 dirty( ele );
72
73 ele.connectedEdges().forEach( dirty );
74 } );
75 }
76
77 return this;
78 },
79
80 // fully updates (recalculates) the style for the elements
81 updateStyle: function( notifyRenderer ){
82 let cy = this._private.cy;
83
84 if( !cy.styleEnabled() ){ return this; }
85
86 if( cy.batching() ){
87 let bEles = cy._private.batchStyleEles;
88
89 bEles.merge( this );
90
91 return this; // chaining and exit early when batching
92 }
93
94 let hasCompounds = cy.hasCompoundNodes();
95 let updatedEles = this;
96
97 notifyRenderer = notifyRenderer || notifyRenderer === undefined ? true : false;
98
99 if( hasCompounds ){ // then add everything up and down for compound selector checks
100 updatedEles = this.spawnSelf().merge( this.descendants() ).merge( this.parents() );
101 }
102
103 // let changedEles = style.apply( updatedEles );
104 let changedEles = updatedEles;
105
106 if( notifyRenderer ){
107 changedEles.emitAndNotify( 'style' ); // let renderer know we changed style
108 } else {
109 changedEles.emit( 'style' ); // just fire the event
110 }
111
112 updatedEles.forEach(ele => ele._private.styleDirty = true);
113
114 return this; // chaining
115 },
116
117 // private: clears dirty flag and recalculates style
118 cleanStyle: function(){
119 let cy = this.cy();
120
121 if( !cy.styleEnabled() ){ return; }
122
123 for( let i = 0; i < this.length; i++ ){
124 let ele = this[i];
125
126 if( ele._private.styleDirty ){
127 // n.b. this flag should be set before apply() to avoid potential infinite recursion
128 ele._private.styleDirty = false;
129
130 cy.style().apply(ele);
131 }
132 }
133 },
134
135 // get the internal parsed style object for the specified property
136 parsedStyle: function( property, includeNonDefault = true ){
137 let ele = this[0];
138 let cy = ele.cy();
139
140 if( !cy.styleEnabled() ){ return; }
141
142 if( ele ){
143 this.cleanStyle();
144
145 let overriddenStyle = ele._private.style[ property ];
146
147 if( overriddenStyle != null ){
148 return overriddenStyle;
149 } else if( includeNonDefault ){
150 return cy.style().getDefaultProperty( property );
151 } else {
152 return null;
153 }
154 }
155 },
156
157 numericStyle: function( property ){
158 let ele = this[0];
159
160 if( !ele.cy().styleEnabled() ){ return; }
161
162 if( ele ){
163 let pstyle = ele.pstyle( property );
164
165 return pstyle.pfValue !== undefined ? pstyle.pfValue : pstyle.value;
166 }
167 },
168
169 numericStyleUnits: function( property ){
170 let ele = this[0];
171
172 if( !ele.cy().styleEnabled() ){ return; }
173
174 if( ele ){
175 return ele.pstyle( property ).units;
176 }
177 },
178
179 // get the specified css property as a rendered value (i.e. on-screen value)
180 // or get the whole rendered style if no property specified (NB doesn't allow setting)
181 renderedStyle: function( property ){
182 let cy = this.cy();
183 if( !cy.styleEnabled() ){ return this; }
184
185 let ele = this[0];
186
187 if( ele ){
188 return cy.style().getRenderedStyle( ele, property );
189 }
190 },
191
192 // read the calculated css style of the element or override the style (via a bypass)
193 style: function( name, value ){
194 let cy = this.cy();
195
196 if( !cy.styleEnabled() ){ return this; }
197
198 let updateTransitions = false;
199 let style = cy.style();
200
201 if( is.plainObject( name ) ){ // then extend the bypass
202 let props = name;
203 style.applyBypass( this, props, updateTransitions );
204
205 this.emitAndNotify( 'style' ); // let the renderer know we've updated style
206
207 } else if( is.string( name ) ){
208
209 if( value === undefined ){ // then get the property from the style
210 let ele = this[0];
211
212 if( ele ){
213 return style.getStylePropertyValue( ele, name );
214 } else { // empty collection => can't get any value
215 return;
216 }
217
218 } else { // then set the bypass with the property value
219 style.applyBypass( this, name, value, updateTransitions );
220
221 this.emitAndNotify( 'style' ); // let the renderer know we've updated style
222 }
223
224 } else if( name === undefined ){
225 let ele = this[0];
226
227 if( ele ){
228 return style.getRawStyle( ele );
229 } else { // empty collection => can't get any value
230 return;
231 }
232 }
233
234 return this; // chaining
235 },
236
237 removeStyle: function( names ){
238 let cy = this.cy();
239
240 if( !cy.styleEnabled() ){ return this; }
241
242 let updateTransitions = false;
243 let style = cy.style();
244 let eles = this;
245
246 if( names === undefined ){
247 for( let i = 0; i < eles.length; i++ ){
248 let ele = eles[ i ];
249
250 style.removeAllBypasses( ele, updateTransitions );
251 }
252 } else {
253 names = names.split( /\s+/ );
254
255 for( let i = 0; i < eles.length; i++ ){
256 let ele = eles[ i ];
257
258 style.removeBypasses( ele, names, updateTransitions );
259 }
260 }
261
262 this.emitAndNotify( 'style' ); // let the renderer know we've updated style
263
264 return this; // chaining
265 },
266
267 show: function(){
268 this.css( 'display', 'element' );
269 return this; // chaining
270 },
271
272 hide: function(){
273 this.css( 'display', 'none' );
274 return this; // chaining
275 },
276
277 effectiveOpacity: function(){
278 let cy = this.cy();
279 if( !cy.styleEnabled() ){ return 1; }
280
281 let hasCompoundNodes = cy.hasCompoundNodes();
282 let ele = this[0];
283
284 if( ele ){
285 let _p = ele._private;
286 let parentOpacity = ele.pstyle( 'opacity' ).value;
287
288 if( !hasCompoundNodes ){ return parentOpacity; }
289
290 let parents = !_p.data.parent ? null : ele.parents();
291
292 if( parents ){
293 for( let i = 0; i < parents.length; i++ ){
294 let parent = parents[ i ];
295 let opacity = parent.pstyle( 'opacity' ).value;
296
297 parentOpacity = opacity * parentOpacity;
298 }
299 }
300
301 return parentOpacity;
302 }
303 },
304
305 transparent: function(){
306 let cy = this.cy();
307 if( !cy.styleEnabled() ){ return false; }
308
309 let ele = this[0];
310 let hasCompoundNodes = ele.cy().hasCompoundNodes();
311
312 if( ele ){
313 if( !hasCompoundNodes ){
314 return ele.pstyle( 'opacity' ).value === 0;
315 } else {
316 return ele.effectiveOpacity() === 0;
317 }
318 }
319 },
320
321 backgrounding: function(){
322 let cy = this.cy();
323 if( !cy.styleEnabled() ){ return false; }
324
325 let ele = this[0];
326
327 return ele._private.backgrounding ? true : false;
328 }
329
330});
331
332function checkCompound( ele, parentOk ){
333 let _p = ele._private;
334 let parents = _p.data.parent ? ele.parents() : null;
335
336 if( parents ){ for( let i = 0; i < parents.length; i++ ){
337 let parent = parents[ i ];
338
339 if( !parentOk( parent ) ){ return false; }
340 } }
341
342 return true;
343}
344
345function defineDerivedStateFunction( specs ){
346 let ok = specs.ok;
347 let edgeOkViaNode = specs.edgeOkViaNode || specs.ok;
348 let parentOk = specs.parentOk || specs.ok;
349
350 return function(){
351 let cy = this.cy();
352 if( !cy.styleEnabled() ){ return true; }
353
354 let ele = this[0];
355 let hasCompoundNodes = cy.hasCompoundNodes();
356
357 if( ele ){
358 let _p = ele._private;
359
360 if( !ok( ele ) ){ return false; }
361
362 if( ele.isNode() ){
363 return !hasCompoundNodes || checkCompound( ele, parentOk );
364 } else {
365 let src = _p.source;
366 let tgt = _p.target;
367
368 return ( edgeOkViaNode(src) && (!hasCompoundNodes || checkCompound(src, edgeOkViaNode)) ) &&
369 ( src === tgt || ( edgeOkViaNode(tgt) && (!hasCompoundNodes || checkCompound(tgt, edgeOkViaNode)) ) );
370 }
371 }
372 };
373}
374
375let eleTakesUpSpace = cacheStyleFunction( 'eleTakesUpSpace', function( ele ){
376 return (
377 ele.pstyle( 'display' ).value === 'element'
378 && ele.width() !== 0
379 && ( ele.isNode() ? ele.height() !== 0 : true )
380 );
381} );
382
383elesfn.takesUpSpace = cachePrototypeStyleFunction( 'takesUpSpace', defineDerivedStateFunction({
384 ok: eleTakesUpSpace
385}) );
386
387let eleInteractive = cacheStyleFunction( 'eleInteractive', function( ele ){
388 return (
389 ele.pstyle('events').value === 'yes'
390 && ele.pstyle('visibility').value === 'visible'
391 && eleTakesUpSpace( ele )
392 );
393} );
394
395let parentInteractive = cacheStyleFunction( 'parentInteractive', function( parent ){
396 return (
397 parent.pstyle('visibility').value === 'visible'
398 && eleTakesUpSpace( parent )
399 );
400} );
401
402elesfn.interactive = cachePrototypeStyleFunction( 'interactive', defineDerivedStateFunction({
403 ok: eleInteractive,
404 parentOk: parentInteractive,
405 edgeOkViaNode: eleTakesUpSpace
406}) );
407
408elesfn.noninteractive = function(){
409 let ele = this[0];
410
411 if( ele ){
412 return !ele.interactive();
413 }
414};
415
416let eleVisible = cacheStyleFunction( 'eleVisible', function( ele ){
417 return (
418 ele.pstyle( 'visibility' ).value === 'visible'
419 && ele.pstyle( 'opacity' ).pfValue !== 0
420 && eleTakesUpSpace( ele )
421 );
422} );
423
424let edgeVisibleViaNode = eleTakesUpSpace;
425
426elesfn.visible = cachePrototypeStyleFunction( 'visible', defineDerivedStateFunction({
427 ok: eleVisible,
428 edgeOkViaNode: edgeVisibleViaNode
429}) );
430
431elesfn.hidden = function(){
432 let ele = this[0];
433
434 if( ele ){
435 return !ele.visible();
436 }
437};
438
439elesfn.isBundledBezier = cachePrototypeStyleFunction('isBundledBezier', function(){
440 if( !this.cy().styleEnabled() ){ return false; }
441
442 return !this.removed() && this.pstyle('curve-style').value === 'bezier' && this.takesUpSpace();
443});
444
445elesfn.bypass = elesfn.css = elesfn.style;
446elesfn.renderedCss = elesfn.renderedStyle;
447elesfn.removeBypass = elesfn.removeCss = elesfn.removeStyle;
448elesfn.pstyle = elesfn.parsedStyle;
449
450export default elesfn;