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.
- data/Manifest.txt +25 -0
- data/lib/red/version.rb +1 -1
- data/lib/source/redshift/accessors.rb +580 -0
- data/lib/source/redshift/browser.rb +150 -0
- data/lib/source/redshift/chainable.rb +75 -0
- data/lib/source/redshift/code_events.rb +204 -0
- data/lib/source/redshift/cookie.rb +142 -0
- data/lib/source/redshift/document.rb +216 -0
- data/lib/source/redshift/element.rb +417 -0
- data/lib/source/redshift/event.rb +312 -0
- data/lib/source/redshift/redshift.red +5 -0
- data/lib/source/redshift/request.rb +276 -0
- data/lib/source/redshift/selectors.rb +374 -0
- data/lib/source/redshift/situated.rb +328 -0
- data/lib/source/redshift/store.rb +48 -0
- data/lib/source/redshift/transform.rb +199 -0
- data/lib/source/redshift/tween.rb +66 -0
- data/lib/source/redshift/user_events.rb +215 -0
- data/lib/source/redshift/validator.rb +21 -0
- data/lib/source/redshift/window.rb +11 -0
- data/lib/source/redspec/index.html +11 -0
- data/lib/source/redspec/lib/red_spec/red_spec.red +525 -0
- data/lib/source/redspec/lib/stylesheets/specs.sass +290 -0
- metadata +27 -2
@@ -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)
|