red 4.1.0 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,374 @@
1
+ require 'browser'
2
+
3
+ `
4
+ Array.fromCollection = function(collection){
5
+ for (var i = 0, a = [], j = collection.length; i < j; i++){
6
+ a.push($E(collection[i]))
7
+ }
8
+ return a
9
+ };
10
+ // used in several places where element/selector comparison is used
11
+ // tells whether an element matches another element or selector
12
+ Element.prototype.match = function(selector){
13
+ if (!selector) return true;
14
+ var tagid = Selectors.Utils.parseTagAndID(selector);
15
+ var tag = tagid[0], id = tagid[1];
16
+ if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false;
17
+ var parsed = Selectors.Utils.parseSelector(selector);
18
+ return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true;
19
+ };
20
+
21
+ // Provides polymorphic access to getElementById to Elements as well as documents, both of which
22
+ // can be passed into Selectors.Utils.search as location for searching for subelements.
23
+ Element.prototype.getElementById = function(id, nocash){
24
+ var el = this.ownerDocument.getElementById(id);
25
+ if (!el) return null;
26
+ for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
27
+ if (!parent) return null;
28
+ }
29
+ return $E(el);
30
+ },
31
+
32
+ Element.Attributes = {
33
+ Props: {'html': 'innerHTML', 'class': 'className', 'for': 'htmlFor', 'text': (#{trident?}) ? 'innerText' : 'textContent'},
34
+ Bools: ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'],
35
+ Camels: ['value', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap']
36
+ };
37
+
38
+ Element.prototype.getProperty = function(attribute){
39
+ var EA = Element.Attributes, key = EA.Props[attribute];
40
+ var value = (key) ? this[key] : this.getAttribute(attribute, 2);
41
+ return (EA.Bools[attribute]) ? !!value : (key) ? value : value || null;
42
+ },
43
+
44
+ String.prototype.contains = function(string, separator){
45
+ return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
46
+ };
47
+
48
+ String.prototype.trim = function(){
49
+ return this.replace(/^\s+|\s+$/g, '');
50
+ };
51
+
52
+ var Selectors = {Cache: {nth: {}, parsed: {}}};
53
+
54
+ Selectors.RegExps = {
55
+ id: (/#([\\w-]+)/),
56
+ tag: (/^(\\w+|\\*)/),
57
+ quick: (/^(\\w+|\\*)$/),
58
+ splitter: (/\\s*([+>~\\s])\\s*([a-zA-Z#.*:\\[])/g),
59
+ combined: (/\\.([\\w-]+)|\\[(\\w+)(?:([!*^$~|]?=)(["']?)([^\\4]*?)\\4)?\\]|:([\\w-]+)(?:\\(["']?(.*?)?["']?\\)|$)/g)
60
+ };
61
+
62
+ Selectors.Utils = {
63
+ // added to replace $uid
64
+ // uses internal Red.id
65
+ object_uid: function(item){
66
+ return item.__id__||(item.__id__=Red.id++)
67
+ },
68
+
69
+ chk: function(item, uniques){
70
+ if (!uniques) return true;
71
+ var uid = Selectors.Utils.object_uid(item);
72
+ if (!uniques[uid]) return uniques[uid] = true;
73
+ return false;
74
+ },
75
+
76
+ parseNthArgument: function(argument){
77
+ if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument];
78
+ var parsed = argument.match(/^([+-]?\\d*)?([a-z]+)?([+-]?\\d*)?$/);
79
+ if (!parsed) return false;
80
+ var inta = parseInt(parsed[1]);
81
+ var a = (inta || inta === 0) ? inta : 1;
82
+ var special = parsed[2] || false;
83
+ var b = parseInt(parsed[3]) || 0;
84
+ if (a != 0){
85
+ b--;
86
+ while (b < 1) b += a;
87
+ while (b >= a) b -= a;
88
+ } else {
89
+ a = b;
90
+ special = 'index';
91
+ }
92
+ switch (special){
93
+ case 'n': parsed = {a: a, b: b, special: 'n'}; break;
94
+ case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break;
95
+ case 'even': parsed = {a: 2, b: 1, special: 'n'}; break;
96
+ case 'first': parsed = {a: 0, special: 'index'}; break;
97
+ case 'last': parsed = {special: 'last-child'}; break;
98
+ case 'only': parsed = {special: 'only-child'}; break;
99
+ default: parsed = {a: (a - 1), special: 'index'};
100
+ }
101
+
102
+ return Selectors.Cache.nth[argument] = parsed;
103
+ },
104
+
105
+ parseSelector: function(selector){
106
+ if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector];
107
+ var m, parsed = {classes: [], pseudos: [], attributes: []};
108
+ while ((m = Selectors.RegExps.combined.exec(selector))){
109
+ var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7];
110
+ if (cn){
111
+ parsed.classes.push(cn);
112
+ } else if (pn){
113
+ var parser = Selectors.Pseudo[pn];
114
+
115
+ if (parser) parsed.pseudos.push({parser: parser, argument: pa});
116
+ else parsed.attributes.push({name: pn, operator: '=', value: pa});
117
+ } else if (an){
118
+ parsed.attributes.push({name: an, operator: ao, value: av});
119
+ }
120
+ }
121
+ if (!parsed.classes.length) delete parsed.classes;
122
+ if (!parsed.attributes.length) delete parsed.attributes;
123
+ if (!parsed.pseudos.length) delete parsed.pseudos;
124
+ if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null;
125
+ return Selectors.Cache.parsed[selector] = parsed;
126
+ },
127
+
128
+ parseTagAndID: function(selector){
129
+ var tag = selector.match(Selectors.RegExps.tag);
130
+ var id = selector.match(Selectors.RegExps.id);
131
+ return [(tag) ? tag[1] : '*', (id) ? id[1] : false];
132
+ },
133
+
134
+ filter: function(item, parsed, local){
135
+ var i;
136
+ if (parsed.classes){
137
+ for (i = parsed.classes.length; i--; i){
138
+ var cn = parsed.classes[i];
139
+ if (!Selectors.Filters.byClass(item, cn)) return false;
140
+ }
141
+ }
142
+ if (parsed.attributes){
143
+ for (i = parsed.attributes.length; i--; i){
144
+ var att = parsed.attributes[i];
145
+ if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false;
146
+ }
147
+ }
148
+ if (parsed.pseudos){
149
+ for (i = parsed.pseudos.length; i--; i){
150
+ var psd = parsed.pseudos[i];
151
+ if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false;
152
+ }
153
+ }
154
+ return true;
155
+ },
156
+
157
+ getByTagAndID: function(ctx, tag, id){
158
+ if (id){
159
+ var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true);
160
+ return (item && Selectors.Filters.byTag(item, tag)) ? [item] : [];
161
+ } else {
162
+ return ctx.getElementsByTagName(tag);
163
+ }
164
+ },
165
+
166
+ search: function(self, expression, local){
167
+ var splitters = [];
168
+
169
+ var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){
170
+ splitters.push(m1);
171
+ return ':)' + m2;
172
+ }).split(':)');
173
+
174
+
175
+ var items, filtered, item;
176
+ for (var i = 0, l = selectors.length; i < l; i++){
177
+ var selector = selectors[i];
178
+
179
+ if (i == 0 && Selectors.RegExps.quick.test(selector)){
180
+ items = self.getElementsByTagName(selector);
181
+ continue;
182
+ }
183
+
184
+ var splitter = splitters[i - 1];
185
+
186
+ var tagid = Selectors.Utils.parseTagAndID(selector);
187
+ var tag = tagid[0], id = tagid[1];
188
+ if (i == 0){
189
+ items = Selectors.Utils.getByTagAndID(self, tag, id);
190
+ } else {
191
+ var uniques = {}, found = [];
192
+ for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques);
193
+ items = found;
194
+ }
195
+
196
+ var parsed = Selectors.Utils.parseSelector(selector);
197
+ if (parsed){
198
+ filtered = [];
199
+ for (var m = 0, n = items.length; m < n; m++){
200
+ item = items[m];
201
+ if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item);
202
+ }
203
+ items = filtered;
204
+ }
205
+
206
+ }
207
+ return items;
208
+
209
+ }
210
+
211
+ };
212
+
213
+ Selectors.Getters = {
214
+
215
+ ' ': function(found, self, tag, id, uniques){
216
+ var items = Selectors.Utils.getByTagAndID(self, tag, id);
217
+ for (var i = 0, l = items.length; i < l; i++){
218
+ var item = items[i];
219
+ if (Selectors.Utils.chk(item, uniques)) found.push(item);
220
+ }
221
+ return found;
222
+ },
223
+
224
+ '>': function(found, self, tag, id, uniques){
225
+ var children = Selectors.Utils.getByTagAndID(self, tag, id);
226
+ for (var i = 0, l = children.length; i < l; i++){
227
+ var child = children[i];
228
+ if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child);
229
+ }
230
+ return found;
231
+ },
232
+
233
+ '+': function(found, self, tag, id, uniques){
234
+ while ((self = self.nextSibling)){
235
+ if (self.nodeType == 1){
236
+ if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
237
+ break;
238
+ }
239
+ }
240
+ return found;
241
+ },
242
+
243
+ '~': function(found, self, tag, id, uniques){
244
+
245
+ while ((self = self.nextSibling)){
246
+ if (self.nodeType == 1){
247
+ if (!Selectors.Utils.chk(self, uniques)) break;
248
+ if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
249
+ }
250
+ }
251
+ return found;
252
+ }
253
+
254
+ };
255
+
256
+ Selectors.Filters = {
257
+
258
+ byTag: function(self, tag){
259
+ return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag));
260
+ },
261
+
262
+ byID: function(self, id){
263
+ return (!id || (self.id && self.id == id));
264
+ },
265
+
266
+ byClass: function(self, klass){
267
+ return (self.className && self.className.contains(klass, ' '));
268
+ },
269
+
270
+ byPseudo: function(self, parser, argument, local){
271
+ return parser.call(self, argument, local);
272
+ },
273
+
274
+ byAttribute: function(self, name, operator, value){
275
+ var result = Element.prototype.getProperty.call(self, name);
276
+ if (!result) return false;
277
+ if (!operator || value == undefined) return true;
278
+ switch (operator){
279
+ case '=': return (result == value);
280
+ case '*=': return (result.contains(value));
281
+ case '^=': return (result.substr(0, value.length) == value);
282
+ case '$=': return (result.substr(result.length - value.length) == value);
283
+ case '!=': return (result != value);
284
+ case '~=': return result.contains(value, ' ');
285
+ case '|=': return result.contains(value, '-');
286
+ }
287
+ return false;
288
+ }
289
+
290
+ };
291
+
292
+ Selectors.Pseudo = {
293
+
294
+ // w3c pseudo selectors
295
+
296
+ empty: function(){
297
+ return !(this.innerText || this.textContent || '').length;
298
+ },
299
+
300
+ not: function(selector){
301
+ return !Element.match(this, selector);
302
+ },
303
+
304
+ contains: function(text){
305
+ return (this.innerText || this.textContent || '').contains(text);
306
+ },
307
+
308
+ 'first-child': function(){
309
+ return Selectors.Pseudo.index.call(this, 0);
310
+ },
311
+
312
+ 'last-child': function(){
313
+ var element = this;
314
+ while ((element = element.nextSibling)){
315
+ if (element.nodeType == 1) return false;
316
+ }
317
+ return true;
318
+ },
319
+
320
+ 'only-child': function(){
321
+ var prev = this;
322
+ while ((prev = prev.previousSibling)){
323
+ if (prev.nodeType == 1) return false;
324
+ }
325
+ var next = this;
326
+ while ((next = next.nextSibling)){
327
+ if (next.nodeType == 1) return false;
328
+ }
329
+ return true;
330
+ },
331
+
332
+ 'nth-child': function(argument, local){
333
+ argument = (argument == undefined) ? 'n' : argument;
334
+ var parsed = Selectors.Utils.parseNthArgument(argument);
335
+ if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local);
336
+ var count = 0;
337
+ local.positions = local.positions || {};
338
+ var uid = Selectors.Utils.object_uid(this);
339
+ if (!local.positions[uid]){
340
+ var self = this;
341
+ while ((self = self.previousSibling)){
342
+ if (self.nodeType != 1) continue;
343
+ count ++;
344
+ var position = local.positions[Selectors.Utils.object_uid(self)];
345
+ if (position != undefined){
346
+ count = position + count;
347
+ break;
348
+ }
349
+ }
350
+ local.positions[uid] = count;
351
+ }
352
+ return (local.positions[uid] % parsed.a == parsed.b);
353
+ },
354
+
355
+ // custom pseudo selectors
356
+
357
+ index: function(index){
358
+ var element = this, count = 0;
359
+ while ((element = element.previousSibling)){
360
+ if (element.nodeType == 1 && ++count > index) return false;
361
+ }
362
+ return (count == index);
363
+ },
364
+
365
+ even: function(argument, local){
366
+ return Selectors.Pseudo['nth-child'].call(this, '2n+1', local);
367
+ },
368
+
369
+ odd: function(argument, local){
370
+ return Selectors.Pseudo['nth-child'].call(this, '2n', local);
371
+ }
372
+
373
+ };
374
+ `
@@ -0,0 +1,328 @@
1
+ require 'element'
2
+ require 'window'
3
+
4
+ # Classes mixing in <tt>Situated</tt> and its submodules gain the ability
5
+ # to provide locational and dimensional data about their visual DOM
6
+ # representation within the browser.
7
+ #
8
+ module Situated
9
+ `window.styleString=function(el,prop){if(el.currentStyle){return el.currentStyle[prop.replace(/[_-]\\D/g, function(match){return match.charAt(1).toUpperCase();})];};var computed=document.defaultView.getComputedStyle(el,null);return(computed?computed.getPropertyValue([prop.replace(/[A-Z]/g, function(match){return('-'+match.charAt(0).toLowerCase());})]):null);}`
10
+ module PositionAndSize
11
+
12
+ # call-seq:
13
+ # situated.height -> integer
14
+ # returns the height of the _situated_ in pixels as an integer.
15
+ # height is the amount of vertical space the content requires
16
+ # plus top padding, bottom padding, top border, and bottom border, if any
17
+ #
18
+ #
19
+ # For example,
20
+ # situated = Document['#situated']
21
+ # where _situated_ is an element whose content is 200px tall
22
+ # with a top padding of 20px, bottom padding of 10px
23
+ # and a top border of 1px
24
+ #
25
+ # situated.height #=> 231 (200 + 20 + 10 + 1)
26
+ #
27
+ def height
28
+ self.size[:y]
29
+ end
30
+
31
+ # call-seq:
32
+ # situated.width -> integer
33
+ # returns the width of the _situated_ in pixels as an integer
34
+ # width is the amount of horizontal space the content requires
35
+ # plus left padding, right padding, left border, and right border, if any
36
+ #
37
+ # For example,
38
+ # situated = Document['#situated']
39
+ # where _situated_ is an element whose content is 200px wide
40
+ # with a left padding of 20px, right padding of 10px
41
+ # and a left border of 1px
42
+ #
43
+ # situated.width #=> 231 (200 + 20 + 10 + 1)
44
+ #
45
+ def width
46
+ self.size[:x]
47
+ end
48
+
49
+ def scroll_top
50
+ self.scroll[:y]
51
+ end
52
+
53
+ def scroll_left
54
+ self.scroll[:x]
55
+ end
56
+
57
+ def scroll_height
58
+ self.scroll_size[:y]
59
+ end
60
+
61
+ def scroll_width
62
+ self.scroll_size[:x]
63
+ end
64
+
65
+ def top
66
+ self.position[:x]
67
+ end
68
+
69
+ def left
70
+ self.position[:y]
71
+ end
72
+ end
73
+
74
+ module Element
75
+ include PositionAndSize
76
+
77
+ def size
78
+ return self.window.size if self.is_body?
79
+ return {:x => `#{self}.__native__.offsetWidth`, :y => `#{self}.__native__.offsetHeight`}
80
+ end
81
+
82
+ # call-seq:
83
+ # situated.scroll -> hash
84
+ # returns a hash containing keys <tt>:x<tt> and <tt>:y<tt> representing the
85
+ # the distance that a _situated_ has been scrolled
86
+ # originating at the top left corner of the _situated_
87
+ #
88
+ # For example if a _situated_ has been scrolled 10px left and 5px down
89
+ # situated.scroll #=> {:x => 10, :y => 5}
90
+ def scroll
91
+ return self.window.scroll if self.is_body?
92
+ return {:x => `#{self}.__native__.scrollLeft`, :y => `#{self}.__native__.scrollTop`}
93
+ end
94
+
95
+ def scrolls
96
+ `var element = this.__native__, position = {x : 0, y : 0};
97
+ while (element && !c$Situated.c$Utilities.m$is_body_bool(element)){
98
+ position.x += element.scrollLeft;
99
+ position.y += element.scrollTop;
100
+ element = element.parentNode;
101
+ }`
102
+ return {:x => `position.x`, :y => `position.y`}
103
+ end
104
+
105
+ # call-seq:
106
+ # situated.scroll_size -> hash
107
+ # returns a hash containing keys <tt>:x<tt> and <tt>:y<tt> representing the
108
+ # the size that a _situated_ included potential scrollable area.
109
+ #
110
+ # For example if a _situated_ has been a visible width of 100px and visible height of 50px
111
+ # and 100 additional pixels of horizontal unseen content that can be scrolled to uncover
112
+ #
113
+ # situated.scroll_size #=> {:x => 200, :y => 50}
114
+ #
115
+ def scroll_size
116
+ return self.window.scroll_size if self.is_body?
117
+ return {:x => `#{self}.__native__.scrollWidth`, :y => `#{self}.__native__.scrollHeight`}
118
+ end
119
+
120
+ # call-seq:
121
+ # situated.scroll_to(x,y) -> situated
122
+ #
123
+ # Scrolls the _situated_ to the position referenced by the coordinates <tt>x</tt> and <tt>y</tt>
124
+ # which are pixel dimensions measured from the top left corner of hte _situated_
125
+ #
126
+ # Will scroll to the limit of one dimension if a cooridnate for that dimension
127
+ # is larger than the size element
128
+ #
129
+ # No scrolling in a direction will occur if scrolling in that direction would have no effect.
130
+ #
131
+ # Examples:
132
+ #
133
+ # Given a _situated_ that is 200px wide and 500px long and has
134
+ # only 150px of horizontal dimension visible at any time and
135
+ # only 200px of vertical dimension visible at any time
136
+ #
137
+ # situated.scroll_to(10,10)
138
+ # will scroll the visible area 10 pixels from the left and 10pixels from the top
139
+ #
140
+ # scroll.scroll_to(200,0) will scroll the _situated_ to the maximum left scrolling possible
141
+ # and return the _situated_ to the original height position.
142
+ #
143
+ def scroll_to(x,y)
144
+ if self.is_body?
145
+ self.window.scroll_to(x, y)
146
+ else
147
+ `this.__native__.scrollLeft = x`
148
+ `this.__native__.scrollTop = y`
149
+ end
150
+ return self
151
+ end
152
+
153
+ # call-seq:
154
+ # situated.offset_parent -> element
155
+ #
156
+ # returns the parent element that provides the visual offset from
157
+ # the top left corner of the viewport
158
+ #
159
+ # This is typically the closest statically position element or <tt>body</tt>
160
+ def offset_parent
161
+ element = self
162
+ return nil if element.is_body?
163
+
164
+ # All engines except trident have a native offsetParent property
165
+ `var e = #{element}.__native__.offsetParent`
166
+ return `$E(#{element}.__native__.offsetParent)` unless trident?
167
+
168
+ # For trident we walk the DOM until we have a static positioned element or reach the body
169
+ while ((element = `$E(#{element}.__native__.parentNode)`) && !element.is_body?) do
170
+ return element unless element.styles[:position] == 'static'
171
+ end
172
+
173
+ return nil
174
+ end
175
+
176
+ def offsets
177
+ `var native = this.__native__`
178
+ if trident?
179
+ `var bound = native.getBoundingClientRect()`
180
+ `var html = this.m$document.__native__.documentElement`
181
+ return {
182
+ :x => `bound.left + html.scrollLeft - html.clientLeft`,
183
+ :y => `bound.top + html.scrollTop - html.clientTop`
184
+ }
185
+ end
186
+
187
+ `var position = {x: 0, y: 0}`
188
+ return {:x => `position.x`, :y => `position.y`} if self.is_body?
189
+ `
190
+
191
+ element = native
192
+ while (element && !c$Situated.c$Utilities.m$is_body_bool(element)){
193
+ position.x += element.offsetLeft;
194
+ position.y += element.offsetTop;
195
+
196
+ if (#{gecko?}){
197
+ if (!c$Situated.c$Utilities.m$border_box(element)){
198
+ position.x += c$Situated.c$Utilities.m$left_border(element);
199
+ position.y += c$Situated.c$Utilities.m$top_border(element);
200
+ }
201
+ var parent = element.parentNode;
202
+ if (parent && window.styleString(parent, 'overflow') != 'visible'){
203
+ position.x += c$Situated.c$Utilities.m$left_border(parent);
204
+ position.y += c$Situated.c$Utilities.m$top_border(parent);
205
+ }
206
+ } else if (element != this && #{webkit?}){
207
+ position.x += c$Situated.c$Utilities.m$left_border(element);
208
+ position.y += c$Situated.c$Utilities.m$top_border(element);
209
+ }
210
+
211
+ element = element.offsetParent;
212
+ }
213
+
214
+ if (#{gecko?} && !c$Situated.c$Utilities.m$border_box(native)){
215
+ position.x -= c$Situated.c$Utilities.m$left_border(native);
216
+ position.y -= c$Situated.c$Utilities.m$top_border(native);
217
+ }
218
+ `
219
+ return {:x => `position.x`, :y => `position.y`}
220
+ end
221
+
222
+ def position_at(x,y)
223
+ native = `this.__native__`
224
+ h = {:left => x - Situated::Utilities.styleNumber(native, `'margin-left'`), :top => y - Situated::Utilities.styleNumber(native, `'margin-top'`), :position => 'absolute'}
225
+ self.set_styles(h)
226
+ end
227
+
228
+ def position(relative_to = nil)
229
+ # if (isBody(this)) return {x: 0, y: 0};
230
+ offset = self.offsets
231
+ scroll = self.scrolls
232
+ position = {:x => offset[:x] - scroll[:x], :y => offset[:y] - scroll[:y]}
233
+ relative_position = {:x => 0, :y => 0}
234
+ # relativePosition = (relative_to && (relative_to = $(relative_to)) ? relative_to.position : {x: 0, y: 0};
235
+ a = {:x => position[:x] - relative_position[:x], :y => position[:y] - relative_position[:y]}
236
+ end
237
+ end
238
+
239
+ module Viewport
240
+ include PositionAndSize
241
+
242
+ def size
243
+ win = self.window
244
+ return {:x => `#{win}.__native__.innerWidth`, :y => `#{win}.__native__.innerHeight`} if (presto? || webkit?)
245
+ doc = Situated::Utilities.native_compat_element(self)
246
+ return {:x => `#{doc}.__native__.clientWidth`, :y => `#{doc}.__native__.clientHeight`}
247
+ end
248
+
249
+ def scroll
250
+ win = self.window
251
+ doc = Situated::Utilities.native_compat_element(self)
252
+ return {:x => `#{win}.__native__.pageXOffset` || `#{doc}.__native__.scrollLeft`, :y => `#{win}.__native__pageYOffset` || `#{doc}.__native__.scrollTop`}
253
+ end
254
+
255
+ def scroll_size
256
+ doc = Situated::Utilities.native_compat_element(self)
257
+ min = self.size
258
+ return {:x => `Math.max(#{doc}.__native__.scrollWidth, #{min[:x]})`, :y => `Math.max(#{doc}.__native__.scrollHeight,#{ min[:y]})`}
259
+ end
260
+
261
+ # call-seq:
262
+ # viewport.position -> hash
263
+ # returns the position of the viewport, which is always {:x => 0, :y => 0}
264
+ #
265
+ def position
266
+ return {:x => 0, :y => 0}
267
+ end
268
+
269
+ # call-seq:
270
+ # viewport.position -> hash
271
+ # returns the coordinates of the viewport in pixesl as integers
272
+ # within a hash with the keys
273
+ # :top
274
+ # :left
275
+ # :bottom
276
+ # :right
277
+ # :height
278
+ # :width
279
+ #
280
+ # For example, a _viewport_ that is 500px wide and 800px tall will return
281
+ # viewport.coordinates
282
+ # {:top => 0,
283
+ # :left => 0,
284
+ # :bottom => 800,
285
+ # :right => 500,
286
+ # :height => 800,
287
+ # :width => 500 }
288
+ #
289
+ def coordinates
290
+ size = self.size
291
+ return {:top => 0, :left => 0, :bottom => size[:y], :right => size[:x], :height => size[:y], :width => size[:x]}
292
+ end
293
+ end
294
+
295
+ module Utilities
296
+ def self.is_body?(element)
297
+ `(/^(?:body|html)$/i).test(element.tagName)`
298
+ end
299
+
300
+ def self.styleNumber(native_element, style)
301
+ `parseInt(window.styleString(native_element, style)) || 0`
302
+ end
303
+
304
+ def self.border_box(element)
305
+ `window.styleString(element, '-moz-box-sizing') == 'border-box'`
306
+ end
307
+
308
+ def self.top_border(element)
309
+ `c$Situated.c$Utilities.m$styleNumber(element, 'border-top-width')`
310
+ end
311
+
312
+ def self.left_border(element)
313
+ `c$Situated.c$Utilities.m$styleNumber(element, 'border-left-width')`
314
+ end
315
+
316
+ def self.native_compat_element(element)
317
+ `var doc = #{element.document}.__native__`
318
+ `$E((!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body)`
319
+ end
320
+ end
321
+ end
322
+
323
+ class Element
324
+ include Situated::Element
325
+ end
326
+
327
+ Document.extend(Situated::Viewport)
328
+ Window.extend(Situated::Viewport)