|
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.0b1 |
|
6 build: 1163 |
|
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 TO_STRING = 'toString', |
|
164 PARSE_INT = parseInt, |
|
165 RE = RegExp; |
|
166 |
|
167 Y.Color = { |
|
168 KEYWORDS: { |
|
169 black: '000', |
|
170 silver: 'c0c0c0', |
|
171 gray: '808080', |
|
172 white: 'fff', |
|
173 maroon: '800000', |
|
174 red: 'f00', |
|
175 purple: '800080', |
|
176 fuchsia: 'f0f', |
|
177 green: '008000', |
|
178 lime: '0f0', |
|
179 olive: '808000', |
|
180 yellow: 'ff0', |
|
181 navy: '000080', |
|
182 blue: '00f', |
|
183 teal: '008080', |
|
184 aqua: '0ff' |
|
185 }, |
|
186 |
|
187 re_RGB: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i, |
|
188 re_hex: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i, |
|
189 re_hex3: /([0-9A-F])/gi, |
|
190 |
|
191 toRGB: function(val) { |
|
192 if (!Y.Color.re_RGB.test(val)) { |
|
193 val = Y.Color.toHex(val); |
|
194 } |
|
195 |
|
196 if(Y.Color.re_hex.exec(val)) { |
|
197 val = 'rgb(' + [ |
|
198 PARSE_INT(RE.$1, 16), |
|
199 PARSE_INT(RE.$2, 16), |
|
200 PARSE_INT(RE.$3, 16) |
|
201 ].join(', ') + ')'; |
|
202 } |
|
203 return val; |
|
204 }, |
|
205 |
|
206 toHex: function(val) { |
|
207 val = Y.Color.KEYWORDS[val] || val; |
|
208 if (Y.Color.re_RGB.exec(val)) { |
|
209 var r = (RE.$1.length === 1) ? '0' + RE.$1 : Number(RE.$1), |
|
210 g = (RE.$2.length === 1) ? '0' + RE.$2 : Number(RE.$2), |
|
211 b = (RE.$3.length === 1) ? '0' + RE.$3 : Number(RE.$3); |
|
212 |
|
213 val = [ |
|
214 r[TO_STRING](16), |
|
215 g[TO_STRING](16), |
|
216 b[TO_STRING](16) |
|
217 ].join(''); |
|
218 } |
|
219 |
|
220 if (val.length < 6) { |
|
221 val = val.replace(Y.Color.re_hex3, '$1$1'); |
|
222 } |
|
223 |
|
224 if (val !== 'transparent' && val.indexOf('#') < 0) { |
|
225 val = '#' + val; |
|
226 } |
|
227 |
|
228 return val.toLowerCase(); |
|
229 } |
|
230 }; |
|
231 })(Y); |
|
232 |
|
233 (function(Y) { |
|
234 var CLIENT_TOP = 'clientTop', |
|
235 CLIENT_LEFT = 'clientLeft', |
|
236 HAS_LAYOUT = 'hasLayout', |
|
237 PX = 'px', |
|
238 FILTER = 'filter', |
|
239 FILTERS = 'filters', |
|
240 OPACITY = 'opacity', |
|
241 AUTO = 'auto', |
|
242 BORDER_TOP_WIDTH = 'borderTopWidth', |
|
243 BORDER_RIGHT_WIDTH = 'borderRightWidth', |
|
244 BORDER_BOTTOM_WIDTH = 'borderBottomWidth', |
|
245 BORDER_LEFT_WIDTH = 'borderLeftWidth', |
|
246 WIDTH = 'width', |
|
247 HEIGHT = 'height', |
|
248 TRANSPARENT = 'transparent', |
|
249 VISIBLE = 'visible', |
|
250 GET_COMPUTED_STYLE = 'getComputedStyle', |
|
251 UNDEFINED = undefined, |
|
252 documentElement = document.documentElement, |
|
253 |
|
254 // TODO: unit-less lineHeight (e.g. 1.22) |
|
255 re_size = /^width|height$/, |
|
256 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, |
|
257 |
|
258 _getStyleObj = function(node) { |
|
259 return node.currentStyle || node.style; |
|
260 }, |
|
261 |
|
262 ComputedStyle = { |
|
263 CUSTOM_STYLES: {}, |
|
264 |
|
265 get: function(el, property) { |
|
266 var value = '', |
|
267 current; |
|
268 |
|
269 if (el) { |
|
270 current = _getStyleObj(el)[property]; |
|
271 |
|
272 if (property === OPACITY) { |
|
273 value = Y.DOM.CUSTOM_STYLES[OPACITY].get(el); |
|
274 } else if (!current || (current.indexOf && current.indexOf(PX) > -1)) { // no need to convert |
|
275 value = current; |
|
276 } else if (Y.DOM.IE.COMPUTED[property]) { // use compute function |
|
277 value = Y.DOM.IE.COMPUTED[property](el, property); |
|
278 } else if (re_unit.test(current)) { // convert to pixel |
|
279 value = ComputedStyle.getPixel(el, property) + PX; |
|
280 } else { |
|
281 value = current; |
|
282 } |
|
283 } |
|
284 |
|
285 return value; |
|
286 }, |
|
287 |
|
288 getOffset: function(el, prop) { |
|
289 var current = _getStyleObj(el)[prop], // value of "width", "top", etc. |
|
290 capped = prop.charAt(0).toUpperCase() + prop.substr(1), // "Width", "Top", etc. |
|
291 offset = 'offset' + capped, // "offsetWidth", "offsetTop", etc. |
|
292 pixel = 'pixel' + capped, // "pixelWidth", "pixelTop", etc. |
|
293 actual, |
|
294 value = ''; |
|
295 |
|
296 if (current === AUTO) { |
|
297 actual = el[offset]; // offsetHeight/Top etc. |
|
298 if (actual === UNDEFINED) { // likely "right" or "bottom" |
|
299 value = 0; |
|
300 } |
|
301 |
|
302 value = actual; |
|
303 if (re_size.test(prop)) { // account for box model diff |
|
304 el.style[prop] = actual; |
|
305 if (el[offset] > actual) { |
|
306 // the difference is padding + border (works in Standards & Quirks modes) |
|
307 value = actual - (el[offset] - actual); |
|
308 } |
|
309 el.style[prop] = AUTO; // revert to auto |
|
310 } |
|
311 } else { // convert units to px |
|
312 if (current.indexOf('%') > -1) { // IE pixelWidth incorrect for percent; manually compute |
|
313 current = el.clientWidth - // offsetWidth - borderWidth |
|
314 ComputedStyle.getPixel(el, 'paddingRight') - |
|
315 ComputedStyle.getPixel(el, 'paddingLeft'); |
|
316 } |
|
317 if (!el.style[pixel] && !el.style[prop]) { // need to map style.width to currentStyle (no currentStyle.pixelWidth) |
|
318 el.style[prop] = current; // no style.pixelWidth if no style.width |
|
319 } |
|
320 value = el.style[pixel]; |
|
321 } |
|
322 return value + PX; |
|
323 }, |
|
324 |
|
325 getBorderWidth: function(el, property) { |
|
326 // clientHeight/Width = paddingBox (e.g. offsetWidth - borderWidth) |
|
327 // clientTop/Left = borderWidth |
|
328 var value = null; |
|
329 if (!el.currentStyle || !el.currentStyle[HAS_LAYOUT]) { // TODO: unset layout? |
|
330 el.style.zoom = 1; // need layout to measure client |
|
331 } |
|
332 |
|
333 switch(property) { |
|
334 case BORDER_TOP_WIDTH: |
|
335 value = el[CLIENT_TOP]; |
|
336 break; |
|
337 case BORDER_BOTTOM_WIDTH: |
|
338 value = el.offsetHeight - el.clientHeight - el[CLIENT_TOP]; |
|
339 break; |
|
340 case BORDER_LEFT_WIDTH: |
|
341 value = el[CLIENT_LEFT]; |
|
342 break; |
|
343 case BORDER_RIGHT_WIDTH: |
|
344 value = el.offsetWidth - el.clientWidth - el[CLIENT_LEFT]; |
|
345 break; |
|
346 } |
|
347 return value + PX; |
|
348 }, |
|
349 |
|
350 getPixel: function(node, att) { |
|
351 // use pixelRight to convert to px |
|
352 var val = null, |
|
353 style = _getStyleObj(node), |
|
354 styleRight = style.right, |
|
355 current = style[att]; |
|
356 |
|
357 node.style.right = current; |
|
358 val = node.style.pixelRight; |
|
359 node.style.right = styleRight; // revert |
|
360 |
|
361 return val; |
|
362 }, |
|
363 |
|
364 getMargin: function(node, att) { |
|
365 var val, |
|
366 style = _getStyleObj(node); |
|
367 |
|
368 if (style[att] == AUTO) { |
|
369 val = 0; |
|
370 } else { |
|
371 val = ComputedStyle.getPixel(node, att); |
|
372 } |
|
373 return val + PX; |
|
374 }, |
|
375 |
|
376 getVisibility: function(node, att) { |
|
377 var current; |
|
378 while ( (current = node.currentStyle) && current[att] == 'inherit') { // NOTE: assignment in test |
|
379 node = node.parentNode; |
|
380 } |
|
381 return (current) ? current[att] : VISIBLE; |
|
382 }, |
|
383 |
|
384 getColor: function(node, att) { |
|
385 var current = _getStyleObj(node)[att]; |
|
386 |
|
387 if (!current || current === TRANSPARENT) { |
|
388 Y.DOM.elementByAxis(node, 'parentNode', null, function(parent) { |
|
389 current = _getStyleObj(parent)[att]; |
|
390 if (current && current !== TRANSPARENT) { |
|
391 node = parent; |
|
392 return true; |
|
393 } |
|
394 }); |
|
395 } |
|
396 |
|
397 return Y.Color.toRGB(current); |
|
398 }, |
|
399 |
|
400 getBorderColor: function(node, att) { |
|
401 var current = _getStyleObj(node), |
|
402 val = current[att] || current.color; |
|
403 return Y.Color.toRGB(Y.Color.toHex(val)); |
|
404 } |
|
405 }, |
|
406 |
|
407 //fontSize: getPixelFont, |
|
408 IEComputed = {}; |
|
409 |
|
410 // use alpha filter for IE opacity |
|
411 if (documentElement.style[OPACITY] === UNDEFINED && |
|
412 documentElement[FILTERS]) { |
|
413 Y.DOM.CUSTOM_STYLES[OPACITY] = { |
|
414 get: function(node) { |
|
415 var val = 100; |
|
416 try { // will error if no DXImageTransform |
|
417 val = node[FILTERS]['DXImageTransform.Microsoft.Alpha'][OPACITY]; |
|
418 |
|
419 } catch(e) { |
|
420 try { // make sure its in the document |
|
421 val = node[FILTERS]('alpha')[OPACITY]; |
|
422 } catch(err) { |
|
423 Y.log('getStyle: IE opacity filter not found; returning 1', 'warn', 'dom-style'); |
|
424 } |
|
425 } |
|
426 return val / 100; |
|
427 }, |
|
428 |
|
429 set: function(node, val, style) { |
|
430 var current, |
|
431 styleObj; |
|
432 |
|
433 if (val === '') { // normalize inline style behavior |
|
434 styleObj = _getStyleObj(node); |
|
435 current = (OPACITY in styleObj) ? styleObj[OPACITY] : 1; // revert to original opacity |
|
436 val = current; |
|
437 } |
|
438 |
|
439 if (typeof style[FILTER] == 'string') { // in case not appended |
|
440 style[FILTER] = 'alpha(' + OPACITY + '=' + val * 100 + ')'; |
|
441 |
|
442 if (!node.currentStyle || !node.currentStyle[HAS_LAYOUT]) { |
|
443 style.zoom = 1; // needs layout |
|
444 } |
|
445 } |
|
446 } |
|
447 }; |
|
448 } |
|
449 |
|
450 try { |
|
451 document.createElement('div').style.height = '-1px'; |
|
452 } catch(e) { // IE throws error on invalid style set; trap common cases |
|
453 Y.DOM.CUSTOM_STYLES.height = { |
|
454 set: function(node, val, style) { |
|
455 if (parseInt(val, 10) >= 0) { |
|
456 style.height = val; |
|
457 } else { |
|
458 Y.log('invalid style value for height: ' + val, 'warn', 'dom-style'); |
|
459 } |
|
460 } |
|
461 }; |
|
462 |
|
463 Y.DOM.CUSTOM_STYLES.width = { |
|
464 set: function(node, val, style) { |
|
465 if (parseInt(val, 10) >= 0) { |
|
466 style.width = val; |
|
467 } else { |
|
468 Y.log('invalid style value for width: ' + val, 'warn', 'dom-style'); |
|
469 } |
|
470 } |
|
471 }; |
|
472 } |
|
473 |
|
474 // TODO: top, right, bottom, left |
|
475 IEComputed[WIDTH] = IEComputed[HEIGHT] = ComputedStyle.getOffset; |
|
476 |
|
477 IEComputed.color = IEComputed.backgroundColor = ComputedStyle.getColor; |
|
478 |
|
479 IEComputed[BORDER_TOP_WIDTH] = IEComputed[BORDER_RIGHT_WIDTH] = |
|
480 IEComputed[BORDER_BOTTOM_WIDTH] = IEComputed[BORDER_LEFT_WIDTH] = |
|
481 ComputedStyle.getBorderWidth; |
|
482 |
|
483 IEComputed.marginTop = IEComputed.marginRight = IEComputed.marginBottom = |
|
484 IEComputed.marginLeft = ComputedStyle.getMargin; |
|
485 |
|
486 IEComputed.visibility = ComputedStyle.getVisibility; |
|
487 IEComputed.borderColor = IEComputed.borderTopColor = |
|
488 IEComputed.borderRightColor = IEComputed.borderBottomColor = |
|
489 IEComputed.borderLeftColor = ComputedStyle.getBorderColor; |
|
490 |
|
491 if (!Y.config.win[GET_COMPUTED_STYLE]) { |
|
492 Y.DOM[GET_COMPUTED_STYLE] = ComputedStyle.get; |
|
493 } |
|
494 |
|
495 Y.namespace('DOM.IE'); |
|
496 Y.DOM.IE.COMPUTED = IEComputed; |
|
497 Y.DOM.IE.ComputedStyle = ComputedStyle; |
|
498 |
|
499 })(Y); |
|
500 |
|
501 |
|
502 }, '3.0.0b1' ,{skinnable:false, requires:['dom-base']}); |