|
1 /* |
|
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved. |
|
3 Code licensed under the BSD License: |
|
4 http://developer.yahoo.net/yui/license.txt |
|
5 version: 3.0.0 |
|
6 build: 1549 |
|
7 */ |
|
8 YUI.add('dom-style', function(Y) { |
|
9 |
|
10 (function(Y) { |
|
11 /** |
|
12 * Add style management functionality to DOM. |
|
13 * @module dom |
|
14 * @submodule dom-style |
|
15 * @for DOM |
|
16 */ |
|
17 |
|
18 var DOCUMENT_ELEMENT = 'documentElement', |
|
19 DEFAULT_VIEW = 'defaultView', |
|
20 OWNER_DOCUMENT = 'ownerDocument', |
|
21 STYLE = 'style', |
|
22 FLOAT = 'float', |
|
23 CSS_FLOAT = 'cssFloat', |
|
24 STYLE_FLOAT = 'styleFloat', |
|
25 TRANSPARENT = 'transparent', |
|
26 GET_COMPUTED_STYLE = 'getComputedStyle', |
|
27 |
|
28 DOCUMENT = Y.config.doc, |
|
29 UNDEFINED = undefined, |
|
30 |
|
31 re_color = /color$/i; |
|
32 |
|
33 |
|
34 Y.mix(Y.DOM, { |
|
35 CUSTOM_STYLES: { |
|
36 }, |
|
37 |
|
38 |
|
39 /** |
|
40 * Sets a style property for a given element. |
|
41 * @method setStyle |
|
42 * @param {HTMLElement} An HTMLElement to apply the style to. |
|
43 * @param {String} att The style property to set. |
|
44 * @param {String|Number} val The value. |
|
45 */ |
|
46 setStyle: function(node, att, val, style) { |
|
47 style = style || node.style; |
|
48 var CUSTOM_STYLES = Y.DOM.CUSTOM_STYLES; |
|
49 |
|
50 if (style) { |
|
51 if (val === null) { |
|
52 val = ''; // normalize for unsetting |
|
53 } |
|
54 if (att in CUSTOM_STYLES) { |
|
55 if (CUSTOM_STYLES[att].set) { |
|
56 CUSTOM_STYLES[att].set(node, val, style); |
|
57 return; // NOTE: return |
|
58 } else if (typeof CUSTOM_STYLES[att] === 'string') { |
|
59 att = CUSTOM_STYLES[att]; |
|
60 } |
|
61 } |
|
62 style[att] = val; |
|
63 } |
|
64 }, |
|
65 |
|
66 /** |
|
67 * Returns the current style value for the given property. |
|
68 * @method getStyle |
|
69 * @param {HTMLElement} An HTMLElement to get the style from. |
|
70 * @param {String} att The style property to get. |
|
71 */ |
|
72 getStyle: function(node, att) { |
|
73 var style = node[STYLE], |
|
74 CUSTOM_STYLES = Y.DOM.CUSTOM_STYLES, |
|
75 val = ''; |
|
76 |
|
77 if (style) { |
|
78 if (att in CUSTOM_STYLES) { |
|
79 if (CUSTOM_STYLES[att].get) { |
|
80 return CUSTOM_STYLES[att].get(node, att, style); // NOTE: return |
|
81 } else if (typeof CUSTOM_STYLES[att] === 'string') { |
|
82 att = CUSTOM_STYLES[att]; |
|
83 } |
|
84 } |
|
85 val = style[att]; |
|
86 if (val === '') { // TODO: is empty string sufficient? |
|
87 val = Y.DOM[GET_COMPUTED_STYLE](node, att); |
|
88 } |
|
89 } |
|
90 |
|
91 return val; |
|
92 }, |
|
93 |
|
94 /** |
|
95 * Sets multiple style properties. |
|
96 * @method setStyles |
|
97 * @param {HTMLElement} node An HTMLElement to apply the styles to. |
|
98 * @param {Object} hash An object literal of property:value pairs. |
|
99 */ |
|
100 setStyles: function(node, hash) { |
|
101 var style = node.style; |
|
102 Y.each(hash, function(v, n) { |
|
103 Y.DOM.setStyle(node, n, v, style); |
|
104 }, Y.DOM); |
|
105 }, |
|
106 |
|
107 /** |
|
108 * Returns the computed style for the given node. |
|
109 * @method getComputedStyle |
|
110 * @param {HTMLElement} An HTMLElement to get the style from. |
|
111 * @param {String} att The style property to get. |
|
112 * @return {String} The computed value of the style property. |
|
113 */ |
|
114 getComputedStyle: function(node, att) { |
|
115 var val = '', |
|
116 doc = node[OWNER_DOCUMENT]; |
|
117 |
|
118 if (node[STYLE]) { |
|
119 val = doc[DEFAULT_VIEW][GET_COMPUTED_STYLE](node, null)[att]; |
|
120 } |
|
121 return val; |
|
122 } |
|
123 }); |
|
124 |
|
125 // normalize reserved word float alternatives ("cssFloat" or "styleFloat") |
|
126 if (DOCUMENT[DOCUMENT_ELEMENT][STYLE][CSS_FLOAT] !== UNDEFINED) { |
|
127 Y.DOM.CUSTOM_STYLES[FLOAT] = CSS_FLOAT; |
|
128 } else if (DOCUMENT[DOCUMENT_ELEMENT][STYLE][STYLE_FLOAT] !== UNDEFINED) { |
|
129 Y.DOM.CUSTOM_STYLES[FLOAT] = STYLE_FLOAT; |
|
130 } |
|
131 |
|
132 // fix opera computedStyle default color unit (convert to rgb) |
|
133 if (Y.UA.opera) { |
|
134 Y.DOM[GET_COMPUTED_STYLE] = function(node, att) { |
|
135 var view = node[OWNER_DOCUMENT][DEFAULT_VIEW], |
|
136 val = view[GET_COMPUTED_STYLE](node, '')[att]; |
|
137 |
|
138 if (re_color.test(att)) { |
|
139 val = Y.Color.toRGB(val); |
|
140 } |
|
141 |
|
142 return val; |
|
143 }; |
|
144 |
|
145 } |
|
146 |
|
147 // safari converts transparent to rgba(), others use "transparent" |
|
148 if (Y.UA.webkit) { |
|
149 Y.DOM[GET_COMPUTED_STYLE] = function(node, att) { |
|
150 var view = node[OWNER_DOCUMENT][DEFAULT_VIEW], |
|
151 val = view[GET_COMPUTED_STYLE](node, '')[att]; |
|
152 |
|
153 if (val === 'rgba(0, 0, 0, 0)') { |
|
154 val = TRANSPARENT; |
|
155 } |
|
156 |
|
157 return val; |
|
158 }; |
|
159 |
|
160 } |
|
161 })(Y); |
|
162 (function(Y) { |
|
163 var PARSE_INT = parseInt, |
|
164 RE = RegExp; |
|
165 |
|
166 Y.Color = { |
|
167 KEYWORDS: { |
|
168 black: '000', |
|
169 silver: 'c0c0c0', |
|
170 gray: '808080', |
|
171 white: 'fff', |
|
172 maroon: '800000', |
|
173 red: 'f00', |
|
174 purple: '800080', |
|
175 fuchsia: 'f0f', |
|
176 green: '008000', |
|
177 lime: '0f0', |
|
178 olive: '808000', |
|
179 yellow: 'ff0', |
|
180 navy: '000080', |
|
181 blue: '00f', |
|
182 teal: '008080', |
|
183 aqua: '0ff' |
|
184 }, |
|
185 |
|
186 re_RGB: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i, |
|
187 re_hex: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i, |
|
188 re_hex3: /([0-9A-F])/gi, |
|
189 |
|
190 toRGB: function(val) { |
|
191 if (!Y.Color.re_RGB.test(val)) { |
|
192 val = Y.Color.toHex(val); |
|
193 } |
|
194 |
|
195 if(Y.Color.re_hex.exec(val)) { |
|
196 val = 'rgb(' + [ |
|
197 PARSE_INT(RE.$1, 16), |
|
198 PARSE_INT(RE.$2, 16), |
|
199 PARSE_INT(RE.$3, 16) |
|
200 ].join(', ') + ')'; |
|
201 } |
|
202 return val; |
|
203 }, |
|
204 |
|
205 toHex: function(val) { |
|
206 val = Y.Color.KEYWORDS[val] || val; |
|
207 if (Y.Color.re_RGB.exec(val)) { |
|
208 val = [ |
|
209 Number(RE.$1).toString(16), |
|
210 Number(RE.$2).toString(16), |
|
211 Number(RE.$3).toString(16) |
|
212 ]; |
|
213 |
|
214 for (var i = 0; i < val.length; i++) { |
|
215 if (val[i].length < 2) { |
|
216 val[i] = val[i].replace(Y.Color.re_hex3, '$1$1'); |
|
217 } |
|
218 } |
|
219 |
|
220 val = '#' + val.join(''); |
|
221 } |
|
222 |
|
223 if (val.length < 6) { |
|
224 val = val.replace(Y.Color.re_hex3, '$1$1'); |
|
225 } |
|
226 |
|
227 if (val !== 'transparent' && val.indexOf('#') < 0) { |
|
228 val = '#' + val; |
|
229 } |
|
230 |
|
231 return val.toLowerCase(); |
|
232 } |
|
233 }; |
|
234 })(Y); |
|
235 |
|
236 (function(Y) { |
|
237 var HAS_LAYOUT = 'hasLayout', |
|
238 PX = 'px', |
|
239 FILTER = 'filter', |
|
240 FILTERS = 'filters', |
|
241 OPACITY = 'opacity', |
|
242 AUTO = 'auto', |
|
243 |
|
244 BORDER_WIDTH = 'borderWidth', |
|
245 BORDER_TOP_WIDTH = 'borderTopWidth', |
|
246 BORDER_RIGHT_WIDTH = 'borderRightWidth', |
|
247 BORDER_BOTTOM_WIDTH = 'borderBottomWidth', |
|
248 BORDER_LEFT_WIDTH = 'borderLeftWidth', |
|
249 WIDTH = 'width', |
|
250 HEIGHT = 'height', |
|
251 TRANSPARENT = 'transparent', |
|
252 VISIBLE = 'visible', |
|
253 GET_COMPUTED_STYLE = 'getComputedStyle', |
|
254 UNDEFINED = undefined, |
|
255 documentElement = document.documentElement, |
|
256 |
|
257 // TODO: unit-less lineHeight (e.g. 1.22) |
|
258 re_unit = /^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz|%){1}?/i, |
|
259 |
|
260 _getStyleObj = function(node) { |
|
261 return node.currentStyle || node.style; |
|
262 }, |
|
263 |
|
264 ComputedStyle = { |
|
265 CUSTOM_STYLES: {}, |
|
266 |
|
267 get: function(el, property) { |
|
268 var value = '', |
|
269 current; |
|
270 |
|
271 if (el) { |
|
272 current = _getStyleObj(el)[property]; |
|
273 |
|
274 if (property === OPACITY && Y.DOM.CUSTOM_STYLES[OPACITY]) { |
|
275 value = Y.DOM.CUSTOM_STYLES[OPACITY].get(el); |
|
276 } else if (!current || (current.indexOf && current.indexOf(PX) > -1)) { // no need to convert |
|
277 value = current; |
|
278 } else if (Y.DOM.IE.COMPUTED[property]) { // use compute function |
|
279 value = Y.DOM.IE.COMPUTED[property](el, property); |
|
280 } else if (re_unit.test(current)) { // convert to pixel |
|
281 value = ComputedStyle.getPixel(el, property) + PX; |
|
282 } else { |
|
283 value = current; |
|
284 } |
|
285 } |
|
286 |
|
287 return value; |
|
288 }, |
|
289 |
|
290 sizeOffsets: { |
|
291 width: ['Left', 'Right'], |
|
292 height: ['Top', 'Bottom'], |
|
293 top: ['Top'], |
|
294 bottom: ['Bottom'] |
|
295 }, |
|
296 |
|
297 getOffset: function(el, prop) { |
|
298 var current = _getStyleObj(el)[prop], // value of "width", "top", etc. |
|
299 capped = prop.charAt(0).toUpperCase() + prop.substr(1), // "Width", "Top", etc. |
|
300 offset = 'offset' + capped, // "offsetWidth", "offsetTop", etc. |
|
301 pixel = 'pixel' + capped, // "pixelWidth", "pixelTop", etc. |
|
302 sizeOffsets = ComputedStyle.sizeOffsets[prop], |
|
303 value = ''; |
|
304 |
|
305 // IE pixelWidth incorrect for percent |
|
306 // manually compute by subtracting padding and border from offset size |
|
307 // NOTE: clientWidth/Height (size minus border) is 0 when current === AUTO so offsetHeight is used |
|
308 // reverting to auto from auto causes position stacking issues (old impl) |
|
309 if (current === AUTO || current.indexOf('%') > -1) { |
|
310 value = el['offset' + capped]; |
|
311 |
|
312 if (sizeOffsets[0]) { |
|
313 value -= ComputedStyle.getPixel(el, 'padding' + sizeOffsets[0]); |
|
314 value -= ComputedStyle.getBorderWidth(el, 'border' + sizeOffsets[0] + 'Width', 1); |
|
315 } |
|
316 |
|
317 if (sizeOffsets[1]) { |
|
318 value -= ComputedStyle.getPixel(el, 'padding' + sizeOffsets[1]); |
|
319 value -= ComputedStyle.getBorderWidth(el, 'border' + sizeOffsets[1] + 'Width', 1); |
|
320 } |
|
321 |
|
322 } else { // use style.pixelWidth, etc. to convert to pixels |
|
323 // need to map style.width to currentStyle (no currentStyle.pixelWidth) |
|
324 if (!el.style[pixel] && !el.style[prop]) { |
|
325 el.style[prop] = current; |
|
326 } |
|
327 value = el.style[pixel]; |
|
328 |
|
329 } |
|
330 return value + PX; |
|
331 }, |
|
332 |
|
333 borderMap: { |
|
334 thin: '2px', |
|
335 medium: '4px', |
|
336 thick: '6px' |
|
337 }, |
|
338 |
|
339 getBorderWidth: function(el, property, omitUnit) { |
|
340 var unit = omitUnit ? '' : PX, |
|
341 current = el.currentStyle[property]; |
|
342 |
|
343 if (current.indexOf(PX) < 0) { // look up keywords |
|
344 if (ComputedStyle.borderMap[current]) { |
|
345 current = ComputedStyle.borderMap[current]; |
|
346 } else { |
|
347 Y.log('borderWidth computing not implemented', 'warn', 'dom-ie-style'); |
|
348 } |
|
349 } |
|
350 return (omitUnit) ? parseFloat(current) : current; |
|
351 }, |
|
352 |
|
353 getPixel: function(node, att) { |
|
354 // use pixelRight to convert to px |
|
355 var val = null, |
|
356 style = _getStyleObj(node), |
|
357 styleRight = style.right, |
|
358 current = style[att]; |
|
359 |
|
360 node.style.right = current; |
|
361 val = node.style.pixelRight; |
|
362 node.style.right = styleRight; // revert |
|
363 |
|
364 return val; |
|
365 }, |
|
366 |
|
367 getMargin: function(node, att) { |
|
368 var val, |
|
369 style = _getStyleObj(node); |
|
370 |
|
371 if (style[att] == AUTO) { |
|
372 val = 0; |
|
373 } else { |
|
374 val = ComputedStyle.getPixel(node, att); |
|
375 } |
|
376 return val + PX; |
|
377 }, |
|
378 |
|
379 getVisibility: function(node, att) { |
|
380 var current; |
|
381 while ( (current = node.currentStyle) && current[att] == 'inherit') { // NOTE: assignment in test |
|
382 node = node.parentNode; |
|
383 } |
|
384 return (current) ? current[att] : VISIBLE; |
|
385 }, |
|
386 |
|
387 getColor: function(node, att) { |
|
388 var current = _getStyleObj(node)[att]; |
|
389 |
|
390 if (!current || current === TRANSPARENT) { |
|
391 Y.DOM.elementByAxis(node, 'parentNode', null, function(parent) { |
|
392 current = _getStyleObj(parent)[att]; |
|
393 if (current && current !== TRANSPARENT) { |
|
394 node = parent; |
|
395 return true; |
|
396 } |
|
397 }); |
|
398 } |
|
399 |
|
400 return Y.Color.toRGB(current); |
|
401 }, |
|
402 |
|
403 getBorderColor: function(node, att) { |
|
404 var current = _getStyleObj(node), |
|
405 val = current[att] || current.color; |
|
406 return Y.Color.toRGB(Y.Color.toHex(val)); |
|
407 } |
|
408 }, |
|
409 |
|
410 //fontSize: getPixelFont, |
|
411 IEComputed = {}; |
|
412 |
|
413 // use alpha filter for IE opacity |
|
414 try { |
|
415 if (documentElement.style[OPACITY] === UNDEFINED && |
|
416 documentElement[FILTERS]) { |
|
417 Y.DOM.CUSTOM_STYLES[OPACITY] = { |
|
418 get: function(node) { |
|
419 var val = 100; |
|
420 try { // will error if no DXImageTransform |
|
421 val = node[FILTERS]['DXImageTransform.Microsoft.Alpha'][OPACITY]; |
|
422 |
|
423 } catch(e) { |
|
424 try { // make sure its in the document |
|
425 val = node[FILTERS]('alpha')[OPACITY]; |
|
426 } catch(err) { |
|
427 Y.log('getStyle: IE opacity filter not found; returning 1', 'warn', 'dom-style'); |
|
428 } |
|
429 } |
|
430 return val / 100; |
|
431 }, |
|
432 |
|
433 set: function(node, val, style) { |
|
434 var current, |
|
435 styleObj; |
|
436 |
|
437 if (val === '') { // normalize inline style behavior |
|
438 styleObj = _getStyleObj(node); |
|
439 current = (OPACITY in styleObj) ? styleObj[OPACITY] : 1; // revert to original opacity |
|
440 val = current; |
|
441 } |
|
442 |
|
443 if (typeof style[FILTER] == 'string') { // in case not appended |
|
444 style[FILTER] = 'alpha(' + OPACITY + '=' + val * 100 + ')'; |
|
445 |
|
446 if (!node.currentStyle || !node.currentStyle[HAS_LAYOUT]) { |
|
447 style.zoom = 1; // needs layout |
|
448 } |
|
449 } |
|
450 } |
|
451 }; |
|
452 } |
|
453 } catch(e) { |
|
454 Y.log('document.documentElement.filters error (activeX disabled)', 'warn', 'dom-style'); |
|
455 } |
|
456 |
|
457 try { |
|
458 document.createElement('div').style.height = '-1px'; |
|
459 } catch(e) { // IE throws error on invalid style set; trap common cases |
|
460 Y.DOM.CUSTOM_STYLES.height = { |
|
461 set: function(node, val, style) { |
|
462 var floatVal = parseFloat(val); |
|
463 if (isNaN(floatVal) || floatVal >= 0) { |
|
464 style.height = val; |
|
465 } else { |
|
466 Y.log('invalid style value for height: ' + val, 'warn', 'dom-style'); |
|
467 } |
|
468 } |
|
469 }; |
|
470 |
|
471 Y.DOM.CUSTOM_STYLES.width = { |
|
472 set: function(node, val, style) { |
|
473 var floatVal = parseFloat(val); |
|
474 if (isNaN(floatVal) || floatVal >= 0) { |
|
475 style.width = val; |
|
476 } else { |
|
477 Y.log('invalid style value for width: ' + val, 'warn', 'dom-style'); |
|
478 } |
|
479 } |
|
480 }; |
|
481 } |
|
482 |
|
483 // TODO: top, right, bottom, left |
|
484 IEComputed[WIDTH] = IEComputed[HEIGHT] = ComputedStyle.getOffset; |
|
485 |
|
486 IEComputed.color = IEComputed.backgroundColor = ComputedStyle.getColor; |
|
487 |
|
488 IEComputed[BORDER_WIDTH] = IEComputed[BORDER_TOP_WIDTH] = IEComputed[BORDER_RIGHT_WIDTH] = |
|
489 IEComputed[BORDER_BOTTOM_WIDTH] = IEComputed[BORDER_LEFT_WIDTH] = |
|
490 ComputedStyle.getBorderWidth; |
|
491 |
|
492 IEComputed.marginTop = IEComputed.marginRight = IEComputed.marginBottom = |
|
493 IEComputed.marginLeft = ComputedStyle.getMargin; |
|
494 |
|
495 IEComputed.visibility = ComputedStyle.getVisibility; |
|
496 IEComputed.borderColor = IEComputed.borderTopColor = |
|
497 IEComputed.borderRightColor = IEComputed.borderBottomColor = |
|
498 IEComputed.borderLeftColor = ComputedStyle.getBorderColor; |
|
499 |
|
500 if (!Y.config.win[GET_COMPUTED_STYLE]) { |
|
501 Y.DOM[GET_COMPUTED_STYLE] = ComputedStyle.get; |
|
502 } |
|
503 |
|
504 Y.namespace('DOM.IE'); |
|
505 Y.DOM.IE.COMPUTED = IEComputed; |
|
506 Y.DOM.IE.ComputedStyle = ComputedStyle; |
|
507 |
|
508 })(Y); |
|
509 |
|
510 |
|
511 }, '3.0.0' ,{requires:['dom-base']}); |