qtip2-jquery-rails 2.1.108 → 2.2.100

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b70df43979b59ec2b579ab1be3cee6d122366962
4
- data.tar.gz: 8735d9cd39da330cc2f20c3f3e33e8637ead0f59
3
+ metadata.gz: 154e6b873880e9dcc0f57d5cccc9c84c9232df46
4
+ data.tar.gz: 0e9c3f4ae90e73467414b7744b62c8b10923f0cc
5
5
  SHA512:
6
- metadata.gz: a19212d9276ed869f13a651928cfcd8c968368abad37891572a8ff8855287dfa295c6ef34f8197a596eae1c8ba9e5ea827671e436a04bbbf0aa0d03f782a2f6f
7
- data.tar.gz: 7519485a7a5c6180fb46982da1113b21427cc71b3f4df755c95db548335d20a6f58bd793b4ff8e6dbe3c6de55fb12d6eee985fe230495a1b85db406187e9c47c
6
+ metadata.gz: 86afbc1f08bd7d0530fe3378985e78997b7e44db7b021cdc5bd3719a72067c19d7ab43621289d8fef4f98ecc38449d3f8d4d3efa4a016d250e17759bb2d10e42
7
+ data.tar.gz: 6474c81e62c4a2c91bee046ff103af992e63a0335740a26833526ffff960d39fc7c59dcb26e067cc86985e2dee4037a13347386b823f462dd4109d7585ae6684
@@ -1,3 +1,9 @@
1
+ 2.2.100 / 2014-11-30
2
+ --------------------
3
+
4
+ - `IMPROVE` - Update qtip2 to v2.2.1 | [view](https://github.com/jhx/gem-qtip2-jquery-rails/commit/42184fc)
5
+
6
+
1
7
  2.1.108 / 2014-11-15
2
8
  --------------------
3
9
 
data/README.md CHANGED
@@ -15,7 +15,7 @@ Add these lines to your application's `Gemfile`:
15
15
 
16
16
  ```rb
17
17
  # qtip2 jquery plugin packaged for the rails asset pipeline
18
- gem 'qtip2-jquery-rails', '~> 2.1.107'
18
+ gem 'qtip2-jquery-rails', '~> 2.2.100'
19
19
  ```
20
20
 
21
21
  And then execute:
@@ -10,8 +10,8 @@ module Qtip2Jquery
10
10
  # "2.1.101" is qtip2 v2.1.1 + gem release 1
11
11
  # "2.1.110" is qtip2 v2.1.1 + gem release 10
12
12
  MAJOR = 2
13
- MINOR = 1
14
- PATCH = 108
13
+ MINOR = 2
14
+ PATCH = 100
15
15
  BUILD = nil
16
16
  VERSION = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
17
17
  end
@@ -7,11 +7,11 @@ feature 'Asset pipeline' do
7
7
  # save_and_open_page # for debugging (opens page in browser)
8
8
 
9
9
  expect(page).to have_text(<<-EOF
10
- * qTip2 - Pretty powerful tooltips - v2.1.1
10
+ * qTip2 - Pretty powerful tooltips - v2.2.1
11
11
  * http://qtip2.com
12
12
  *
13
- * Copyright (c) 2013 Craig Michael Thompson
14
- * Released under the MIT, GPL licenses
13
+ * Copyright (c) 2014
14
+ * Released under the MIT licenses
15
15
  * http://jquery.org/license
16
16
  EOF
17
17
  )
@@ -1,14 +1,14 @@
1
1
  /*
2
- * qTip2 - Pretty powerful tooltips - v2.1.1
2
+ * qTip2 - Pretty powerful tooltips - v2.2.1
3
3
  * http://qtip2.com
4
4
  *
5
- * Copyright (c) 2013 Craig Michael Thompson
6
- * Released under the MIT, GPL licenses
5
+ * Copyright (c) 2014
6
+ * Released under the MIT licenses
7
7
  * http://jquery.org/license
8
8
  *
9
- * Date: Thu Jul 11 2013 02:03 GMT+0100+0100
10
- * Plugins: tips modal viewport svg imagemap ie6
11
- * Styles: basic css3
9
+ * Date: Sat Sep 6 2014 06:25 EDT-0400
10
+ * Plugins: tips viewport imagemap svg modal ie6
11
+ * Styles: core basic css3
12
12
  */
13
13
  /*global window: false, jQuery: false, console: false, define: false */
14
14
 
@@ -19,16 +19,14 @@
19
19
  (function( factory ) {
20
20
  "use strict";
21
21
  if(typeof define === 'function' && define.amd) {
22
- define(['jquery', 'imagesloaded'], factory);
22
+ define(['jquery'], factory);
23
23
  }
24
24
  else if(jQuery && !jQuery.fn.qtip) {
25
25
  factory(jQuery);
26
26
  }
27
27
  }
28
28
  (function($) {
29
- /* This currently causes issues with Safari 6, so for it's disabled */
30
- //"use strict"; // (Dis)able ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
31
-
29
+ "use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
32
30
  ;// Munge the primitives - Paul Irish tip
33
31
  var TRUE = true,
34
32
  FALSE = false,
@@ -69,7 +67,7 @@ CLASS_DISABLED = NAMESPACE+'-disabled',
69
67
 
70
68
  replaceSuffix = '_replacedByqTip',
71
69
  oldtitle = 'oldtitle',
72
- trackingBound;
70
+ trackingBound,
73
71
 
74
72
  // Browser detection
75
73
  BROWSER = {
@@ -80,222 +78,227 @@ BROWSER = {
80
78
  * Credit to James Padolsey for the original implemntation!
81
79
  */
82
80
  ie: (function(){
83
- var v = 3, div = document.createElement('div');
84
- while ((div.innerHTML = '<!--[if gt IE '+(++v)+']><i></i><![endif]-->')) {
85
- if(!div.getElementsByTagName('i')[0]) { break; }
86
- }
81
+ for (
82
+ var v = 4, i = document.createElement("div");
83
+ (i.innerHTML = "<!--[if gt IE " + v + "]><i></i><![endif]-->") && i.getElementsByTagName("i")[0];
84
+ v+=1
85
+ ) {}
87
86
  return v > 4 ? v : NaN;
88
87
  }()),
89
-
88
+
90
89
  /*
91
90
  * iOS version detection
92
91
  */
93
- iOS: parseFloat(
92
+ iOS: parseFloat(
94
93
  ('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
95
94
  .replace('undefined', '3_2').replace('_', '.').replace('_', '')
96
95
  ) || FALSE
97
96
  };
97
+ ;function QTip(target, options, id, attr) {
98
+ // Elements and ID
99
+ this.id = id;
100
+ this.target = target;
101
+ this.tooltip = NULL;
102
+ this.elements = { target: target };
103
+
104
+ // Internal constructs
105
+ this._id = NAMESPACE + '-' + id;
106
+ this.timers = { img: {} };
107
+ this.options = options;
108
+ this.plugins = {};
109
+
110
+ // Cache object
111
+ this.cache = {
112
+ event: {},
113
+ target: $(),
114
+ disabled: FALSE,
115
+ attr: attr,
116
+ onTooltip: FALSE,
117
+ lastClass: ''
118
+ };
119
+
120
+ // Set the initial flags
121
+ this.rendered = this.destroyed = this.disabled = this.waiting =
122
+ this.hiddenDuringWait = this.positioning = this.triggering = FALSE;
123
+ }
124
+ PROTOTYPE = QTip.prototype;
125
+
126
+ PROTOTYPE._when = function(deferreds) {
127
+ return $.when.apply($, deferreds);
128
+ };
129
+
130
+ PROTOTYPE.render = function(show) {
131
+ if(this.rendered || this.destroyed) { return this; } // If tooltip has already been rendered, exit
132
+
133
+ var self = this,
134
+ options = this.options,
135
+ cache = this.cache,
136
+ elements = this.elements,
137
+ text = options.content.text,
138
+ title = options.content.title,
139
+ button = options.content.button,
140
+ posOptions = options.position,
141
+ namespace = '.'+this._id+' ',
142
+ deferreds = [],
143
+ tooltip;
144
+
145
+ // Add ARIA attributes to target
146
+ $.attr(this.target[0], 'aria-describedby', this._id);
147
+
148
+ // Create public position object that tracks current position corners
149
+ cache.posClass = this._createPosClass(
150
+ (this.position = { my: posOptions.my, at: posOptions.at }).my
151
+ );
152
+
153
+ // Create tooltip element
154
+ this.tooltip = elements.tooltip = tooltip = $('<div/>', {
155
+ 'id': this._id,
156
+ 'class': [ NAMESPACE, CLASS_DEFAULT, options.style.classes, cache.posClass ].join(' '),
157
+ 'width': options.style.width || '',
158
+ 'height': options.style.height || '',
159
+ 'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,
160
+
161
+ /* ARIA specific attributes */
162
+ 'role': 'alert',
163
+ 'aria-live': 'polite',
164
+ 'aria-atomic': FALSE,
165
+ 'aria-describedby': this._id + '-content',
166
+ 'aria-hidden': TRUE
167
+ })
168
+ .toggleClass(CLASS_DISABLED, this.disabled)
169
+ .attr(ATTR_ID, this.id)
170
+ .data(NAMESPACE, this)
171
+ .appendTo(posOptions.container)
172
+ .append(
173
+ // Create content element
174
+ elements.content = $('<div />', {
175
+ 'class': NAMESPACE + '-content',
176
+ 'id': this._id + '-content',
177
+ 'aria-atomic': TRUE
178
+ })
179
+ );
180
+
181
+ // Set rendered flag and prevent redundant reposition calls for now
182
+ this.rendered = -1;
183
+ this.positioning = TRUE;
184
+
185
+ // Create title...
186
+ if(title) {
187
+ this._createTitle();
188
+
189
+ // Update title only if its not a callback (called in toggle if so)
190
+ if(!$.isFunction(title)) {
191
+ deferreds.push( this._updateTitle(title, FALSE) );
192
+ }
193
+ }
194
+
195
+ // Create button
196
+ if(button) { this._createButton(); }
197
+
198
+ // Set proper rendered flag and update content if not a callback function (called in toggle)
199
+ if(!$.isFunction(text)) {
200
+ deferreds.push( this._updateContent(text, FALSE) );
201
+ }
202
+ this.rendered = TRUE;
203
+
204
+ // Setup widget classes
205
+ this._setWidget();
206
+
207
+ // Initialize 'render' plugins
208
+ $.each(PLUGINS, function(name) {
209
+ var instance;
210
+ if(this.initialize === 'render' && (instance = this(self))) {
211
+ self.plugins[name] = instance;
212
+ }
213
+ });
214
+
215
+ // Unassign initial events and assign proper events
216
+ this._unassignEvents();
217
+ this._assignEvents();
218
+
219
+ // When deferreds have completed
220
+ this._when(deferreds).then(function() {
221
+ // tooltiprender event
222
+ self._trigger('render');
223
+
224
+ // Reset flags
225
+ self.positioning = FALSE;
226
+
227
+ // Show tooltip if not hidden during wait period
228
+ if(!self.hiddenDuringWait && (options.show.ready || show)) {
229
+ self.toggle(TRUE, cache.event, FALSE);
230
+ }
231
+ self.hiddenDuringWait = FALSE;
232
+ });
233
+
234
+ // Expose API
235
+ QTIP.api[this.id] = this;
236
+
237
+ return this;
238
+ };
239
+
240
+ PROTOTYPE.destroy = function(immediate) {
241
+ // Set flag the signify destroy is taking place to plugins
242
+ // and ensure it only gets destroyed once!
243
+ if(this.destroyed) { return this.target; }
244
+
245
+ function process() {
246
+ if(this.destroyed) { return; }
247
+ this.destroyed = TRUE;
248
+
249
+ var target = this.target,
250
+ title = target.attr(oldtitle),
251
+ timer;
252
+
253
+ // Destroy tooltip if rendered
254
+ if(this.rendered) {
255
+ this.tooltip.stop(1,0).find('*').remove().end().remove();
256
+ }
257
+
258
+ // Destroy all plugins
259
+ $.each(this.plugins, function(name) {
260
+ this.destroy && this.destroy();
261
+ });
262
+
263
+ // Clear timers
264
+ for(timer in this.timers) {
265
+ clearTimeout(this.timers[timer]);
266
+ }
267
+
268
+ // Remove api object and ARIA attributes
269
+ target.removeData(NAMESPACE)
270
+ .removeAttr(ATTR_ID)
271
+ .removeAttr(ATTR_HAS)
272
+ .removeAttr('aria-describedby');
273
+
274
+ // Reset old title attribute if removed
275
+ if(this.options.suppress && title) {
276
+ target.attr('title', title).removeAttr(oldtitle);
277
+ }
278
+
279
+ // Remove qTip events associated with this API
280
+ this._unassignEvents();
281
+
282
+ // Remove ID from used id objects, and delete object references
283
+ // for better garbage collection and leak protection
284
+ this.options = this.elements = this.cache = this.timers =
285
+ this.plugins = this.mouse = NULL;
286
+
287
+ // Delete epoxsed API object
288
+ delete QTIP.api[this.id];
289
+ }
98
290
 
99
- ;function QTip(target, options, id, attr) {
100
- // Elements and ID
101
- this.id = id;
102
- this.target = target;
103
- this.tooltip = NULL;
104
- this.elements = elements = { target: target };
105
-
106
- // Internal constructs
107
- this._id = NAMESPACE + '-' + id;
108
- this.timers = { img: {} };
109
- this.options = options;
110
- this.plugins = {};
111
-
112
- // Cache object
113
- this.cache = cache = {
114
- event: {},
115
- target: $(),
116
- disabled: FALSE,
117
- attr: attr,
118
- onTooltip: FALSE,
119
- lastClass: ''
120
- };
121
-
122
- // Set the initial flags
123
- this.rendered = this.destroyed = this.disabled = this.waiting =
124
- this.hiddenDuringWait = this.positioning = this.triggering = FALSE;
125
- }
126
- PROTOTYPE = QTip.prototype;
127
-
128
- PROTOTYPE.render = function(show) {
129
- if(this.rendered || this.destroyed) { return this; } // If tooltip has already been rendered, exit
130
-
131
- var self = this,
132
- options = this.options,
133
- cache = this.cache,
134
- elements = this.elements,
135
- text = options.content.text,
136
- title = options.content.title,
137
- button = options.content.button,
138
- posOptions = options.position,
139
- namespace = '.'+this._id+' ',
140
- deferreds = [];
141
-
142
- // Add ARIA attributes to target
143
- $.attr(this.target[0], 'aria-describedby', this._id);
144
-
145
- // Create tooltip element
146
- this.tooltip = elements.tooltip = tooltip = $('<div/>', {
147
- 'id': this._id,
148
- 'class': [ NAMESPACE, CLASS_DEFAULT, options.style.classes, NAMESPACE + '-pos-' + options.position.my.abbrev() ].join(' '),
149
- 'width': options.style.width || '',
150
- 'height': options.style.height || '',
151
- 'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,
152
-
153
- /* ARIA specific attributes */
154
- 'role': 'alert',
155
- 'aria-live': 'polite',
156
- 'aria-atomic': FALSE,
157
- 'aria-describedby': this._id + '-content',
158
- 'aria-hidden': TRUE
159
- })
160
- .toggleClass(CLASS_DISABLED, this.disabled)
161
- .attr(ATTR_ID, this.id)
162
- .data(NAMESPACE, this)
163
- .appendTo(posOptions.container)
164
- .append(
165
- // Create content element
166
- elements.content = $('<div />', {
167
- 'class': NAMESPACE + '-content',
168
- 'id': this._id + '-content',
169
- 'aria-atomic': TRUE
170
- })
171
- );
172
-
173
- // Set rendered flag and prevent redundant reposition calls for now
174
- this.rendered = -1;
175
- this.positioning = TRUE;
176
-
177
- // Create title...
178
- if(title) {
179
- this._createTitle();
180
-
181
- // Update title only if its not a callback (called in toggle if so)
182
- if(!$.isFunction(title)) {
183
- deferreds.push( this._updateTitle(title, FALSE) );
184
- }
185
- }
186
-
187
- // Create button
188
- if(button) { this._createButton(); }
189
-
190
- // Set proper rendered flag and update content if not a callback function (called in toggle)
191
- if(!$.isFunction(text)) {
192
- deferreds.push( this._updateContent(text, FALSE) );
193
- }
194
- this.rendered = TRUE;
195
-
196
- // Setup widget classes
197
- this._setWidget();
198
-
199
- // Assign passed event callbacks (before plugins!)
200
- $.each(options.events, function(name, callback) {
201
- $.isFunction(callback) && tooltip.bind(
202
- (name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name])
203
- .join(namespace)+namespace, callback
204
- );
205
- });
206
-
207
- // Initialize 'render' plugins
208
- $.each(PLUGINS, function(name) {
209
- var instance;
210
- if(this.initialize === 'render' && (instance = this(self))) {
211
- self.plugins[name] = instance;
212
- }
213
- });
214
-
215
- // Assign events
216
- this._assignEvents();
217
-
218
- // When deferreds have completed
219
- $.when.apply($, deferreds).then(function() {
220
- // tooltiprender event
221
- self._trigger('render');
222
-
223
- // Reset flags
224
- self.positioning = FALSE;
225
-
226
- // Show tooltip if not hidden during wait period
227
- if(!self.hiddenDuringWait && (options.show.ready || show)) {
228
- self.toggle(TRUE, cache.event, FALSE);
229
- }
230
- self.hiddenDuringWait = FALSE;
231
- });
232
-
233
- // Expose API
234
- QTIP.api[this.id] = this;
235
-
236
- return this;
237
- };
238
-
239
- PROTOTYPE.destroy = function(immediate) {
240
- // Set flag the signify destroy is taking place to plugins
241
- // and ensure it only gets destroyed once!
242
- if(this.destroyed) { return this.target; }
243
-
244
- function process() {
245
- if(this.destroyed) { return; }
246
- this.destroyed = TRUE;
247
-
248
- var target = this.target,
249
- title = target.attr(oldtitle);
250
-
251
- // Destroy tooltip if rendered
252
- if(this.rendered) {
253
- this.tooltip.stop(1,0).find('*').remove().end().remove();
254
- }
255
-
256
- // Destroy all plugins
257
- $.each(this.plugins, function(name) {
258
- this.destroy && this.destroy();
259
- });
260
-
261
- // Clear timers and remove bound events
262
- clearTimeout(this.timers.show);
263
- clearTimeout(this.timers.hide);
264
- this._unassignEvents();
265
-
266
- // Remove api object and ARIA attributes
267
- target.removeData(NAMESPACE).removeAttr(ATTR_ID)
268
- .removeAttr('aria-describedby');
269
-
270
- // Reset old title attribute if removed
271
- if(this.options.suppress && title) {
272
- target.attr('title', title).removeAttr(oldtitle);
273
- }
274
-
275
- // Remove qTip events associated with this API
276
- this._unbind(target);
277
-
278
- // Remove ID from used id objects, and delete object references
279
- // for better garbage collection and leak protection
280
- this.options = this.elements = this.cache = this.timers =
281
- this.plugins = this.mouse = NULL;
282
-
283
- // Delete epoxsed API object
284
- delete QTIP.api[this.id];
285
- }
286
-
287
- // If an immediate destory is needed
288
- if(immediate !== TRUE && this.rendered) {
289
- tooltip.one('tooltiphidden', $.proxy(process, this));
290
- !this.triggering && this.hide();
291
- }
292
-
293
- // If we're not in the process of hiding... process
294
- else { process.call(this); }
295
-
296
- return this.target;
297
- };
298
-
291
+ // If an immediate destory is needed
292
+ if((immediate !== TRUE || this.triggering === 'hide') && this.rendered) {
293
+ this.tooltip.one('tooltiphidden', $.proxy(process, this));
294
+ !this.triggering && this.hide();
295
+ }
296
+
297
+ // If we're not in the process of hiding... process
298
+ else { process.call(this); }
299
+
300
+ return this.target;
301
+ };
299
302
  ;function invalidOpt(a) {
300
303
  return a === NULL || $.type(a) !== 'object';
301
304
  }
@@ -352,7 +355,7 @@ function sanitizeOptions(opts) {
352
355
  }
353
356
 
354
357
  if('title' in content) {
355
- if(!invalidOpt(content.title)) {
358
+ if($.isPlainObject(content.title)) {
356
359
  content.button = content.title.button;
357
360
  content.title = content.title.text;
358
361
  }
@@ -368,7 +371,7 @@ function sanitizeOptions(opts) {
368
371
  }
369
372
 
370
373
  if('show' in opts && invalidOpt(opts.show)) {
371
- opts.show = opts.show.jquery ? { target: opts.show } :
374
+ opts.show = opts.show.jquery ? { target: opts.show } :
372
375
  opts.show === TRUE ? { ready: TRUE } : { event: opts.show };
373
376
  }
374
377
 
@@ -433,14 +436,14 @@ CHECKS = PROTOTYPE.checks = {
433
436
  },
434
437
  '^content.title.(text|button)$': function(obj, o, v) {
435
438
  this.set('content.'+o, v); // Backwards title.text/button compat
436
- },
439
+ },
437
440
 
438
441
  // Position checks
439
442
  '^position.(my|at)$': function(obj, o, v){
440
- 'string' === typeof v && (obj[o] = new CORNER(v, o === 'at'));
443
+ 'string' === typeof v && (this.position[o] = obj[o] = new CORNER(v, o === 'at'));
441
444
  },
442
445
  '^position.container$': function(obj, o, v){
443
- this.tooltip.appendTo(v);
446
+ this.rendered && this.tooltip.appendTo(v);
444
447
  },
445
448
 
446
449
  // Show checks
@@ -450,29 +453,30 @@ CHECKS = PROTOTYPE.checks = {
450
453
 
451
454
  // Style checks
452
455
  '^style.classes$': function(obj, o, v, p) {
453
- this.tooltip.removeClass(p).addClass(v);
456
+ this.rendered && this.tooltip.removeClass(p).addClass(v);
454
457
  },
455
- '^style.width|height': function(obj, o, v) {
456
- this.tooltip.css(o, v);
458
+ '^style.(width|height)': function(obj, o, v) {
459
+ this.rendered && this.tooltip.css(o, v);
457
460
  },
458
461
  '^style.widget|content.title': function() {
459
- this._setWidget();
462
+ this.rendered && this._setWidget();
460
463
  },
461
464
  '^style.def': function(obj, o, v) {
462
- this.tooltip.toggleClass(CLASS_DEFAULT, !!v);
465
+ this.rendered && this.tooltip.toggleClass(CLASS_DEFAULT, !!v);
463
466
  },
464
467
 
465
468
  // Events check
466
469
  '^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
467
- tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
470
+ this.rendered && this.tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
468
471
  },
469
472
 
470
473
  // Properties which require event reassignment
471
474
  '^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
472
- var posOptions = this.options.position;
475
+ if(!this.rendered) { return; }
473
476
 
474
477
  // Set tracking flag
475
- tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);
478
+ var posOptions = this.options.position;
479
+ this.tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);
476
480
 
477
481
  // Reassign events
478
482
  this._unassignEvents();
@@ -543,7 +547,7 @@ PROTOTYPE.set = function(option, value) {
543
547
 
544
548
  // Set all of the defined options to their new values
545
549
  $.each(option, function(notation, value) {
546
- if(!rendered && !rrender.test(notation)) {
550
+ if(rendered && rrender.test(notation)) {
547
551
  delete option[notation]; return;
548
552
  }
549
553
 
@@ -577,7 +581,6 @@ PROTOTYPE.set = function(option, value) {
577
581
 
578
582
  return this;
579
583
  };
580
-
581
584
  ;PROTOTYPE._update = function(content, element, reposition) {
582
585
  var self = this,
583
586
  cache = this.cache;
@@ -606,24 +609,31 @@ PROTOTYPE.set = function(option, value) {
606
609
 
607
610
  // Append new content if its a DOM array and show it if hidden
608
611
  if(content.jquery && content.length > 0) {
609
- element.children().detach().end().append( content.css({ display: 'block' }) );
612
+ element.empty().append(
613
+ content.css({ display: 'block', visibility: 'visible' })
614
+ );
610
615
  }
611
616
 
612
617
  // Content is a regular string, insert the new content
613
618
  else { element.html(content); }
614
619
 
615
- // If imagesLoaded is included, ensure images have loaded and return promise
616
- cache.waiting = TRUE;
620
+ // Wait for content to be loaded, and reposition
621
+ return this._waitForContent(element).then(function(images) {
622
+ if(self.rendered && self.tooltip[0].offsetWidth > 0) {
623
+ self.reposition(cache.event, !images.length);
624
+ }
625
+ });
626
+ };
617
627
 
618
- return ( $.fn.imagesLoaded ? element.imagesLoaded() : $.Deferred().resolve($([])) )
619
- .done(function(images) {
620
- cache.waiting = FALSE;
628
+ PROTOTYPE._waitForContent = function(element) {
629
+ var cache = this.cache;
621
630
 
622
- // Reposition if rendered
623
- if(images.length && self.rendered && self.tooltip[0].offsetWidth > 0) {
624
- self.reposition(cache.event, !images.length);
625
- }
626
- })
631
+ // Set flag
632
+ cache.waiting = TRUE;
633
+
634
+ // If imagesLoaded is included, ensure images have loaded and return promise
635
+ return ( $.fn.imagesLoaded ? element.imagesLoaded() : $.Deferred().resolve([]) )
636
+ .done(function() { cache.waiting = FALSE; })
627
637
  .promise();
628
638
  };
629
639
 
@@ -682,8 +692,11 @@ PROTOTYPE._removeTitle = function(reposition)
682
692
  if(reposition !== FALSE) { this.reposition(); }
683
693
  }
684
694
  };
695
+ ;PROTOTYPE._createPosClass = function(my) {
696
+ return NAMESPACE + '-pos-' + (my || this.options.position.my).abbrev();
697
+ };
685
698
 
686
- ;PROTOTYPE.reposition = function(event, effect) {
699
+ PROTOTYPE.reposition = function(event, effect) {
687
700
  if(!this.rendered || this.positioning || this.destroyed) { return this; }
688
701
 
689
702
  // Set positioning flag
@@ -699,8 +712,8 @@ PROTOTYPE._removeTitle = function(reposition)
699
712
  container = posOptions.container,
700
713
  adjust = posOptions.adjust,
701
714
  method = adjust.method.split(' '),
702
- elemWidth = tooltip.outerWidth(FALSE),
703
- elemHeight = tooltip.outerHeight(FALSE),
715
+ tooltipWidth = tooltip.outerWidth(FALSE),
716
+ tooltipHeight = tooltip.outerHeight(FALSE),
704
717
  targetWidth = 0,
705
718
  targetHeight = 0,
706
719
  type = tooltip.css('position'),
@@ -710,7 +723,7 @@ PROTOTYPE._removeTitle = function(reposition)
710
723
  win = $(window),
711
724
  doc = container[0].ownerDocument,
712
725
  mouse = this.mouse,
713
- pluginCalculations, offset;
726
+ pluginCalculations, offset, adjusted, newClass;
714
727
 
715
728
  // Check if absolute position was passed
716
729
  if($.isArray(target) && target.length === 2) {
@@ -720,20 +733,30 @@ PROTOTYPE._removeTitle = function(reposition)
720
733
  }
721
734
 
722
735
  // Check if mouse was the target
723
- else if(target === 'mouse' && ((event && event.pageX) || cache.event.pageX)) {
736
+ else if(target === 'mouse') {
724
737
  // Force left top to allow flipping
725
738
  at = { x: LEFT, y: TOP };
726
739
 
727
- // Use cached event if one isn't available for positioning
728
- event = mouse && mouse.pageX && (adjust.mouse || !event || !event.pageX) ? mouse :
729
- (event && (event.type === 'resize' || event.type === 'scroll') ? cache.event :
730
- event && event.pageX && event.type === 'mousemove' ? event :
731
- (!adjust.mouse || this.options.show.distance) && cache.origin && cache.origin.pageX ? cache.origin :
732
- event) || event || cache.event || mouse || {};
740
+ // Use the mouse origin that caused the show event, if distance hiding is enabled
741
+ if((!adjust.mouse || this.options.hide.distance) && cache.origin && cache.origin.pageX) {
742
+ event = cache.origin;
743
+ }
744
+
745
+ // Use cached event for resize/scroll events
746
+ else if(!event || (event && (event.type === 'resize' || event.type === 'scroll'))) {
747
+ event = cache.event;
748
+ }
749
+
750
+ // Otherwise, use the cached mouse coordinates if available
751
+ else if(mouse && mouse.pageX) {
752
+ event = mouse;
753
+ }
733
754
 
734
755
  // Calculate body and container offset and take them into account below
735
756
  if(type !== 'static') { position = container.offset(); }
736
- if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) { offset = $(doc.body).offset(); }
757
+ if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) {
758
+ offset = $(document.body).offset();
759
+ }
737
760
 
738
761
  // Use event coordinates for position
739
762
  position = {
@@ -742,20 +765,25 @@ PROTOTYPE._removeTitle = function(reposition)
742
765
  };
743
766
 
744
767
  // Scroll events are a pain, some browsers
745
- if(adjust.mouse && isScroll) {
746
- position.left -= mouse.scrollX - win.scrollLeft();
747
- position.top -= mouse.scrollY - win.scrollTop();
768
+ if(adjust.mouse && isScroll && mouse) {
769
+ position.left -= (mouse.scrollX || 0) - win.scrollLeft();
770
+ position.top -= (mouse.scrollY || 0) - win.scrollTop();
748
771
  }
749
772
  }
750
773
 
751
774
  // Target wasn't mouse or absolute...
752
775
  else {
753
776
  // Check if event targetting is being used
754
- if(target === 'event' && event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
755
- cache.target = $(event.target);
777
+ if(target === 'event') {
778
+ if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
779
+ cache.target = $(event.target);
780
+ }
781
+ else if(!event.target) {
782
+ cache.target = this.elements.target;
783
+ }
756
784
  }
757
785
  else if(target !== 'event'){
758
- cache.target = $(target.jquery ? target : elements.target);
786
+ cache.target = $(target.jquery ? target : this.elements.target);
759
787
  }
760
788
  target = cache.target;
761
789
 
@@ -782,7 +810,7 @@ PROTOTYPE._removeTitle = function(reposition)
782
810
  }
783
811
 
784
812
  // Check if the target is an SVG element
785
- else if(PLUGINS.svg && target[0].ownerSVGElement) {
813
+ else if(PLUGINS.svg && target && target[0].ownerSVGElement) {
786
814
  pluginCalculations = PLUGINS.svg(this, target, at, PLUGINS.viewport ? method : FALSE);
787
815
  }
788
816
 
@@ -805,8 +833,8 @@ PROTOTYPE._removeTitle = function(reposition)
805
833
  position = this.reposition.offset(target, position, container);
806
834
 
807
835
  // Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2)
808
- if((BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1) ||
809
- (BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33) ||
836
+ if((BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1) ||
837
+ (BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33) ||
810
838
  (!BROWSER.iOS && type === 'fixed')
811
839
  ){
812
840
  position.left -= win.scrollLeft();
@@ -821,23 +849,31 @@ PROTOTYPE._removeTitle = function(reposition)
821
849
  }
822
850
 
823
851
  // Adjust position relative to tooltip
824
- position.left += adjust.x + (my.x === RIGHT ? -elemWidth : my.x === CENTER ? -elemWidth / 2 : 0);
825
- position.top += adjust.y + (my.y === BOTTOM ? -elemHeight : my.y === CENTER ? -elemHeight / 2 : 0);
852
+ position.left += adjust.x + (my.x === RIGHT ? -tooltipWidth : my.x === CENTER ? -tooltipWidth / 2 : 0);
853
+ position.top += adjust.y + (my.y === BOTTOM ? -tooltipHeight : my.y === CENTER ? -tooltipHeight / 2 : 0);
826
854
 
827
855
  // Use viewport adjustment plugin if enabled
828
856
  if(PLUGINS.viewport) {
829
- position.adjusted = PLUGINS.viewport(
830
- this, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight
857
+ adjusted = position.adjusted = PLUGINS.viewport(
858
+ this, position, posOptions, targetWidth, targetHeight, tooltipWidth, tooltipHeight
831
859
  );
832
860
 
833
861
  // Apply offsets supplied by positioning plugin (if used)
834
- if(offset && position.adjusted.left) { position.left += offset.left; }
835
- if(offset && position.adjusted.top) { position.top += offset.top; }
862
+ if(offset && adjusted.left) { position.left += offset.left; }
863
+ if(offset && adjusted.top) { position.top += offset.top; }
864
+
865
+ // Apply any new 'my' position
866
+ if(adjusted.my) { this.position.my = adjusted.my; }
836
867
  }
837
868
 
838
869
  // Viewport adjustment is disabled, set values to zero
839
870
  else { position.adjusted = { left: 0, top: 0 }; }
840
871
 
872
+ // Set tooltip position class if it's changed
873
+ if(cache.posClass !== (newClass = this._createPosClass(this.position.my))) {
874
+ tooltip.removeClass(cache.posClass).addClass( (cache.posClass = newClass) );
875
+ }
876
+
841
877
  // tooltipmove event
842
878
  if(!this._trigger('move', [position, viewport.elem || viewport], event)) { return this; }
843
879
  delete position.adjusted;
@@ -921,22 +957,31 @@ var C = (CORNER = PROTOTYPE.reposition.Corner = function(corner, forceY) {
921
957
  }).prototype;
922
958
 
923
959
  C.invert = function(z, center) {
924
- this[z] = this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z];
960
+ this[z] = this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z];
925
961
  };
926
962
 
927
- C.string = function() {
963
+ C.string = function(join) {
928
964
  var x = this.x, y = this.y;
929
- return x === y ? x : this.precedance === Y || (this.forceY && y !== 'center') ? y+' '+x : x+' '+y;
965
+
966
+ var result = x !== y ?
967
+ (x === 'center' || y !== 'center' && (this.precedance === Y || this.forceY) ?
968
+ [y,x] : [x,y]
969
+ ) :
970
+ [x];
971
+
972
+ return join !== false ? result.join(' ') : result;
930
973
  };
931
974
 
932
975
  C.abbrev = function() {
933
- var result = this.string().split(' ');
976
+ var result = this.string(false);
934
977
  return result[0].charAt(0) + (result[1] && result[1].charAt(0) || '');
935
978
  };
936
979
 
937
980
  C.clone = function() {
938
981
  return new CORNER( this.string(), this.forceY );
939
- };;
982
+ };
983
+
984
+ ;
940
985
  PROTOTYPE.toggle = function(state, event) {
941
986
  var cache = this.cache,
942
987
  options = this.options,
@@ -944,16 +989,16 @@ PROTOTYPE.toggle = function(state, event) {
944
989
 
945
990
  // Try to prevent flickering when tooltip overlaps show element
946
991
  if(event) {
947
- if((/over|enter/).test(event.type) && (/out|leave/).test(cache.event.type) &&
992
+ if((/over|enter/).test(event.type) && cache.event && (/out|leave/).test(cache.event.type) &&
948
993
  options.show.target.add(event.target).length === options.show.target.length &&
949
994
  tooltip.has(event.relatedTarget).length) {
950
995
  return this;
951
996
  }
952
997
 
953
998
  // Cache event
954
- cache.event = $.extend({}, event);
999
+ cache.event = $.event.fix(event);
955
1000
  }
956
-
1001
+
957
1002
  // If we're currently waiting and we've just hidden... stop it
958
1003
  this.waiting && !state && (this.hiddenDuringWait = TRUE);
959
1004
 
@@ -967,10 +1012,10 @@ PROTOTYPE.toggle = function(state, event) {
967
1012
  posOptions = this.options.position,
968
1013
  contentOptions = this.options.content,
969
1014
  width = this.tooltip.css('width'),
970
- visible = this.tooltip[0].offsetWidth > 0,
1015
+ visible = this.tooltip.is(':visible'),
971
1016
  animate = state || opts.target.length === 1,
972
1017
  sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target,
973
- identicalState, allow, showEvent, delay;
1018
+ identicalState, allow, showEvent, delay, after;
974
1019
 
975
1020
  // Detect state if valid one isn't provided
976
1021
  if((typeof state).search('boolean|number')) { state = !visible; }
@@ -981,6 +1026,9 @@ PROTOTYPE.toggle = function(state, event) {
981
1026
  // Fire tooltip(show/hide) event and check if destroyed
982
1027
  allow = !identicalState ? !!this._trigger(type, [90]) : NULL;
983
1028
 
1029
+ // Check to make sure the tooltip wasn't destroyed in the callback
1030
+ if(this.destroyed) { return this; }
1031
+
984
1032
  // If the user didn't stop the method prematurely and we're showing the tooltip, focus it
985
1033
  if(allow !== FALSE && state) { this.focus(event); }
986
1034
 
@@ -993,7 +1041,7 @@ PROTOTYPE.toggle = function(state, event) {
993
1041
  // Execute state specific properties
994
1042
  if(state) {
995
1043
  // Store show origin coordinates
996
- cache.origin = $.extend({}, this.mouse);
1044
+ this.mouse && (cache.origin = $.event.fix(this.mouse));
997
1045
 
998
1046
  // Update tooltip content & title if it's a dynamic function
999
1047
  if($.isFunction(contentOptions.text)) { this._updateContent(contentOptions.text, FALSE); }
@@ -1092,7 +1140,6 @@ PROTOTYPE.toggle = function(state, event) {
1092
1140
  PROTOTYPE.show = function(event) { return this.toggle(TRUE, event); };
1093
1141
 
1094
1142
  PROTOTYPE.hide = function(event) { return this.toggle(FALSE, event); };
1095
-
1096
1143
  ;PROTOTYPE.focus = function(event) {
1097
1144
  if(!this.rendered || this.destroyed) { return this; }
1098
1145
 
@@ -1138,12 +1185,17 @@ PROTOTYPE.blur = function(event) {
1138
1185
 
1139
1186
  return this;
1140
1187
  };
1141
-
1142
1188
  ;PROTOTYPE.disable = function(state) {
1143
1189
  if(this.destroyed) { return this; }
1144
1190
 
1145
- if('boolean' !== typeof state) {
1146
- state = !(this.tooltip.hasClass(CLASS_DISABLED) || this.disabled);
1191
+ // If 'toggle' is passed, toggle the current state
1192
+ if(state === 'toggle') {
1193
+ state = !(this.rendered ? this.tooltip.hasClass(CLASS_DISABLED) : this.disabled);
1194
+ }
1195
+
1196
+ // Disable if no state passed
1197
+ else if('boolean' !== typeof state) {
1198
+ state = TRUE;
1147
1199
  }
1148
1200
 
1149
1201
  if(this.rendered) {
@@ -1157,7 +1209,6 @@ PROTOTYPE.blur = function(event) {
1157
1209
  };
1158
1210
 
1159
1211
  PROTOTYPE.enable = function() { return this.disable(FALSE); };
1160
-
1161
1212
  ;PROTOTYPE._createButton = function()
1162
1213
  {
1163
1214
  var self = this,
@@ -1205,7 +1256,6 @@ PROTOTYPE._updateButton = function(button)
1205
1256
  if(button) { this._createButton(); }
1206
1257
  else { elem.remove(); }
1207
1258
  };
1208
-
1209
1259
  ;// Widget class creator
1210
1260
  function createWidgetClass(cls) {
1211
1261
  return WIDGET.concat('').join(cls ? '-'+cls+' ' : ' ');
@@ -1224,7 +1274,7 @@ PROTOTYPE._setWidget = function()
1224
1274
  tooltip.toggleClass(CLASS_DISABLED, disabled);
1225
1275
 
1226
1276
  tooltip.toggleClass('ui-helper-reset '+createWidgetClass(), on).toggleClass(CLASS_DEFAULT, this.options.style.def && !on);
1227
-
1277
+
1228
1278
  if(elements.content) {
1229
1279
  elements.content.toggleClass( createWidgetClass('content'), on);
1230
1280
  }
@@ -1234,23 +1284,33 @@ PROTOTYPE._setWidget = function()
1234
1284
  if(elements.button) {
1235
1285
  elements.button.toggleClass(NAMESPACE+'-icon', !on);
1236
1286
  }
1237
- };;function showMethod(event) {
1238
- if(this.tooltip.hasClass(CLASS_DISABLED)) { return FALSE; }
1287
+ };
1288
+ ;function delay(callback, duration) {
1289
+ // If tooltip has displayed, start hide timer
1290
+ if(duration > 0) {
1291
+ return setTimeout(
1292
+ $.proxy(callback, this), duration
1293
+ );
1294
+ }
1295
+ else{ callback.call(this); }
1296
+ }
1297
+
1298
+ function showMethod(event) {
1299
+ if(this.tooltip.hasClass(CLASS_DISABLED)) { return; }
1239
1300
 
1240
1301
  // Clear hide timers
1241
1302
  clearTimeout(this.timers.show);
1242
1303
  clearTimeout(this.timers.hide);
1243
1304
 
1244
1305
  // Start show timer
1245
- var callback = $.proxy(function(){ this.toggle(TRUE, event); }, this);
1246
- if(this.options.show.delay > 0) {
1247
- this.timers.show = setTimeout(callback, this.options.show.delay);
1248
- }
1249
- else{ callback(); }
1306
+ this.timers.show = delay.call(this,
1307
+ function() { this.toggle(TRUE, event); },
1308
+ this.options.show.delay
1309
+ );
1250
1310
  }
1251
1311
 
1252
1312
  function hideMethod(event) {
1253
- if(this.tooltip.hasClass(CLASS_DISABLED)) { return FALSE; }
1313
+ if(this.tooltip.hasClass(CLASS_DISABLED) || this.destroyed) { return; }
1254
1314
 
1255
1315
  // Check if new target was actually the tooltip element
1256
1316
  var relatedTarget = $(event.relatedTarget),
@@ -1263,8 +1323,8 @@ function hideMethod(event) {
1263
1323
 
1264
1324
  // Prevent hiding if tooltip is fixed and event target is the tooltip.
1265
1325
  // Or if mouse positioning is enabled and cursor momentarily overlaps
1266
- if(this !== relatedTarget[0] &&
1267
- (this.options.position.target === 'mouse' && ontoTooltip) ||
1326
+ if(this !== relatedTarget[0] &&
1327
+ (this.options.position.target === 'mouse' && ontoTooltip) ||
1268
1328
  (this.options.hide.fixed && (
1269
1329
  (/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget))
1270
1330
  ))
@@ -1278,20 +1338,22 @@ function hideMethod(event) {
1278
1338
  }
1279
1339
 
1280
1340
  // If tooltip has displayed, start hide timer
1281
- var callback = $.proxy(function(){ this.toggle(FALSE, event); }, this);
1282
- if(this.options.hide.delay > 0) {
1283
- this.timers.hide = setTimeout(callback, this.options.hide.delay);
1284
- }
1285
- else{ callback(); }
1341
+ this.timers.hide = delay.call(this,
1342
+ function() { this.toggle(FALSE, event); },
1343
+ this.options.hide.delay,
1344
+ this
1345
+ );
1286
1346
  }
1287
1347
 
1288
1348
  function inactiveMethod(event) {
1289
- if(this.tooltip.hasClass(CLASS_DISABLED) || !this.options.hide.inactive) { return FALSE; }
1349
+ if(this.tooltip.hasClass(CLASS_DISABLED) || !this.options.hide.inactive) { return; }
1290
1350
 
1291
1351
  // Clear timer
1292
1352
  clearTimeout(this.timers.inactive);
1293
- this.timers.inactive = setTimeout(
1294
- $.proxy(function(){ this.hide(event); }, this), this.options.hide.inactive
1353
+
1354
+ this.timers.inactive = delay.call(this,
1355
+ function(){ this.hide(event); },
1356
+ this.options.hide.inactive
1295
1357
  );
1296
1358
  }
1297
1359
 
@@ -1301,87 +1363,144 @@ function repositionMethod(event) {
1301
1363
 
1302
1364
  // Store mouse coordinates
1303
1365
  PROTOTYPE._storeMouse = function(event) {
1304
- this.mouse = {
1305
- pageX: event.pageX,
1306
- pageY: event.pageY,
1307
- type: 'mousemove',
1308
- scrollX: window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft,
1309
- scrollY: window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop
1310
- };
1366
+ (this.mouse = $.event.fix(event)).type = 'mousemove';
1367
+ return this;
1311
1368
  };
1312
1369
 
1313
1370
  // Bind events
1314
1371
  PROTOTYPE._bind = function(targets, events, method, suffix, context) {
1372
+ if(!targets || !method || !events.length) { return; }
1315
1373
  var ns = '.' + this._id + (suffix ? '-'+suffix : '');
1316
- events.length && $(targets).bind(
1374
+ $(targets).bind(
1317
1375
  (events.split ? events : events.join(ns + ' ')) + ns,
1318
1376
  $.proxy(method, context || this)
1319
1377
  );
1378
+ return this;
1320
1379
  };
1321
1380
  PROTOTYPE._unbind = function(targets, suffix) {
1322
- $(targets).unbind('.' + this._id + (suffix ? '-'+suffix : ''));
1381
+ targets && $(targets).unbind('.' + this._id + (suffix ? '-'+suffix : ''));
1382
+ return this;
1323
1383
  };
1324
1384
 
1325
- // Apply common event handlers using delegate (avoids excessive .bind calls!)
1326
- var ns = '.'+NAMESPACE;
1327
- function delegate(selector, events, method) {
1385
+ // Global delegation helper
1386
+ function delegate(selector, events, method) {
1328
1387
  $(document.body).delegate(selector,
1329
- (events.split ? events : events.join(ns + ' ')) + ns,
1388
+ (events.split ? events : events.join('.'+NAMESPACE + ' ')) + '.'+NAMESPACE,
1330
1389
  function() {
1331
1390
  var api = QTIP.api[ $.attr(this, ATTR_ID) ];
1332
1391
  api && !api.disabled && method.apply(api, arguments);
1333
1392
  }
1334
1393
  );
1335
1394
  }
1395
+ // Event trigger
1396
+ PROTOTYPE._trigger = function(type, args, event) {
1397
+ var callback = $.Event('tooltip'+type);
1398
+ callback.originalEvent = (event && $.extend({}, event)) || this.cache.event || NULL;
1336
1399
 
1337
- $(function() {
1338
- delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) {
1339
- var state = event.type === 'mouseenter',
1340
- tooltip = $(event.currentTarget),
1341
- target = $(event.relatedTarget || event.target),
1342
- options = this.options;
1400
+ this.triggering = type;
1401
+ this.tooltip.trigger(callback, [this].concat(args || []));
1402
+ this.triggering = FALSE;
1343
1403
 
1344
- // On mouseenter...
1345
- if(state) {
1346
- // Focus the tooltip on mouseenter (z-index stacking)
1347
- this.focus(event);
1404
+ return !callback.isDefaultPrevented();
1405
+ };
1348
1406
 
1349
- // Clear hide timer on tooltip hover to prevent it from closing
1350
- tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide);
1351
- }
1407
+ PROTOTYPE._bindEvents = function(showEvents, hideEvents, showTargets, hideTargets, showMethod, hideMethod) {
1408
+ // Get tasrgets that lye within both
1409
+ var similarTargets = showTargets.filter( hideTargets ).add( hideTargets.filter(showTargets) ),
1410
+ toggleEvents = [];
1352
1411
 
1353
- // On mouseleave...
1354
- else {
1355
- // Hide when we leave the tooltip and not onto the show target (if a hide event is set)
1356
- if(options.position.target === 'mouse' && options.hide.event &&
1357
- options.show.target && !target.closest(options.show.target[0]).length) {
1358
- this.hide(event);
1359
- }
1412
+ // If hide and show targets are the same...
1413
+ if(similarTargets.length) {
1414
+
1415
+ // Filter identical show/hide events
1416
+ $.each(hideEvents, function(i, type) {
1417
+ var showIndex = $.inArray(type, showEvents);
1418
+
1419
+ // Both events are identical, remove from both hide and show events
1420
+ // and append to toggleEvents
1421
+ showIndex > -1 && toggleEvents.push( showEvents.splice( showIndex, 1 )[0] );
1422
+ });
1423
+
1424
+ // Toggle events are special case of identical show/hide events, which happen in sequence
1425
+ if(toggleEvents.length) {
1426
+ // Bind toggle events to the similar targets
1427
+ this._bind(similarTargets, toggleEvents, function(event) {
1428
+ var state = this.rendered ? this.tooltip[0].offsetWidth > 0 : false;
1429
+ (state ? hideMethod : showMethod).call(this, event);
1430
+ });
1431
+
1432
+ // Remove the similar targets from the regular show/hide bindings
1433
+ showTargets = showTargets.not(similarTargets);
1434
+ hideTargets = hideTargets.not(similarTargets);
1360
1435
  }
1436
+ }
1361
1437
 
1362
- // Add hover class
1363
- tooltip.toggleClass(CLASS_HOVER, state);
1438
+ // Apply show/hide/toggle events
1439
+ this._bind(showTargets, showEvents, showMethod);
1440
+ this._bind(hideTargets, hideEvents, hideMethod);
1441
+ };
1442
+
1443
+ PROTOTYPE._assignInitialEvents = function(event) {
1444
+ var options = this.options,
1445
+ showTarget = options.show.target,
1446
+ hideTarget = options.hide.target,
1447
+ showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],
1448
+ hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [];
1449
+
1450
+ // Catch remove/removeqtip events on target element to destroy redundant tooltips
1451
+ this._bind(this.elements.target, ['remove', 'removeqtip'], function(event) {
1452
+ this.destroy(true);
1453
+ }, 'destroy');
1454
+
1455
+ /*
1456
+ * Make sure hoverIntent functions properly by using mouseleave as a hide event if
1457
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
1458
+ */
1459
+ if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) {
1460
+ hideEvents.push('mouseleave');
1461
+ }
1462
+
1463
+ /*
1464
+ * Also make sure initial mouse targetting works correctly by caching mousemove coords
1465
+ * on show targets before the tooltip has rendered. Also set onTarget when triggered to
1466
+ * keep mouse tracking working.
1467
+ */
1468
+ this._bind(showTarget, 'mousemove', function(event) {
1469
+ this._storeMouse(event);
1470
+ this.cache.onTarget = TRUE;
1364
1471
  });
1365
1472
 
1366
- // Define events which reset the 'inactive' event handler
1367
- delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod);
1368
- });
1473
+ // Define hoverIntent function
1474
+ function hoverIntent(event) {
1475
+ // Only continue if tooltip isn't disabled
1476
+ if(this.disabled || this.destroyed) { return FALSE; }
1369
1477
 
1370
- // Event trigger
1371
- PROTOTYPE._trigger = function(type, args, event) {
1372
- var callback = $.Event('tooltip'+type);
1373
- callback.originalEvent = (event && $.extend({}, event)) || this.cache.event || NULL;
1478
+ // Cache the event data
1479
+ this.cache.event = event && $.event.fix(event);
1480
+ this.cache.target = event && $(event.target);
1374
1481
 
1375
- this.triggering = TRUE;
1376
- this.tooltip.trigger(callback, [this].concat(args || []));
1377
- this.triggering = FALSE;
1482
+ // Start the event sequence
1483
+ clearTimeout(this.timers.show);
1484
+ this.timers.show = delay.call(this,
1485
+ function() { this.render(typeof event === 'object' || options.show.ready); },
1486
+ options.prerender ? 0 : options.show.delay
1487
+ );
1488
+ }
1378
1489
 
1379
- return !callback.isDefaultPrevented();
1490
+ // Filter and bind events
1491
+ this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, hoverIntent, function() {
1492
+ if(!this.timers) { return FALSE; }
1493
+ clearTimeout(this.timers.show);
1494
+ });
1495
+
1496
+ // Prerendering is enabled, create tooltip now
1497
+ if(options.show.ready || options.prerender) { hoverIntent.call(this, event); }
1380
1498
  };
1381
1499
 
1382
1500
  // Event assignment method
1383
1501
  PROTOTYPE._assignEvents = function() {
1384
- var options = this.options,
1502
+ var self = this,
1503
+ options = this.options,
1385
1504
  posOptions = options.position,
1386
1505
 
1387
1506
  tooltip = this.tooltip,
@@ -1394,8 +1513,13 @@ PROTOTYPE._assignEvents = function() {
1394
1513
  windowTarget = $(window),
1395
1514
 
1396
1515
  showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],
1397
- hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [],
1398
- toggleEvents = [];
1516
+ hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [];
1517
+
1518
+
1519
+ // Assign passed event callbacks
1520
+ $.each(options.events, function(name, callback) {
1521
+ self._bind(tooltip, name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name], callback, null, tooltip);
1522
+ });
1399
1523
 
1400
1524
  // Hide tooltips when leaving current window/frame (but not select/option elements)
1401
1525
  if(/mouse(out|leave)/i.test(options.hide.event) && options.hide.leave === 'window') {
@@ -1439,31 +1563,14 @@ PROTOTYPE._assignEvents = function() {
1439
1563
  // Check if the tooltip hides when inactive
1440
1564
  if('number' === typeof options.hide.inactive) {
1441
1565
  // Bind inactive method to show target(s) as a custom event
1442
- this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod);
1566
+ this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod, 'inactive');
1443
1567
 
1444
1568
  // Define events which reset the 'inactive' event handler
1445
- this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod, '-inactive');
1569
+ this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod);
1446
1570
  }
1447
1571
 
1448
- // Apply hide events (and filter identical show events)
1449
- hideEvents = $.map(hideEvents, function(type) {
1450
- var showIndex = $.inArray(type, showEvents);
1451
-
1452
- // Both events and targets are identical, apply events using a toggle
1453
- if((showIndex > -1 && hideTarget.add(showTarget).length === hideTarget.length)) {
1454
- toggleEvents.push( showEvents.splice( showIndex, 1 )[0] ); return;
1455
- }
1456
-
1457
- return type;
1458
- });
1459
-
1460
- // Apply show/hide/toggle events
1461
- this._bind(showTarget, showEvents, showMethod);
1462
- this._bind(hideTarget, hideEvents, hideMethod);
1463
- this._bind(showTarget, toggleEvents, function(event) {
1464
- (this.tooltip[0].offsetWidth > 0 ? hideMethod : showMethod).call(this, event);
1465
- });
1466
-
1572
+ // Filter and bind events
1573
+ this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, showMethod, hideMethod);
1467
1574
 
1468
1575
  // Mouse movement bindings
1469
1576
  this._bind(showTarget.add(tooltip), 'mousemove', function(event) {
@@ -1491,6 +1598,7 @@ PROTOTYPE._assignEvents = function() {
1491
1598
  if(options.hide.event) {
1492
1599
  // Track if we're on the target or not
1493
1600
  this._bind(showTarget, ['mouseenter', 'mouseleave'], function(event) {
1601
+ if(!this.cache) {return FALSE; }
1494
1602
  this.cache.onTarget = event.type === 'mouseenter';
1495
1603
  });
1496
1604
  }
@@ -1518,31 +1626,70 @@ PROTOTYPE._assignEvents = function() {
1518
1626
 
1519
1627
  // Un-assignment method
1520
1628
  PROTOTYPE._unassignEvents = function() {
1521
- var targets = [
1522
- this.options.show.target[0],
1523
- this.options.hide.target[0],
1524
- this.rendered && this.tooltip[0],
1525
- this.options.position.container[0],
1526
- this.options.position.viewport[0],
1527
- this.options.position.container.closest('html')[0], // unfocus
1528
- window,
1529
- document
1530
- ];
1531
-
1532
- // Check if tooltip is rendered
1533
- if(this.rendered) {
1534
- this._unbind($([]).pushStack( $.grep(targets, function(i) {
1629
+ var options = this.options,
1630
+ showTargets = options.show.target,
1631
+ hideTargets = options.hide.target,
1632
+ targets = $.grep([
1633
+ this.elements.target[0],
1634
+ this.rendered && this.tooltip[0],
1635
+ options.position.container[0],
1636
+ options.position.viewport[0],
1637
+ options.position.container.closest('html')[0], // unfocus
1638
+ window,
1639
+ document
1640
+ ], function(i) {
1535
1641
  return typeof i === 'object';
1536
- })));
1642
+ });
1643
+
1644
+ // Add show and hide targets if they're valid
1645
+ if(showTargets && showTargets.toArray) {
1646
+ targets = targets.concat(showTargets.toArray());
1647
+ }
1648
+ if(hideTargets && hideTargets.toArray) {
1649
+ targets = targets.concat(hideTargets.toArray());
1537
1650
  }
1538
1651
 
1539
- // Tooltip isn't yet rendered, remove render event
1540
- else { $(targets[0]).unbind('.'+this._id+'-create'); }
1652
+ // Unbind the events
1653
+ this._unbind(targets)
1654
+ ._unbind(targets, 'destroy')
1655
+ ._unbind(targets, 'inactive');
1541
1656
  };
1542
1657
 
1658
+ // Apply common event handlers using delegate (avoids excessive .bind calls!)
1659
+ $(function() {
1660
+ delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) {
1661
+ var state = event.type === 'mouseenter',
1662
+ tooltip = $(event.currentTarget),
1663
+ target = $(event.relatedTarget || event.target),
1664
+ options = this.options;
1665
+
1666
+ // On mouseenter...
1667
+ if(state) {
1668
+ // Focus the tooltip on mouseenter (z-index stacking)
1669
+ this.focus(event);
1670
+
1671
+ // Clear hide timer on tooltip hover to prevent it from closing
1672
+ tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide);
1673
+ }
1674
+
1675
+ // On mouseleave...
1676
+ else {
1677
+ // When mouse tracking is enabled, hide when we leave the tooltip and not onto the show target (if a hide event is set)
1678
+ if(options.position.target === 'mouse' && options.position.adjust.mouse &&
1679
+ options.hide.event && options.show.target && !target.closest(options.show.target[0]).length) {
1680
+ this.hide(event);
1681
+ }
1682
+ }
1683
+
1684
+ // Add hover class
1685
+ tooltip.toggleClass(CLASS_HOVER, state);
1686
+ });
1687
+
1688
+ // Define events which reset the 'inactive' event handler
1689
+ delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod);
1690
+ });
1543
1691
  ;// Initialization method
1544
- function init(elem, id, opts)
1545
- {
1692
+ function init(elem, id, opts) {
1546
1693
  var obj, posOptions, attr, config, title,
1547
1694
 
1548
1695
  // Setup element references
@@ -1601,7 +1748,7 @@ function init(elem, id, opts)
1601
1748
  // Destroy previous tooltip if overwrite is enabled, or skip element if not
1602
1749
  if(elem.data(NAMESPACE)) {
1603
1750
  if(config.overwrite) {
1604
- elem.qtip('destroy');
1751
+ elem.qtip('destroy', true);
1605
1752
  }
1606
1753
  else if(config.overwrite === FALSE) {
1607
1754
  return FALSE;
@@ -1621,11 +1768,6 @@ function init(elem, id, opts)
1621
1768
  obj = new QTip(elem, config, id, !!attr);
1622
1769
  elem.data(NAMESPACE, obj);
1623
1770
 
1624
- // Catch remove/removeqtip events on target element to destroy redundant tooltip
1625
- elem.one('remove.qtip-'+id+' removeqtip.qtip-'+id, function() {
1626
- var api; if((api = $(this).data(NAMESPACE))) { api.destroy(); }
1627
- });
1628
-
1629
1771
  return obj;
1630
1772
  }
1631
1773
 
@@ -1644,10 +1786,8 @@ QTIP = $.fn.qtip = function(options, notation, newValue)
1644
1786
  }
1645
1787
 
1646
1788
  // Execute API command if present
1647
- else if('string' === typeof options)
1648
- {
1649
- this.each(function()
1650
- {
1789
+ else if('string' === typeof options) {
1790
+ this.each(function() {
1651
1791
  var api = $.data(this, NAMESPACE);
1652
1792
  if(!api) { return TRUE; }
1653
1793
 
@@ -1675,101 +1815,36 @@ QTIP = $.fn.qtip = function(options, notation, newValue)
1675
1815
  }
1676
1816
 
1677
1817
  // No API commands. validate provided options and setup qTips
1678
- else if('object' === typeof options || !arguments.length)
1679
- {
1818
+ else if('object' === typeof options || !arguments.length) {
1819
+ // Sanitize options first
1680
1820
  opts = sanitizeOptions($.extend(TRUE, {}, options));
1681
1821
 
1682
- // Bind the qTips
1683
- return QTIP.bind.call(this, opts, event);
1684
- }
1685
- };
1686
-
1687
- // $.fn.qtip Bind method
1688
- QTIP.bind = function(opts, event)
1689
- {
1690
- return this.each(function(i) {
1691
- var options, targets, events, namespace, api, id;
1822
+ return this.each(function(i) {
1823
+ var api, id;
1692
1824
 
1693
- // Find next available ID, or use custom ID if provided
1694
- id = $.isArray(opts.id) ? opts.id[i] : opts.id;
1695
- id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id;
1825
+ // Find next available ID, or use custom ID if provided
1826
+ id = $.isArray(opts.id) ? opts.id[i] : opts.id;
1827
+ id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id;
1696
1828
 
1697
- // Setup events namespace
1698
- namespace = '.qtip-'+id+'-create';
1829
+ // Initialize the qTip and re-grab newly sanitized options
1830
+ api = init($(this), id, opts);
1831
+ if(api === FALSE) { return TRUE; }
1832
+ else { QTIP.api[id] = api; }
1699
1833
 
1700
- // Initialize the qTip and re-grab newly sanitized options
1701
- api = init($(this), id, opts);
1702
- if(api === FALSE) { return TRUE; }
1703
- else { QTIP.api[id] = api; }
1704
- options = api.options;
1834
+ // Initialize plugins
1835
+ $.each(PLUGINS, function() {
1836
+ if(this.initialize === 'initialize') { this(api); }
1837
+ });
1705
1838
 
1706
- // Initialize plugins
1707
- $.each(PLUGINS, function() {
1708
- if(this.initialize === 'initialize') { this(api); }
1839
+ // Assign initial pre-render events
1840
+ api._assignInitialEvents(event);
1709
1841
  });
1710
-
1711
- // Determine hide and show targets
1712
- targets = { show: options.show.target, hide: options.hide.target };
1713
- events = {
1714
- show: $.trim('' + options.show.event).replace(/ /g, namespace+' ') + namespace,
1715
- hide: $.trim('' + options.hide.event).replace(/ /g, namespace+' ') + namespace
1716
- };
1717
-
1718
- /*
1719
- * Make sure hoverIntent functions properly by using mouseleave as a hide event if
1720
- * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
1721
- */
1722
- if(/mouse(over|enter)/i.test(events.show) && !/mouse(out|leave)/i.test(events.hide)) {
1723
- events.hide += ' mouseleave' + namespace;
1724
- }
1725
-
1726
- /*
1727
- * Also make sure initial mouse targetting works correctly by caching mousemove coords
1728
- * on show targets before the tooltip has rendered.
1729
- *
1730
- * Also set onTarget when triggered to keep mouse tracking working
1731
- */
1732
- targets.show.bind('mousemove'+namespace, function(event) {
1733
- api._storeMouse(event);
1734
- api.cache.onTarget = TRUE;
1735
- });
1736
-
1737
- // Define hoverIntent function
1738
- function hoverIntent(event) {
1739
- function render() {
1740
- // Cache mouse coords,render and render the tooltip
1741
- api.render(typeof event === 'object' || options.show.ready);
1742
-
1743
- // Unbind show and hide events
1744
- targets.show.add(targets.hide).unbind(namespace);
1745
- }
1746
-
1747
- // Only continue if tooltip isn't disabled
1748
- if(api.disabled) { return FALSE; }
1749
-
1750
- // Cache the event data
1751
- api.cache.event = $.extend({}, event);
1752
- api.cache.target = event ? $(event.target) : [undefined];
1753
-
1754
- // Start the event sequence
1755
- if(options.show.delay > 0) {
1756
- clearTimeout(api.timers.show);
1757
- api.timers.show = setTimeout(render, options.show.delay);
1758
- if(events.show !== events.hide) {
1759
- targets.hide.bind(events.hide, function() { clearTimeout(api.timers.show); });
1760
- }
1761
- }
1762
- else { render(); }
1763
- }
1764
-
1765
- // Bind show events to target
1766
- targets.show.bind(events.show, hoverIntent);
1767
-
1768
- // Prerendering is enabled, create tooltip now
1769
- if(options.show.ready || options.prerender) { hoverIntent(event); }
1770
- });
1842
+ }
1771
1843
  };
1772
1844
 
1845
+ // Expose class
1846
+ $.qtip = QTip;
1847
+
1773
1848
  // Populated in render method
1774
1849
  QTIP.api = {};
1775
1850
  ;$.each({
@@ -1833,16 +1908,15 @@ if(!$.ui) {
1833
1908
  $.cleanData = function( elems ) {
1834
1909
  for(var i = 0, elem; (elem = $( elems[i] )).length; i++) {
1835
1910
  if(elem.attr(ATTR_HAS)) {
1836
- try { elem.triggerHandler('removeqtip'); }
1911
+ try { elem.triggerHandler('removeqtip'); }
1837
1912
  catch( e ) {}
1838
1913
  }
1839
1914
  }
1840
1915
  $['cleanData'+replaceSuffix].apply(this, arguments);
1841
1916
  };
1842
1917
  }
1843
-
1844
1918
  ;// qTip version
1845
- QTIP.version = '2.1.1';
1919
+ QTIP.version = '2.2.1';
1846
1920
 
1847
1921
  // Base ID for all qTips
1848
1922
  QTIP.nextid = 0;
@@ -1923,7 +1997,6 @@ QTIP.defaults = {
1923
1997
  blur: NULL
1924
1998
  }
1925
1999
  };
1926
-
1927
2000
  ;var TIP,
1928
2001
 
1929
2002
  // .bind()/.on() namespace
@@ -1969,18 +2042,28 @@ function vendorCss(elem, prop) {
1969
2042
 
1970
2043
  // Parse a given elements CSS property into an int
1971
2044
  function intCss(elem, prop) {
1972
- return parseInt(vendorCss(elem, prop), 10);
2045
+ return Math.ceil(parseFloat(vendorCss(elem, prop)));
1973
2046
  }
1974
2047
 
1975
2048
 
1976
2049
  // VML creation (for IE only)
1977
2050
  if(!HASCANVAS) {
1978
- createVML = function(tag, props, style) {
2051
+ var createVML = function(tag, props, style) {
1979
2052
  return '<qtipvml:'+tag+' xmlns="urn:schemas-microsoft.com:vml" class="qtip-vml" '+(props||'')+
1980
2053
  ' style="behavior: url(#default#VML); '+(style||'')+ '" />';
1981
2054
  };
1982
2055
  }
1983
2056
 
2057
+ // Canvas only definitions
2058
+ else {
2059
+ var PIXEL_RATIO = window.devicePixelRatio || 1,
2060
+ BACKING_STORE_RATIO = (function() {
2061
+ var context = document.createElement('canvas').getContext('2d');
2062
+ return context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio ||
2063
+ context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || 1;
2064
+ }()),
2065
+ SCALE = PIXEL_RATIO / BACKING_STORE_RATIO;
2066
+ }
1984
2067
 
1985
2068
 
1986
2069
  function Tip(qtip, options) {
@@ -2007,7 +2090,7 @@ $.extend(Tip.prototype, {
2007
2090
 
2008
2091
  // Setup constant parameters
2009
2092
  context.lineJoin = 'miter';
2010
- context.miterLimit = 100;
2093
+ context.miterLimit = 100000;
2011
2094
  context.save();
2012
2095
  }
2013
2096
  else {
@@ -2066,7 +2149,7 @@ $.extend(Tip.prototype, {
2066
2149
  return (use ? intCss(use, prop) : (
2067
2150
  intCss(elements.content, prop) ||
2068
2151
  intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||
2069
- intCss(tooltip, prop)
2152
+ intCss(elements.tooltip, prop)
2070
2153
  )) || 0;
2071
2154
  },
2072
2155
 
@@ -2075,7 +2158,7 @@ $.extend(Tip.prototype, {
2075
2158
  prop = BORDER + camel(corner.y) + camel(corner.x) + 'Radius';
2076
2159
 
2077
2160
  return BROWSER.ie < 9 ? 0 :
2078
- intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||
2161
+ intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||
2079
2162
  intCss(elements.tooltip, prop) || 0;
2080
2163
  },
2081
2164
 
@@ -2092,12 +2175,12 @@ $.extend(Tip.prototype, {
2092
2175
  css = this._invalidColour, color = [];
2093
2176
 
2094
2177
  // Attempt to detect the background colour from various elements, left-to-right precedance
2095
- color[0] = css(tip, BG_COLOR) || css(colorElem, BG_COLOR) || css(elements.content, BG_COLOR) ||
2096
- css(tooltip, BG_COLOR) || tip.css(BG_COLOR);
2178
+ color[0] = css(tip, BG_COLOR) || css(colorElem, BG_COLOR) || css(elements.content, BG_COLOR) ||
2179
+ css(elements.tooltip, BG_COLOR) || tip.css(BG_COLOR);
2097
2180
 
2098
2181
  // Attempt to detect the correct border side colour from various elements, left-to-right precedance
2099
- color[1] = css(tip, borderSide, COLOR) || css(colorElem, borderSide, COLOR) ||
2100
- css(elements.content, borderSide, COLOR) || css(tooltip, borderSide, COLOR) || tooltip.css(borderSide);
2182
+ color[1] = css(tip, borderSide, COLOR) || css(colorElem, borderSide, COLOR) ||
2183
+ css(elements.content, borderSide, COLOR) || css(elements.tooltip, borderSide, COLOR) || elements.tooltip.css(borderSide);
2101
2184
 
2102
2185
  // Reset background and border colours
2103
2186
  $('*', tip).add(tip).css('cssText', BG_COLOR+':'+TRANSPARENT+IMPORTANT+';'+BORDER+':0'+IMPORTANT+';');
@@ -2107,10 +2190,10 @@ $.extend(Tip.prototype, {
2107
2190
 
2108
2191
  _calculateSize: function(corner) {
2109
2192
  var y = corner.precedance === Y,
2110
- width = this.options[ y ? 'height' : 'width' ],
2111
- height = this.options[ y ? 'width' : 'height' ],
2193
+ width = this.options['width'],
2194
+ height = this.options['height'],
2112
2195
  isCenter = corner.abbrev() === 'c',
2113
- base = width * (isCenter ? 0.5 : 1),
2196
+ base = (y ? width: height) * (isCenter ? 0.5 : 1),
2114
2197
  pow = Math.pow,
2115
2198
  round = Math.round,
2116
2199
  bigHyp, ratio, result,
@@ -2125,13 +2208,16 @@ $.extend(Tip.prototype, {
2125
2208
  ratio = bigHyp / smallHyp;
2126
2209
 
2127
2210
  result = [ round(ratio * width), round(ratio * height) ];
2128
-
2129
2211
  return y ? result : result.reverse();
2130
2212
  },
2131
2213
 
2132
2214
  // Tip coordinates calculator
2133
- _calculateTip: function(corner) {
2134
- var width = this.size[0], height = this.size[1],
2215
+ _calculateTip: function(corner, size, scale) {
2216
+ scale = scale || 1;
2217
+ size = size || this.size;
2218
+
2219
+ var width = size[0] * scale,
2220
+ height = size[1] * scale,
2135
2221
  width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2),
2136
2222
 
2137
2223
  // Define tip coordinates in terms of height and width values
@@ -2153,10 +2239,19 @@ $.extend(Tip.prototype, {
2153
2239
  return tips[ corner.abbrev() ];
2154
2240
  },
2155
2241
 
2242
+ // Tip coordinates drawer (canvas)
2243
+ _drawCoords: function(context, coords) {
2244
+ context.beginPath();
2245
+ context.moveTo(coords[0], coords[1]);
2246
+ context.lineTo(coords[2], coords[3]);
2247
+ context.lineTo(coords[4], coords[5]);
2248
+ context.closePath();
2249
+ },
2250
+
2156
2251
  create: function() {
2157
2252
  // Determine tip corner
2158
2253
  var c = this.corner = (HASCANVAS || BROWSER.ie) && this._parseCorner(this.options.corner);
2159
-
2254
+
2160
2255
  // If we have a tip corner...
2161
2256
  if( (this.enabled = !!this.corner && this.corner.abbrev() !== 'c') ) {
2162
2257
  // Cache it
@@ -2179,11 +2274,11 @@ $.extend(Tip.prototype, {
2179
2274
  tip = this.element,
2180
2275
  inner = tip.children(),
2181
2276
  options = this.options,
2182
- size = this.size,
2277
+ curSize = this.size,
2183
2278
  mimic = options.mimic,
2184
2279
  round = Math.round,
2185
2280
  color, precedance, context,
2186
- coords, translate, newSize, border;
2281
+ coords, bigCoords, translate, newSize, border, BACKING_STORE_RATIO;
2187
2282
 
2188
2283
  // Re-determine tip if not already set
2189
2284
  if(!corner) { corner = this.qtip.cache.corner || this.corner; }
@@ -2216,8 +2311,8 @@ $.extend(Tip.prototype, {
2216
2311
  // Grab border width
2217
2312
  border = this.border = this._parseWidth(corner, corner[corner.precedance]);
2218
2313
 
2219
- // If border width isn't zero, use border color as fill (1.0 style tips)
2220
- if(options.border && border < 1) { color[0] = color[1]; }
2314
+ // If border width isn't zero, use border color as fill if it's not invalid (1.0 style tips)
2315
+ if(options.border && border < 1 && !INVALID.test(color[1])) { color[0] = color[1]; }
2221
2316
 
2222
2317
  // Set border width (use detected border width if options.border is true)
2223
2318
  this.border = border = options.border !== TRUE ? options.border : border;
@@ -2226,9 +2321,6 @@ $.extend(Tip.prototype, {
2226
2321
  // Border colour was invalid, set border to zero
2227
2322
  else { this.border = border = 0; }
2228
2323
 
2229
- // Calculate coordinates
2230
- coords = this._calculateTip(mimic);
2231
-
2232
2324
  // Determine tip size
2233
2325
  newSize = this.size = this._calculateSize(corner);
2234
2326
  tip.css({
@@ -2240,79 +2332,72 @@ $.extend(Tip.prototype, {
2240
2332
  // Calculate tip translation
2241
2333
  if(corner.precedance === Y) {
2242
2334
  translate = [
2243
- round(mimic.x === LEFT ? border : mimic.x === RIGHT ? newSize[0] - size[0] - border : (newSize[0] - size[0]) / 2),
2244
- round(mimic.y === TOP ? newSize[1] - size[1] : 0)
2335
+ round(mimic.x === LEFT ? border : mimic.x === RIGHT ? newSize[0] - curSize[0] - border : (newSize[0] - curSize[0]) / 2),
2336
+ round(mimic.y === TOP ? newSize[1] - curSize[1] : 0)
2245
2337
  ];
2246
2338
  }
2247
2339
  else {
2248
2340
  translate = [
2249
- round(mimic.x === LEFT ? newSize[0] - size[0] : 0),
2250
- round(mimic.y === TOP ? border : mimic.y === BOTTOM ? newSize[1] - size[1] - border : (newSize[1] - size[1]) / 2)
2341
+ round(mimic.x === LEFT ? newSize[0] - curSize[0] : 0),
2342
+ round(mimic.y === TOP ? border : mimic.y === BOTTOM ? newSize[1] - curSize[1] - border : (newSize[1] - curSize[1]) / 2)
2251
2343
  ];
2252
2344
  }
2253
2345
 
2254
2346
  // Canvas drawing implementation
2255
2347
  if(HASCANVAS) {
2256
- // Set the canvas size using calculated size
2257
- inner.attr(WIDTH, newSize[0]).attr(HEIGHT, newSize[1]);
2258
-
2259
2348
  // Grab canvas context and clear/save it
2260
2349
  context = inner[0].getContext('2d');
2261
2350
  context.restore(); context.save();
2262
- context.clearRect(0,0,3000,3000);
2351
+ context.clearRect(0,0,6000,6000);
2352
+
2353
+ // Calculate coordinates
2354
+ coords = this._calculateTip(mimic, curSize, SCALE);
2355
+ bigCoords = this._calculateTip(mimic, this.size, SCALE);
2356
+
2357
+ // Set the canvas size using calculated size
2358
+ inner.attr(WIDTH, newSize[0] * SCALE).attr(HEIGHT, newSize[1] * SCALE);
2359
+ inner.css(WIDTH, newSize[0]).css(HEIGHT, newSize[1]);
2360
+
2361
+ // Draw the outer-stroke tip
2362
+ this._drawCoords(context, bigCoords);
2363
+ context.fillStyle = color[1];
2364
+ context.fill();
2263
2365
 
2264
- // Set properties
2366
+ // Draw the actual tip
2367
+ context.translate(translate[0] * SCALE, translate[1] * SCALE);
2368
+ this._drawCoords(context, coords);
2265
2369
  context.fillStyle = color[0];
2266
- context.strokeStyle = color[1];
2267
- context.lineWidth = border * 2;
2268
-
2269
- // Draw the tip
2270
- context.translate(translate[0], translate[1]);
2271
- context.beginPath();
2272
- context.moveTo(coords[0], coords[1]);
2273
- context.lineTo(coords[2], coords[3]);
2274
- context.lineTo(coords[4], coords[5]);
2275
- context.closePath();
2276
-
2277
- // Apply fill and border
2278
- if(border) {
2279
- // Make sure transparent borders are supported by doing a stroke
2280
- // of the background colour before the stroke colour
2281
- if(tooltip.css('background-clip') === 'border-box') {
2282
- context.strokeStyle = color[0];
2283
- context.stroke();
2284
- }
2285
- context.strokeStyle = color[1];
2286
- context.stroke();
2287
- }
2288
2370
  context.fill();
2289
2371
  }
2290
2372
 
2291
2373
  // VML (IE Proprietary implementation)
2292
2374
  else {
2375
+ // Calculate coordinates
2376
+ coords = this._calculateTip(mimic);
2377
+
2293
2378
  // Setup coordinates string
2294
2379
  coords = 'm' + coords[0] + ',' + coords[1] + ' l' + coords[2] +
2295
2380
  ',' + coords[3] + ' ' + coords[4] + ',' + coords[5] + ' xe';
2296
2381
 
2297
2382
  // Setup VML-specific offset for pixel-perfection
2298
- translate[2] = border && /^(r|b)/i.test(corner.string()) ?
2383
+ translate[2] = border && /^(r|b)/i.test(corner.string()) ?
2299
2384
  BROWSER.ie === 8 ? 2 : 1 : 0;
2300
2385
 
2301
2386
  // Set initial CSS
2302
2387
  inner.css({
2303
- coordsize: (size[0]+border) + ' ' + (size[1]+border),
2388
+ coordsize: (newSize[0]+border) + ' ' + (newSize[1]+border),
2304
2389
  antialias: ''+(mimic.string().indexOf(CENTER) > -1),
2305
2390
  left: translate[0] - (translate[2] * Number(precedance === X)),
2306
2391
  top: translate[1] - (translate[2] * Number(precedance === Y)),
2307
- width: size[0] + border,
2308
- height: size[1] + border
2392
+ width: newSize[0] + border,
2393
+ height: newSize[1] + border
2309
2394
  })
2310
2395
  .each(function(i) {
2311
2396
  var $this = $(this);
2312
2397
 
2313
2398
  // Set shape specific attributes
2314
2399
  $this[ $this.prop ? 'prop' : 'attr' ]({
2315
- coordsize: (size[0]+border) + ' ' + (size[1]+border),
2400
+ coordsize: (newSize[0]+border) + ' ' + (newSize[1]+border),
2316
2401
  path: coords,
2317
2402
  fillcolor: color[0],
2318
2403
  filled: !!i,
@@ -2327,27 +2412,36 @@ $.extend(Tip.prototype, {
2327
2412
  });
2328
2413
  }
2329
2414
 
2415
+ // Opera bug #357 - Incorrect tip position
2416
+ // https://github.com/Craga89/qTip2/issues/367
2417
+ window.opera && setTimeout(function() {
2418
+ elements.tip.css({
2419
+ display: 'inline-block',
2420
+ visibility: 'visible'
2421
+ });
2422
+ }, 1);
2423
+
2330
2424
  // Position if needed
2331
- if(position !== FALSE) { this.calculate(corner); }
2425
+ if(position !== FALSE) { this.calculate(corner, newSize); }
2332
2426
  },
2333
2427
 
2334
- calculate: function(corner) {
2428
+ calculate: function(corner, size) {
2335
2429
  if(!this.enabled) { return FALSE; }
2336
2430
 
2337
2431
  var self = this,
2338
2432
  elements = this.qtip.elements,
2339
2433
  tip = this.element,
2340
2434
  userOffset = this.options.offset,
2341
- isWidget = this.qtip.tooltip.hasClass('ui-widget'),
2435
+ isWidget = elements.tooltip.hasClass('ui-widget'),
2342
2436
  position = { },
2343
- precedance, size, corners;
2437
+ precedance, corners;
2344
2438
 
2345
2439
  // Inherit corner if not provided
2346
2440
  corner = corner || this.corner;
2347
2441
  precedance = corner.precedance;
2348
2442
 
2349
2443
  // Determine which tip dimension to use for adjustment
2350
- size = this._calculateSize(corner);
2444
+ size = size || this._calculateSize(corner);
2351
2445
 
2352
2446
  // Setup corners and offset array
2353
2447
  corners = [ corner.x, corner.y ];
@@ -2391,71 +2485,57 @@ $.extend(Tip.prototype, {
2391
2485
  shift = { left: FALSE, top: FALSE, x: 0, y: 0 },
2392
2486
  offset, css = {}, props;
2393
2487
 
2394
- // If our tip position isn't fixed e.g. doesn't adjust with viewport...
2395
- if(this.corner.fixed !== TRUE) {
2488
+ function shiftflip(direction, precedance, popposite, side, opposite) {
2396
2489
  // Horizontal - Shift or flip method
2397
- if(horizontal === SHIFT && newCorner.precedance === X && adjust.left && newCorner.y !== CENTER) {
2490
+ if(direction === SHIFT && newCorner.precedance === precedance && adjust[side] && newCorner[popposite] !== CENTER) {
2398
2491
  newCorner.precedance = newCorner.precedance === X ? Y : X;
2399
2492
  }
2400
- else if(horizontal !== SHIFT && adjust.left){
2401
- newCorner.x = newCorner.x === CENTER ? (adjust.left > 0 ? LEFT : RIGHT) : (newCorner.x === LEFT ? RIGHT : LEFT);
2493
+ else if(direction !== SHIFT && adjust[side]){
2494
+ newCorner[precedance] = newCorner[precedance] === CENTER ?
2495
+ (adjust[side] > 0 ? side : opposite) : (newCorner[precedance] === side ? opposite : side);
2402
2496
  }
2497
+ }
2403
2498
 
2404
- // Vertical - Shift or flip method
2405
- if(vertical === SHIFT && newCorner.precedance === Y && adjust.top && newCorner.x !== CENTER) {
2406
- newCorner.precedance = newCorner.precedance === Y ? X : Y;
2499
+ function shiftonly(xy, side, opposite) {
2500
+ if(newCorner[xy] === CENTER) {
2501
+ css[MARGIN+'-'+side] = shift[xy] = offset[MARGIN+'-'+side] - adjust[side];
2407
2502
  }
2408
- else if(vertical !== SHIFT && adjust.top) {
2409
- newCorner.y = newCorner.y === CENTER ? (adjust.top > 0 ? TOP : BOTTOM) : (newCorner.y === TOP ? BOTTOM : TOP);
2503
+ else {
2504
+ props = offset[opposite] !== undefined ?
2505
+ [ adjust[side], -offset[side] ] : [ -adjust[side], offset[side] ];
2506
+
2507
+ if( (shift[xy] = Math.max(props[0], props[1])) > props[0] ) {
2508
+ pos[side] -= adjust[side];
2509
+ shift[side] = FALSE;
2510
+ }
2511
+
2512
+ css[ offset[opposite] !== undefined ? opposite : side ] = shift[xy];
2410
2513
  }
2514
+ }
2515
+
2516
+ // If our tip position isn't fixed e.g. doesn't adjust with viewport...
2517
+ if(this.corner.fixed !== TRUE) {
2518
+ // Perform shift/flip adjustments
2519
+ shiftflip(horizontal, X, Y, LEFT, RIGHT);
2520
+ shiftflip(vertical, Y, X, TOP, BOTTOM);
2411
2521
 
2412
2522
  // Update and redraw the tip if needed (check cached details of last drawn tip)
2413
- if(newCorner.string() !== cache.corner.string() && (cache.cornerTop !== adjust.top || cache.cornerLeft !== adjust.left)) {
2523
+ if(newCorner.string() !== cache.corner.string() || cache.cornerTop !== adjust.top || cache.cornerLeft !== adjust.left) {
2414
2524
  this.update(newCorner, FALSE);
2415
2525
  }
2416
2526
  }
2417
2527
 
2418
2528
  // Setup tip offset properties
2419
- offset = this.calculate(newCorner, adjust);
2529
+ offset = this.calculate(newCorner);
2420
2530
 
2421
2531
  // Readjust offset object to make it left/top
2422
2532
  if(offset.right !== undefined) { offset.left = -offset.right; }
2423
2533
  if(offset.bottom !== undefined) { offset.top = -offset.bottom; }
2424
2534
  offset.user = this.offset;
2425
2535
 
2426
- // Viewport "shift" specific adjustments
2427
- if(shift.left = (horizontal === SHIFT && !!adjust.left)) {
2428
- if(newCorner.x === CENTER) {
2429
- css[MARGIN+'-left'] = shift.x = offset[MARGIN+'-left'] - adjust.left;
2430
- }
2431
- else {
2432
- props = offset.right !== undefined ?
2433
- [ adjust.left, -offset.left ] : [ -adjust.left, offset.left ];
2434
-
2435
- if( (shift.x = Math.max(props[0], props[1])) > props[0] ) {
2436
- pos.left -= adjust.left;
2437
- shift.left = FALSE;
2438
- }
2439
-
2440
- css[ offset.right !== undefined ? RIGHT : LEFT ] = shift.x;
2441
- }
2442
- }
2443
- if(shift.top = (vertical === SHIFT && !!adjust.top)) {
2444
- if(newCorner.y === CENTER) {
2445
- css[MARGIN+'-top'] = shift.y = offset[MARGIN+'-top'] - adjust.top;
2446
- }
2447
- else {
2448
- props = offset.bottom !== undefined ?
2449
- [ adjust.top, -offset.top ] : [ -adjust.top, offset.top ];
2450
-
2451
- if( (shift.y = Math.max(props[0], props[1])) > props[0] ) {
2452
- pos.top -= adjust.top;
2453
- shift.top = FALSE;
2454
- }
2455
-
2456
- css[ offset.bottom !== undefined ? BOTTOM : TOP ] = shift.y;
2457
- }
2458
- }
2536
+ // Perform shift adjustments
2537
+ if(shift.left = (horizontal === SHIFT && !!adjust.left)) { shiftonly(X, LEFT, RIGHT); }
2538
+ if(shift.top = (vertical === SHIFT && !!adjust.top)) { shiftonly(Y, TOP, BOTTOM); }
2459
2539
 
2460
2540
  /*
2461
2541
  * If the tip is adjusted in both dimensions, or in a
@@ -2467,8 +2547,10 @@ $.extend(Tip.prototype, {
2467
2547
  );
2468
2548
 
2469
2549
  // Adjust position to accomodate tip dimensions
2470
- pos.left -= offset.left.charAt ? offset.user : horizontal !== SHIFT || shift.top || !shift.left && !shift.top ? offset.left : 0;
2471
- pos.top -= offset.top.charAt ? offset.user : vertical !== SHIFT || shift.left || !shift.left && !shift.top ? offset.top : 0;
2550
+ pos.left -= offset.left.charAt ? offset.user :
2551
+ horizontal !== SHIFT || shift.top || !shift.left && !shift.top ? offset.left + this.border : 0;
2552
+ pos.top -= offset.top.charAt ? offset.user :
2553
+ vertical !== SHIFT || shift.left || !shift.left && !shift.top ? offset.top + this.border : 0;
2472
2554
 
2473
2555
  // Cache details
2474
2556
  cache.cornerLeft = adjust.left; cache.cornerTop = adjust.top;
@@ -2497,7 +2579,7 @@ TIP.initialize = 'render';
2497
2579
  // Setup plugin sanitization options
2498
2580
  TIP.sanitize = function(options) {
2499
2581
  if(options.style && 'tip' in options.style) {
2500
- opts = options.style.tip;
2582
+ var opts = options.style.tip;
2501
2583
  if(typeof opts !== 'object') { opts = options.style.tip = { corner: opts }; }
2502
2584
  if(!(/string|boolean/i).test(typeof opts.corner)) { opts.corner = TRUE; }
2503
2585
  }
@@ -2508,13 +2590,13 @@ CHECKS.tip = {
2508
2590
  '^position.my|style.tip.(corner|mimic|border)$': function() {
2509
2591
  // Make sure a tip can be drawn
2510
2592
  this.create();
2511
-
2593
+
2512
2594
  // Reposition the tooltip
2513
2595
  this.qtip.reposition();
2514
2596
  },
2515
2597
  '^style.tip.(height|width)$': function(obj) {
2516
2598
  // Re-set dimensions and redraw the tip
2517
- this.size = size = [ obj.width, obj.height ];
2599
+ this.size = [ obj.width, obj.height ];
2518
2600
  this.update();
2519
2601
 
2520
2602
  // Reposition the tooltip
@@ -2538,727 +2620,697 @@ $.extend(TRUE, QTIP.defaults, {
2538
2620
  }
2539
2621
  }
2540
2622
  });
2541
-
2542
- ;var MODAL, OVERLAY,
2543
- MODALCLASS = 'qtip-modal',
2544
- MODALSELECTOR = '.'+MODALCLASS;
2545
-
2546
- OVERLAY = function()
2623
+ ;PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight)
2547
2624
  {
2548
- var self = this,
2549
- focusableElems = {},
2550
- current, onLast,
2551
- prevState, elem;
2552
-
2553
- // Modified code from jQuery UI 1.10.0 source
2554
- // http://code.jquery.com/ui/1.10.0/jquery-ui.js
2555
- function focusable(element) {
2556
- // Use the defined focusable checker when possible
2557
- if($.expr[':'].focusable) { return $.expr[':'].focusable; }
2558
-
2559
- var isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex')),
2560
- nodeName = element.nodeName && element.nodeName.toLowerCase(),
2561
- map, mapName, img;
2625
+ var target = posOptions.target,
2626
+ tooltip = api.elements.tooltip,
2627
+ my = posOptions.my,
2628
+ at = posOptions.at,
2629
+ adjust = posOptions.adjust,
2630
+ method = adjust.method.split(' '),
2631
+ methodX = method[0],
2632
+ methodY = method[1] || method[0],
2633
+ viewport = posOptions.viewport,
2634
+ container = posOptions.container,
2635
+ cache = api.cache,
2636
+ adjusted = { left: 0, top: 0 },
2637
+ fixed, newMy, containerOffset, containerStatic,
2638
+ viewportWidth, viewportHeight, viewportScroll, viewportOffset;
2562
2639
 
2563
- if('area' === nodeName) {
2564
- map = element.parentNode;
2565
- mapName = map.name;
2566
- if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') {
2567
- return false;
2568
- }
2569
- img = $('img[usemap=#' + mapName + ']')[0];
2570
- return !!img && img.is(':visible');
2571
- }
2572
- return (/input|select|textarea|button|object/.test( nodeName ) ?
2573
- !element.disabled :
2574
- 'a' === nodeName ?
2575
- element.href || isTabIndexNotNaN :
2576
- isTabIndexNotNaN
2577
- );
2640
+ // If viewport is not a jQuery element, or it's the window/document, or no adjustment method is used... return
2641
+ if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') {
2642
+ return adjusted;
2578
2643
  }
2579
2644
 
2580
- // Focus inputs using cached focusable elements (see update())
2581
- function focusInputs(blurElems) {
2582
- // Blurring body element in IE causes window.open windows to unfocus!
2583
- if(focusableElems.length < 1 && blurElems.length) { blurElems.not('body').blur(); }
2645
+ // Cach container details
2646
+ containerOffset = container.offset() || adjusted;
2647
+ containerStatic = container.css('position') === 'static';
2584
2648
 
2585
- // Focus the inputs
2586
- else { focusableElems.first().focus(); }
2587
- }
2649
+ // Cache our viewport details
2650
+ fixed = tooltip.css('position') === 'fixed';
2651
+ viewportWidth = viewport[0] === window ? viewport.width() : viewport.outerWidth(FALSE);
2652
+ viewportHeight = viewport[0] === window ? viewport.height() : viewport.outerHeight(FALSE);
2653
+ viewportScroll = { left: fixed ? 0 : viewport.scrollLeft(), top: fixed ? 0 : viewport.scrollTop() };
2654
+ viewportOffset = viewport.offset() || adjusted;
2588
2655
 
2589
- // Steal focus from elements outside tooltip
2590
- function stealFocus(event) {
2591
- if(!elem.is(':visible')) { return; }
2656
+ // Generic calculation method
2657
+ function calculate(side, otherSide, type, adjust, side1, side2, lengthName, targetLength, elemLength) {
2658
+ var initialPos = position[side1],
2659
+ mySide = my[side],
2660
+ atSide = at[side],
2661
+ isShift = type === SHIFT,
2662
+ myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2,
2663
+ atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2,
2664
+ sideOffset = viewportScroll[side1] + viewportOffset[side1] - (containerStatic ? 0 : containerOffset[side1]),
2665
+ overflow1 = sideOffset - initialPos,
2666
+ overflow2 = initialPos + elemLength - (lengthName === WIDTH ? viewportWidth : viewportHeight) - sideOffset,
2667
+ offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0);
2592
2668
 
2593
- var target = $(event.target),
2594
- tooltip = current.tooltip,
2595
- container = target.closest(SELECTOR),
2596
- targetOnTop;
2669
+ // shift
2670
+ if(isShift) {
2671
+ offset = (mySide === side1 ? 1 : -1) * myLength;
2597
2672
 
2598
- // Determine if input container target is above this
2599
- targetOnTop = container.length < 1 ? FALSE :
2600
- (parseInt(container[0].style.zIndex, 10) > parseInt(tooltip[0].style.zIndex, 10));
2673
+ // Adjust position but keep it within viewport dimensions
2674
+ position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0;
2675
+ position[side1] = Math.max(
2676
+ -containerOffset[side1] + viewportOffset[side1],
2677
+ initialPos - offset,
2678
+ Math.min(
2679
+ Math.max(
2680
+ -containerOffset[side1] + viewportOffset[side1] + (lengthName === WIDTH ? viewportWidth : viewportHeight),
2681
+ initialPos + offset
2682
+ ),
2683
+ position[side1],
2684
+
2685
+ // Make sure we don't adjust complete off the element when using 'center'
2686
+ mySide === 'center' ? initialPos - myLength : 1E9
2687
+ )
2688
+ );
2601
2689
 
2602
- // If we're showing a modal, but focus has landed on an input below
2603
- // this modal, divert focus to the first visible input in this modal
2604
- // or if we can't find one... the tooltip itself
2605
- if(!targetOnTop && target.closest(SELECTOR)[0] !== tooltip[0]) {
2606
- focusInputs(target);
2607
2690
  }
2608
2691
 
2609
- // Detect when we leave the last focusable element...
2610
- onLast = event.target === focusableElems[focusableElems.length - 1];
2611
- }
2612
-
2613
- $.extend(self, {
2614
- init: function() {
2615
- // Create document overlay
2616
- elem = self.elem = $('<div />', {
2617
- id: 'qtip-overlay',
2618
- html: '<div></div>',
2619
- mousedown: function() { return FALSE; }
2620
- })
2621
- .hide();
2692
+ // flip/flipinvert
2693
+ else {
2694
+ // Update adjustment amount depending on if using flipinvert or flip
2695
+ adjust *= (type === FLIPINVERT ? 2 : 0);
2622
2696
 
2623
- // Update position on window resize or scroll
2624
- function resize() {
2625
- var win = $(this);
2626
- elem.css({
2627
- height: win.height(),
2628
- width: win.width()
2629
- });
2697
+ // Check for overflow on the left/top
2698
+ if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) {
2699
+ position[side1] -= offset + adjust;
2700
+ newMy.invert(side, side1);
2630
2701
  }
2631
- $(window).bind('resize'+MODALSELECTOR, resize);
2632
- resize(); // Fire it initially too
2633
2702
 
2634
- // Make sure we can't focus anything outside the tooltip
2635
- $(document.body).bind('focusin'+MODALSELECTOR, stealFocus);
2703
+ // Check for overflow on the bottom/right
2704
+ else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0) ) {
2705
+ position[side1] -= (mySide === CENTER ? -offset : offset) + adjust;
2706
+ newMy.invert(side, side2);
2707
+ }
2636
2708
 
2637
- // Apply keyboard "Escape key" close handler
2638
- $(document).bind('keydown'+MODALSELECTOR, function(event) {
2639
- if(current && current.options.show.modal.escape && event.keyCode === 27) {
2640
- current.hide(event);
2641
- }
2642
- });
2709
+ // Make sure we haven't made things worse with the adjustment and reset if so
2710
+ if(position[side1] < viewportScroll && -position[side1] > overflow2) {
2711
+ position[side1] = initialPos; newMy = my.clone();
2712
+ }
2713
+ }
2643
2714
 
2644
- // Apply click handler for blur option
2645
- elem.bind('click'+MODALSELECTOR, function(event) {
2646
- if(current && current.options.show.modal.blur) {
2647
- current.hide(event);
2648
- }
2649
- });
2715
+ return position[side1] - initialPos;
2716
+ }
2650
2717
 
2651
- return self;
2652
- },
2718
+ // Set newMy if using flip or flipinvert methods
2719
+ if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); }
2653
2720
 
2654
- update: function(api) {
2655
- // Update current API reference
2656
- current = api;
2721
+ // Adjust position based onviewport and adjustment options
2722
+ adjusted = {
2723
+ left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0,
2724
+ top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 0,
2725
+ my: newMy
2726
+ };
2657
2727
 
2658
- // Update focusable elements if enabled
2659
- if(api.options.show.modal.stealfocus !== FALSE) {
2660
- focusableElems = api.tooltip.find('*').filter(function() {
2661
- return focusable(this);
2662
- });
2663
- }
2664
- else { focusableElems = []; }
2728
+ return adjusted;
2729
+ };
2730
+ ;PLUGINS.polys = {
2731
+ // POLY area coordinate calculator
2732
+ // Special thanks to Ed Cradock for helping out with this.
2733
+ // Uses a binary search algorithm to find suitable coordinates.
2734
+ polygon: function(baseCoords, corner) {
2735
+ var result = {
2736
+ width: 0, height: 0,
2737
+ position: {
2738
+ top: 1e10, right: 0,
2739
+ bottom: 0, left: 1e10
2740
+ },
2741
+ adjustable: FALSE
2665
2742
  },
2743
+ i = 0, next,
2744
+ coords = [],
2745
+ compareX = 1, compareY = 1,
2746
+ realX = 0, realY = 0,
2747
+ newWidth, newHeight;
2666
2748
 
2667
- toggle: function(api, state, duration) {
2668
- var docBody = $(document.body),
2669
- tooltip = api.tooltip,
2670
- options = api.options.show.modal,
2671
- effect = options.effect,
2672
- type = state ? 'show': 'hide',
2673
- visible = elem.is(':visible'),
2674
- visibleModals = $(MODALSELECTOR).filter(':visible:not(:animated)').not(tooltip),
2675
- zindex;
2676
-
2677
- // Set active tooltip API reference
2678
- self.update(api);
2749
+ // First pass, sanitize coords and determine outer edges
2750
+ i = baseCoords.length; while(i--) {
2751
+ next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ];
2679
2752
 
2680
- // If the modal can steal the focus...
2681
- // Blur the current item and focus anything in the modal we an
2682
- if(state && options.stealfocus !== FALSE) {
2683
- focusInputs( $(':focus') );
2684
- }
2753
+ if(next[0] > result.position.right){ result.position.right = next[0]; }
2754
+ if(next[0] < result.position.left){ result.position.left = next[0]; }
2755
+ if(next[1] > result.position.bottom){ result.position.bottom = next[1]; }
2756
+ if(next[1] < result.position.top){ result.position.top = next[1]; }
2685
2757
 
2686
- // Toggle backdrop cursor style on show
2687
- elem.toggleClass('blurs', options.blur);
2758
+ coords.push(next);
2759
+ }
2688
2760
 
2689
- // Set position and append to body on show
2690
- if(state) {
2691
- elem.css({ left: 0, top: 0 })
2692
- .appendTo(document.body);
2693
- }
2761
+ // Calculate height and width from outer edges
2762
+ newWidth = result.width = Math.abs(result.position.right - result.position.left);
2763
+ newHeight = result.height = Math.abs(result.position.bottom - result.position.top);
2694
2764
 
2695
- // Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible
2696
- if((elem.is(':animated') && visible === state && prevState !== FALSE) || (!state && visibleModals.length)) {
2697
- return self;
2698
- }
2765
+ // If it's the center corner...
2766
+ if(corner.abbrev() === 'c') {
2767
+ result.position = {
2768
+ left: result.position.left + (result.width / 2),
2769
+ top: result.position.top + (result.height / 2)
2770
+ };
2771
+ }
2772
+ else {
2773
+ // Second pass, use a binary search algorithm to locate most suitable coordinate
2774
+ while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0)
2775
+ {
2776
+ newWidth = Math.floor(newWidth / 2);
2777
+ newHeight = Math.floor(newHeight / 2);
2699
2778
 
2700
- // Stop all animations
2701
- elem.stop(TRUE, FALSE);
2779
+ if(corner.x === LEFT){ compareX = newWidth; }
2780
+ else if(corner.x === RIGHT){ compareX = result.width - newWidth; }
2781
+ else{ compareX += Math.floor(newWidth / 2); }
2702
2782
 
2703
- // Use custom function if provided
2704
- if($.isFunction(effect)) {
2705
- effect.call(elem, state);
2706
- }
2783
+ if(corner.y === TOP){ compareY = newHeight; }
2784
+ else if(corner.y === BOTTOM){ compareY = result.height - newHeight; }
2785
+ else{ compareY += Math.floor(newHeight / 2); }
2707
2786
 
2708
- // If no effect type is supplied, use a simple toggle
2709
- else if(effect === FALSE) {
2710
- elem[ type ]();
2711
- }
2787
+ i = coords.length; while(i--)
2788
+ {
2789
+ if(coords.length < 2){ break; }
2712
2790
 
2713
- // Use basic fade function
2714
- else {
2715
- elem.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() {
2716
- if(!state) { elem.hide(); }
2717
- });
2718
- }
2791
+ realX = coords[i][0] - result.position.left;
2792
+ realY = coords[i][1] - result.position.top;
2719
2793
 
2720
- // Reset position and detach from body on hide
2721
- if(!state) {
2722
- elem.queue(function(next) {
2723
- elem.css({ left: '', top: '' });
2724
- if(!$(MODALSELECTOR).length) { elem.detach(); }
2725
- next();
2726
- });
2794
+ if((corner.x === LEFT && realX >= compareX) ||
2795
+ (corner.x === RIGHT && realX <= compareX) ||
2796
+ (corner.x === CENTER && (realX < compareX || realX > (result.width - compareX))) ||
2797
+ (corner.y === TOP && realY >= compareY) ||
2798
+ (corner.y === BOTTOM && realY <= compareY) ||
2799
+ (corner.y === CENTER && (realY < compareY || realY > (result.height - compareY)))) {
2800
+ coords.splice(i, 1);
2801
+ }
2802
+ }
2727
2803
  }
2804
+ result.position = { left: coords[0][0], top: coords[0][1] };
2805
+ }
2728
2806
 
2729
- // Cache the state
2730
- prevState = state;
2807
+ return result;
2808
+ },
2731
2809
 
2732
- // If the tooltip is destroyed, set reference to null
2733
- if(current.destroyed) { current = NULL; }
2810
+ rect: function(ax, ay, bx, by) {
2811
+ return {
2812
+ width: Math.abs(bx - ax),
2813
+ height: Math.abs(by - ay),
2814
+ position: {
2815
+ left: Math.min(ax, bx),
2816
+ top: Math.min(ay, by)
2817
+ }
2818
+ };
2819
+ },
2734
2820
 
2735
- return self;
2736
- }
2737
- });
2821
+ _angles: {
2822
+ tc: 3 / 2, tr: 7 / 4, tl: 5 / 4,
2823
+ bc: 1 / 2, br: 1 / 4, bl: 3 / 4,
2824
+ rc: 2, lc: 1, c: 0
2825
+ },
2826
+ ellipse: function(cx, cy, rx, ry, corner) {
2827
+ var c = PLUGINS.polys._angles[ corner.abbrev() ],
2828
+ rxc = c === 0 ? 0 : rx * Math.cos( c * Math.PI ),
2829
+ rys = ry * Math.sin( c * Math.PI );
2738
2830
 
2739
- self.init();
2831
+ return {
2832
+ width: (rx * 2) - Math.abs(rxc),
2833
+ height: (ry * 2) - Math.abs(rys),
2834
+ position: {
2835
+ left: cx + rxc,
2836
+ top: cy + rys
2837
+ },
2838
+ adjustable: FALSE
2839
+ };
2840
+ },
2841
+ circle: function(cx, cy, r, corner) {
2842
+ return PLUGINS.polys.ellipse(cx, cy, r, r, corner);
2843
+ }
2740
2844
  };
2741
- OVERLAY = new OVERLAY();
2845
+ ;PLUGINS.imagemap = function(api, area, corner, adjustMethod)
2846
+ {
2847
+ if(!area.jquery) { area = $(area); }
2742
2848
 
2743
- function Modal(api, options) {
2744
- this.options = options;
2745
- this._ns = '-modal';
2849
+ var shape = (area.attr('shape') || 'rect').toLowerCase().replace('poly', 'polygon'),
2850
+ image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'),
2851
+ coordsString = $.trim(area.attr('coords')),
2852
+ coordsArray = coordsString.replace(/,$/, '').split(','),
2853
+ imageOffset, coords, i, next, result, len;
2746
2854
 
2747
- this.init( (this.qtip = api) );
2748
- }
2855
+ // If we can't find the image using the map...
2856
+ if(!image.length) { return FALSE; }
2749
2857
 
2750
- $.extend(Modal.prototype, {
2751
- init: function(qtip) {
2752
- var tooltip = qtip.tooltip;
2858
+ // Pass coordinates string if polygon
2859
+ if(shape === 'polygon') {
2860
+ result = PLUGINS.polys.polygon(coordsArray, corner);
2861
+ }
2753
2862
 
2754
- // If modal is disabled... return
2755
- if(!this.options.on) { return this; }
2863
+ // Otherwise parse the coordinates and pass them as arguments
2864
+ else if(PLUGINS.polys[shape]) {
2865
+ for(i = -1, len = coordsArray.length, coords = []; ++i < len;) {
2866
+ coords.push( parseInt(coordsArray[i], 10) );
2867
+ }
2756
2868
 
2757
- // Set overlay reference
2758
- qtip.elements.overlay = OVERLAY.elem;
2869
+ result = PLUGINS.polys[shape].apply(
2870
+ this, coords.concat(corner)
2871
+ );
2872
+ }
2759
2873
 
2760
- // Add unique attribute so we can grab modal tooltips easily via a SELECTOR, and set z-index
2761
- tooltip.addClass(MODALCLASS).css('z-index', PLUGINS.modal.zindex + $(MODALSELECTOR).length);
2762
-
2763
- // Apply our show/hide/focus modal events
2764
- qtip._bind(tooltip, ['tooltipshow', 'tooltiphide'], function(event, api, duration) {
2765
- var oEvent = event.originalEvent;
2874
+ // If no shapre calculation method was found, return false
2875
+ else { return FALSE; }
2766
2876
 
2767
- // Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop
2768
- if(event.target === tooltip[0]) {
2769
- if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(overlay[0]).length) {
2770
- try { event.preventDefault(); } catch(e) {}
2771
- }
2772
- else if(!oEvent || (oEvent && !oEvent.solo)) {
2773
- this.toggle(event, event.type === 'tooltipshow', duration);
2774
- }
2775
- }
2776
- }, this._ns, this);
2877
+ // Make sure we account for padding and borders on the image
2878
+ imageOffset = image.offset();
2879
+ imageOffset.left += Math.ceil((image.outerWidth(FALSE) - image.width()) / 2);
2880
+ imageOffset.top += Math.ceil((image.outerHeight(FALSE) - image.height()) / 2);
2777
2881
 
2778
- // Adjust modal z-index on tooltip focus
2779
- qtip._bind(tooltip, 'tooltipfocus', function(event, api) {
2780
- // If focus was cancelled before it reached us, don't do anything
2781
- if(event.isDefaultPrevented() || event.target !== tooltip[0]) { return; }
2882
+ // Add image position to offset coordinates
2883
+ result.position.left += imageOffset.left;
2884
+ result.position.top += imageOffset.top;
2782
2885
 
2783
- var qtips = $(MODALSELECTOR),
2886
+ return result;
2887
+ };
2888
+ ;PLUGINS.svg = function(api, svg, corner)
2889
+ {
2890
+ var doc = $(document),
2891
+ elem = svg[0],
2892
+ root = $(elem.ownerSVGElement),
2893
+ ownerDocument = elem.ownerDocument,
2894
+ strokeWidth2 = (parseInt(svg.css('stroke-width'), 10) || 0) / 2,
2895
+ frameOffset, mtx, transformed, viewBox,
2896
+ len, next, i, points,
2897
+ result, position, dimensions;
2784
2898
 
2785
- // Keep the modal's lower than other, regular qtips
2786
- newIndex = PLUGINS.modal.zindex + qtips.length,
2787
- curIndex = parseInt(tooltip[0].style.zIndex, 10);
2899
+ // Ascend the parentNode chain until we find an element with getBBox()
2900
+ while(!elem.getBBox) { elem = elem.parentNode; }
2901
+ if(!elem.getBBox || !elem.parentNode) { return FALSE; }
2788
2902
 
2789
- // Set overlay z-index
2790
- OVERLAY.elem[0].style.zIndex = newIndex - 1;
2903
+ // Determine which shape calculation to use
2904
+ switch(elem.nodeName) {
2905
+ case 'ellipse':
2906
+ case 'circle':
2907
+ result = PLUGINS.polys.ellipse(
2908
+ elem.cx.baseVal.value,
2909
+ elem.cy.baseVal.value,
2910
+ (elem.rx || elem.r).baseVal.value + strokeWidth2,
2911
+ (elem.ry || elem.r).baseVal.value + strokeWidth2,
2912
+ corner
2913
+ );
2914
+ break;
2791
2915
 
2792
- // Reduce modal z-index's and keep them properly ordered
2793
- qtips.each(function() {
2794
- if(this.style.zIndex > curIndex) {
2795
- this.style.zIndex -= 1;
2916
+ case 'line':
2917
+ case 'polygon':
2918
+ case 'polyline':
2919
+ // Determine points object (line has none, so mimic using array)
2920
+ points = elem.points || [
2921
+ { x: elem.x1.baseVal.value, y: elem.y1.baseVal.value },
2922
+ { x: elem.x2.baseVal.value, y: elem.y2.baseVal.value }
2923
+ ];
2924
+
2925
+ for(result = [], i = -1, len = points.numberOfItems || points.length; ++i < len;) {
2926
+ next = points.getItem ? points.getItem(i) : points[i];
2927
+ result.push.apply(result, [next.x, next.y]);
2928
+ }
2929
+
2930
+ result = PLUGINS.polys.polygon(result, corner);
2931
+ break;
2932
+
2933
+ // Unknown shape or rectangle? Use bounding box
2934
+ default:
2935
+ result = elem.getBBox();
2936
+ result = {
2937
+ width: result.width,
2938
+ height: result.height,
2939
+ position: {
2940
+ left: result.x,
2941
+ top: result.y
2796
2942
  }
2797
- });
2943
+ };
2944
+ break;
2945
+ }
2798
2946
 
2799
- // Fire blur event for focused tooltip
2800
- qtips.filter('.' + CLASS_FOCUS).qtip('blur', event.originalEvent);
2947
+ // Shortcut assignments
2948
+ position = result.position;
2949
+ root = root[0];
2801
2950
 
2802
- // Set the new z-index
2803
- tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;
2951
+ // Convert position into a pixel value
2952
+ if(root.createSVGPoint) {
2953
+ mtx = elem.getScreenCTM();
2954
+ points = root.createSVGPoint();
2804
2955
 
2805
- // Set current
2806
- OVERLAY.update(api);
2956
+ points.x = position.left;
2957
+ points.y = position.top;
2958
+ transformed = points.matrixTransform( mtx );
2959
+ position.left = transformed.x;
2960
+ position.top = transformed.y;
2961
+ }
2807
2962
 
2808
- // Prevent default handling
2809
- try { event.preventDefault(); } catch(e) {}
2810
- }, this._ns, this);
2963
+ // Check the element is not in a child document, and if so, adjust for frame elements offset
2964
+ if(ownerDocument !== document && api.position.target !== 'mouse') {
2965
+ frameOffset = $((ownerDocument.defaultView || ownerDocument.parentWindow).frameElement).offset();
2966
+ if(frameOffset) {
2967
+ position.left += frameOffset.left;
2968
+ position.top += frameOffset.top;
2969
+ }
2970
+ }
2811
2971
 
2812
- // Focus any other visible modals when this one hides
2813
- qtip._bind(tooltip, 'tooltiphide', function(event) {
2814
- if(event.target === tooltip[0]) {
2815
- $(MODALSELECTOR).filter(':visible').not(tooltip).last().qtip('focus', event);
2816
- }
2817
- }, this._ns, this);
2818
- },
2972
+ // Adjust by scroll offset of owner document
2973
+ ownerDocument = $(ownerDocument);
2974
+ position.left += ownerDocument.scrollLeft();
2975
+ position.top += ownerDocument.scrollTop();
2819
2976
 
2820
- toggle: function(event, state, duration) {
2821
- // Make sure default event hasn't been prevented
2822
- if(event && event.isDefaultPrevented()) { return this; }
2977
+ return result;
2978
+ };
2979
+ ;var MODAL, OVERLAY,
2980
+ MODALCLASS = 'qtip-modal',
2981
+ MODALSELECTOR = '.'+MODALCLASS;
2823
2982
 
2824
- // Toggle it
2825
- OVERLAY.toggle(this.qtip, !!state, duration);
2826
- },
2983
+ OVERLAY = function()
2984
+ {
2985
+ var self = this,
2986
+ focusableElems = {},
2987
+ current, onLast,
2988
+ prevState, elem;
2827
2989
 
2828
- destroy: function() {
2829
- // Remove modal class
2830
- this.qtip.tooltip.removeClass(MODALCLASS);
2990
+ // Modified code from jQuery UI 1.10.0 source
2991
+ // http://code.jquery.com/ui/1.10.0/jquery-ui.js
2992
+ function focusable(element) {
2993
+ // Use the defined focusable checker when possible
2994
+ if($.expr[':'].focusable) { return $.expr[':'].focusable; }
2831
2995
 
2832
- // Remove bound events
2833
- this.qtip._unbind(this.qtip.tooltip, this._ns);
2996
+ var isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex')),
2997
+ nodeName = element.nodeName && element.nodeName.toLowerCase(),
2998
+ map, mapName, img;
2834
2999
 
2835
- // Delete element reference
2836
- OVERLAY.toggle(this.qtip, FALSE);
2837
- delete this.qtip.elements.overlay;
3000
+ if('area' === nodeName) {
3001
+ map = element.parentNode;
3002
+ mapName = map.name;
3003
+ if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') {
3004
+ return false;
3005
+ }
3006
+ img = $('img[usemap=#' + mapName + ']')[0];
3007
+ return !!img && img.is(':visible');
3008
+ }
3009
+ return (/input|select|textarea|button|object/.test( nodeName ) ?
3010
+ !element.disabled :
3011
+ 'a' === nodeName ?
3012
+ element.href || isTabIndexNotNaN :
3013
+ isTabIndexNotNaN
3014
+ );
2838
3015
  }
2839
- });
2840
-
2841
3016
 
2842
- MODAL = PLUGINS.modal = function(api) {
2843
- return new Modal(api, api.options.show.modal);
2844
- };
3017
+ // Focus inputs using cached focusable elements (see update())
3018
+ function focusInputs(blurElems) {
3019
+ // Blurring body element in IE causes window.open windows to unfocus!
3020
+ if(focusableElems.length < 1 && blurElems.length) { blurElems.not('body').blur(); }
2845
3021
 
2846
- // Setup sanitiztion rules
2847
- MODAL.sanitize = function(opts) {
2848
- if(opts.show) {
2849
- if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; }
2850
- else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; }
3022
+ // Focus the inputs
3023
+ else { focusableElems.first().focus(); }
2851
3024
  }
2852
- };
2853
3025
 
2854
- // Base z-index for all modal tooltips (use qTip core z-index as a base)
2855
- MODAL.zindex = QTIP.zindex - 200;
3026
+ // Steal focus from elements outside tooltip
3027
+ function stealFocus(event) {
3028
+ if(!elem.is(':visible')) { return; }
2856
3029
 
2857
- // Plugin needs to be initialized on render
2858
- MODAL.initialize = 'render';
3030
+ var target = $(event.target),
3031
+ tooltip = current.tooltip,
3032
+ container = target.closest(SELECTOR),
3033
+ targetOnTop;
2859
3034
 
2860
- // Setup option set checks
2861
- CHECKS.modal = {
2862
- '^show.modal.(on|blur)$': function() {
2863
- // Initialise
2864
- this.destroy();
2865
- this.init();
2866
-
2867
- // Show the modal if not visible already and tooltip is visible
2868
- this.qtip.elems.overlay.toggle(
2869
- this.qtip.tooltip[0].offsetWidth > 0
2870
- );
2871
- }
2872
- };
3035
+ // Determine if input container target is above this
3036
+ targetOnTop = container.length < 1 ? FALSE :
3037
+ (parseInt(container[0].style.zIndex, 10) > parseInt(tooltip[0].style.zIndex, 10));
2873
3038
 
2874
- // Extend original api defaults
2875
- $.extend(TRUE, QTIP.defaults, {
2876
- show: {
2877
- modal: {
2878
- on: FALSE,
2879
- effect: TRUE,
2880
- blur: TRUE,
2881
- stealfocus: TRUE,
2882
- escape: TRUE
3039
+ // If we're showing a modal, but focus has landed on an input below
3040
+ // this modal, divert focus to the first visible input in this modal
3041
+ // or if we can't find one... the tooltip itself
3042
+ if(!targetOnTop && target.closest(SELECTOR)[0] !== tooltip[0]) {
3043
+ focusInputs(target);
2883
3044
  }
2884
- }
2885
- });
2886
- ;PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight)
2887
- {
2888
- var target = posOptions.target,
2889
- tooltip = api.elements.tooltip,
2890
- my = posOptions.my,
2891
- at = posOptions.at,
2892
- adjust = posOptions.adjust,
2893
- method = adjust.method.split(' '),
2894
- methodX = method[0],
2895
- methodY = method[1] || method[0],
2896
- viewport = posOptions.viewport,
2897
- container = posOptions.container,
2898
- cache = api.cache,
2899
- tip = api.plugins.tip,
2900
- adjusted = { left: 0, top: 0 },
2901
- fixed, newMy, newClass;
2902
3045
 
2903
- // If viewport is not a jQuery element, or it's the window/document or no adjustment method is used... return
2904
- if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') {
2905
- return adjusted;
3046
+ // Detect when we leave the last focusable element...
3047
+ onLast = event.target === focusableElems[focusableElems.length - 1];
2906
3048
  }
2907
3049
 
2908
- // Cache our viewport details
2909
- fixed = tooltip.css('position') === 'fixed';
2910
- viewport = {
2911
- elem: viewport,
2912
- width: viewport[0] === window ? viewport.width() : viewport.outerWidth(FALSE),
2913
- height: viewport[0] === window ? viewport.height() : viewport.outerHeight(FALSE),
2914
- scrollleft: fixed ? 0 : viewport.scrollLeft(),
2915
- scrolltop: fixed ? 0 : viewport.scrollTop(),
2916
- offset: viewport.offset() || { left: 0, top: 0 }
2917
- };
2918
- container = {
2919
- elem: container,
2920
- scrollLeft: container.scrollLeft(),
2921
- scrollTop: container.scrollTop(),
2922
- offset: container.offset() || { left: 0, top: 0 }
2923
- };
3050
+ $.extend(self, {
3051
+ init: function() {
3052
+ // Create document overlay
3053
+ elem = self.elem = $('<div />', {
3054
+ id: 'qtip-overlay',
3055
+ html: '<div></div>',
3056
+ mousedown: function() { return FALSE; }
3057
+ })
3058
+ .hide();
2924
3059
 
2925
- // Generic calculation method
2926
- function calculate(side, otherSide, type, adjust, side1, side2, lengthName, targetLength, elemLength) {
2927
- var initialPos = position[side1],
2928
- mySide = my[side], atSide = at[side],
2929
- isShift = type === SHIFT,
2930
- viewportScroll = -container.offset[side1] + viewport.offset[side1] + viewport['scroll'+side1],
2931
- myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2,
2932
- atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2,
2933
- tipLength = tip && tip.size ? tip.size[lengthName] || 0 : 0,
2934
- tipAdjust = tip && tip.corner && tip.corner.precedance === side && !isShift ? tipLength : 0,
2935
- overflow1 = viewportScroll - initialPos + tipAdjust,
2936
- overflow2 = initialPos + elemLength - viewport[lengthName] - viewportScroll + tipAdjust,
2937
- offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0);
3060
+ // Make sure we can't focus anything outside the tooltip
3061
+ $(document.body).bind('focusin'+MODALSELECTOR, stealFocus);
2938
3062
 
2939
- // shift
2940
- if(isShift) {
2941
- tipAdjust = tip && tip.corner && tip.corner.precedance === otherSide ? tipLength : 0;
2942
- offset = (mySide === side1 ? 1 : -1) * myLength - tipAdjust;
3063
+ // Apply keyboard "Escape key" close handler
3064
+ $(document).bind('keydown'+MODALSELECTOR, function(event) {
3065
+ if(current && current.options.show.modal.escape && event.keyCode === 27) {
3066
+ current.hide(event);
3067
+ }
3068
+ });
2943
3069
 
2944
- // Adjust position but keep it within viewport dimensions
2945
- position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0;
2946
- position[side1] = Math.max(
2947
- -container.offset[side1] + viewport.offset[side1] + (tipAdjust && tip.corner[side] === CENTER ? tip.offset : 0),
2948
- initialPos - offset,
2949
- Math.min(
2950
- Math.max(-container.offset[side1] + viewport.offset[side1] + viewport[lengthName], initialPos + offset),
2951
- position[side1]
2952
- )
2953
- );
2954
- }
3070
+ // Apply click handler for blur option
3071
+ elem.bind('click'+MODALSELECTOR, function(event) {
3072
+ if(current && current.options.show.modal.blur) {
3073
+ current.hide(event);
3074
+ }
3075
+ });
2955
3076
 
2956
- // flip/flipinvert
2957
- else {
2958
- // Update adjustment amount depending on if using flipinvert or flip
2959
- adjust *= (type === FLIPINVERT ? 2 : 0);
3077
+ return self;
3078
+ },
2960
3079
 
2961
- // Check for overflow on the left/top
2962
- if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) {
2963
- position[side1] -= offset + adjust;
2964
- newMy.invert(side, side1);
3080
+ update: function(api) {
3081
+ // Update current API reference
3082
+ current = api;
3083
+
3084
+ // Update focusable elements if enabled
3085
+ if(api.options.show.modal.stealfocus !== FALSE) {
3086
+ focusableElems = api.tooltip.find('*').filter(function() {
3087
+ return focusable(this);
3088
+ });
2965
3089
  }
3090
+ else { focusableElems = []; }
3091
+ },
2966
3092
 
2967
- // Check for overflow on the bottom/right
2968
- else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0) ) {
2969
- position[side1] -= (mySide === CENTER ? -offset : offset) + adjust;
2970
- newMy.invert(side, side2);
3093
+ toggle: function(api, state, duration) {
3094
+ var docBody = $(document.body),
3095
+ tooltip = api.tooltip,
3096
+ options = api.options.show.modal,
3097
+ effect = options.effect,
3098
+ type = state ? 'show': 'hide',
3099
+ visible = elem.is(':visible'),
3100
+ visibleModals = $(MODALSELECTOR).filter(':visible:not(:animated)').not(tooltip),
3101
+ zindex;
3102
+
3103
+ // Set active tooltip API reference
3104
+ self.update(api);
3105
+
3106
+ // If the modal can steal the focus...
3107
+ // Blur the current item and focus anything in the modal we an
3108
+ if(state && options.stealfocus !== FALSE) {
3109
+ focusInputs( $(':focus') );
2971
3110
  }
2972
3111
 
2973
- // Make sure we haven't made things worse with the adjustment and reset if so
2974
- if(position[side1] < viewportScroll && -position[side1] > overflow2) {
2975
- position[side1] = initialPos; newMy = my.clone();
3112
+ // Toggle backdrop cursor style on show
3113
+ elem.toggleClass('blurs', options.blur);
3114
+
3115
+ // Append to body on show
3116
+ if(state) {
3117
+ elem.appendTo(document.body);
2976
3118
  }
2977
- }
2978
3119
 
2979
- return position[side1] - initialPos;
2980
- }
3120
+ // Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible
3121
+ if((elem.is(':animated') && visible === state && prevState !== FALSE) || (!state && visibleModals.length)) {
3122
+ return self;
3123
+ }
2981
3124
 
2982
- // Set newMy if using flip or flipinvert methods
2983
- if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); }
3125
+ // Stop all animations
3126
+ elem.stop(TRUE, FALSE);
2984
3127
 
2985
- // Adjust position based onviewport and adjustment options
2986
- adjusted = {
2987
- left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0,
2988
- top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 0
2989
- };
3128
+ // Use custom function if provided
3129
+ if($.isFunction(effect)) {
3130
+ effect.call(elem, state);
3131
+ }
2990
3132
 
2991
- // Set tooltip position class if it's changed
2992
- if(newMy && cache.lastClass !== (newClass = NAMESPACE + '-pos-' + newMy.abbrev())) {
2993
- tooltip.removeClass(api.cache.lastClass).addClass( (api.cache.lastClass = newClass) );
2994
- }
3133
+ // If no effect type is supplied, use a simple toggle
3134
+ else if(effect === FALSE) {
3135
+ elem[ type ]();
3136
+ }
2995
3137
 
2996
- return adjusted;
2997
- };;PLUGINS.polys = {
2998
- // POLY area coordinate calculator
2999
- // Special thanks to Ed Cradock for helping out with this.
3000
- // Uses a binary search algorithm to find suitable coordinates.
3001
- polygon: function(baseCoords, corner) {
3002
- var result = {
3003
- width: 0, height: 0,
3004
- position: {
3005
- top: 1e10, right: 0,
3006
- bottom: 0, left: 1e10
3007
- },
3008
- adjustable: FALSE
3009
- },
3010
- i = 0, next,
3011
- coords = [],
3012
- compareX = 1, compareY = 1,
3013
- realX = 0, realY = 0,
3014
- newWidth, newHeight;
3138
+ // Use basic fade function
3139
+ else {
3140
+ elem.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() {
3141
+ if(!state) { elem.hide(); }
3142
+ });
3143
+ }
3015
3144
 
3016
- // First pass, sanitize coords and determine outer edges
3017
- i = baseCoords.length; while(i--) {
3018
- next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ];
3145
+ // Reset position and detach from body on hide
3146
+ if(!state) {
3147
+ elem.queue(function(next) {
3148
+ elem.css({ left: '', top: '' });
3149
+ if(!$(MODALSELECTOR).length) { elem.detach(); }
3150
+ next();
3151
+ });
3152
+ }
3019
3153
 
3020
- if(next[0] > result.position.right){ result.position.right = next[0]; }
3021
- if(next[0] < result.position.left){ result.position.left = next[0]; }
3022
- if(next[1] > result.position.bottom){ result.position.bottom = next[1]; }
3023
- if(next[1] < result.position.top){ result.position.top = next[1]; }
3154
+ // Cache the state
3155
+ prevState = state;
3024
3156
 
3025
- coords.push(next);
3157
+ // If the tooltip is destroyed, set reference to null
3158
+ if(current.destroyed) { current = NULL; }
3159
+
3160
+ return self;
3026
3161
  }
3162
+ });
3027
3163
 
3028
- // Calculate height and width from outer edges
3029
- newWidth = result.width = Math.abs(result.position.right - result.position.left);
3030
- newHeight = result.height = Math.abs(result.position.bottom - result.position.top);
3164
+ self.init();
3165
+ };
3166
+ OVERLAY = new OVERLAY();
3031
3167
 
3032
- // If it's the center corner...
3033
- if(corner.abbrev() === 'c') {
3034
- result.position = {
3035
- left: result.position.left + (result.width / 2),
3036
- top: result.position.top + (result.height / 2)
3037
- };
3038
- }
3039
- else {
3040
- // Second pass, use a binary search algorithm to locate most suitable coordinate
3041
- while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0)
3042
- {
3043
- newWidth = Math.floor(newWidth / 2);
3044
- newHeight = Math.floor(newHeight / 2);
3168
+ function Modal(api, options) {
3169
+ this.options = options;
3170
+ this._ns = '-modal';
3045
3171
 
3046
- if(corner.x === LEFT){ compareX = newWidth; }
3047
- else if(corner.x === RIGHT){ compareX = result.width - newWidth; }
3048
- else{ compareX += Math.floor(newWidth / 2); }
3172
+ this.init( (this.qtip = api) );
3173
+ }
3049
3174
 
3050
- if(corner.y === TOP){ compareY = newHeight; }
3051
- else if(corner.y === BOTTOM){ compareY = result.height - newHeight; }
3052
- else{ compareY += Math.floor(newHeight / 2); }
3175
+ $.extend(Modal.prototype, {
3176
+ init: function(qtip) {
3177
+ var tooltip = qtip.tooltip;
3053
3178
 
3054
- i = coords.length; while(i--)
3055
- {
3056
- if(coords.length < 2){ break; }
3179
+ // If modal is disabled... return
3180
+ if(!this.options.on) { return this; }
3057
3181
 
3058
- realX = coords[i][0] - result.position.left;
3059
- realY = coords[i][1] - result.position.top;
3182
+ // Set overlay reference
3183
+ qtip.elements.overlay = OVERLAY.elem;
3060
3184
 
3061
- if((corner.x === LEFT && realX >= compareX) ||
3062
- (corner.x === RIGHT && realX <= compareX) ||
3063
- (corner.x === CENTER && (realX < compareX || realX > (result.width - compareX))) ||
3064
- (corner.y === TOP && realY >= compareY) ||
3065
- (corner.y === BOTTOM && realY <= compareY) ||
3066
- (corner.y === CENTER && (realY < compareY || realY > (result.height - compareY)))) {
3067
- coords.splice(i, 1);
3068
- }
3069
- }
3070
- }
3071
- result.position = { left: coords[0][0], top: coords[0][1] };
3072
- }
3185
+ // Add unique attribute so we can grab modal tooltips easily via a SELECTOR, and set z-index
3186
+ tooltip.addClass(MODALCLASS).css('z-index', QTIP.modal_zindex + $(MODALSELECTOR).length);
3073
3187
 
3074
- return result;
3075
- },
3188
+ // Apply our show/hide/focus modal events
3189
+ qtip._bind(tooltip, ['tooltipshow', 'tooltiphide'], function(event, api, duration) {
3190
+ var oEvent = event.originalEvent;
3076
3191
 
3077
- rect: function(ax, ay, bx, by, corner) {
3078
- return {
3079
- width: Math.abs(bx - ax),
3080
- height: Math.abs(by - ay),
3081
- position: {
3082
- left: Math.min(ax, bx),
3083
- top: Math.min(ay, by)
3192
+ // Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop
3193
+ if(event.target === tooltip[0]) {
3194
+ if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(OVERLAY.elem[0]).length) {
3195
+ try { event.preventDefault(); } catch(e) {}
3196
+ }
3197
+ else if(!oEvent || (oEvent && oEvent.type !== 'tooltipsolo')) {
3198
+ this.toggle(event, event.type === 'tooltipshow', duration);
3199
+ }
3084
3200
  }
3085
- };
3086
- },
3201
+ }, this._ns, this);
3087
3202
 
3088
- _angles: {
3089
- tc: 3 / 2, tr: 7 / 4, tl: 5 / 4,
3090
- bc: 1 / 2, br: 1 / 4, bl: 3 / 4,
3091
- rc: 2, lc: 1, c: 0
3092
- },
3093
- ellipse: function(cx, cy, rx, ry, corner) {
3094
- var c = PLUGINS.polys._angles[ corner.abbrev() ],
3095
- rxc = rx * Math.cos( c * Math.PI ),
3096
- rys = ry * Math.sin( c * Math.PI );
3203
+ // Adjust modal z-index on tooltip focus
3204
+ qtip._bind(tooltip, 'tooltipfocus', function(event, api) {
3205
+ // If focus was cancelled before it reached us, don't do anything
3206
+ if(event.isDefaultPrevented() || event.target !== tooltip[0]) { return; }
3097
3207
 
3098
- return {
3099
- width: (rx * 2) - Math.abs(rxc),
3100
- height: (ry * 2) - Math.abs(rys),
3101
- position: {
3102
- left: cx + rxc,
3103
- top: cy + rys
3104
- },
3105
- adjustable: FALSE
3106
- };
3107
- },
3108
- circle: function(cx, cy, r, corner) {
3109
- return PLUGINS.polys.ellipse(cx, cy, r, r, corner);
3110
- }
3111
- };;PLUGINS.svg = function(api, svg, corner, adjustMethod)
3112
- {
3113
- var doc = $(document),
3114
- elem = svg[0],
3115
- result = {},
3116
- name, box, position, dimensions;
3208
+ var qtips = $(MODALSELECTOR),
3117
3209
 
3118
- // Ascend the parentNode chain until we find an element with getBBox()
3119
- while(!elem.getBBox) { elem = elem.parentNode; }
3120
- if(!elem.getBBox || !elem.parentNode) { return FALSE; }
3210
+ // Keep the modal's lower than other, regular qtips
3211
+ newIndex = QTIP.modal_zindex + qtips.length,
3212
+ curIndex = parseInt(tooltip[0].style.zIndex, 10);
3121
3213
 
3122
- // Determine which shape calculation to use
3123
- switch(elem.nodeName) {
3124
- case 'rect':
3125
- position = PLUGINS.svg.toPixel(elem, elem.x.baseVal.value, elem.y.baseVal.value);
3126
- dimensions = PLUGINS.svg.toPixel(elem,
3127
- elem.x.baseVal.value + elem.width.baseVal.value,
3128
- elem.y.baseVal.value + elem.height.baseVal.value
3129
- );
3214
+ // Set overlay z-index
3215
+ OVERLAY.elem[0].style.zIndex = newIndex - 1;
3130
3216
 
3131
- result = PLUGINS.polys.rect(
3132
- position[0], position[1],
3133
- dimensions[0], dimensions[1],
3134
- corner
3135
- );
3136
- break;
3217
+ // Reduce modal z-index's and keep them properly ordered
3218
+ qtips.each(function() {
3219
+ if(this.style.zIndex > curIndex) {
3220
+ this.style.zIndex -= 1;
3221
+ }
3222
+ });
3137
3223
 
3138
- case 'ellipse':
3139
- case 'circle':
3140
- position = PLUGINS.svg.toPixel(elem,
3141
- elem.cx.baseVal.value,
3142
- elem.cy.baseVal.value
3143
- );
3224
+ // Fire blur event for focused tooltip
3225
+ qtips.filter('.' + CLASS_FOCUS).qtip('blur', event.originalEvent);
3144
3226
 
3145
- result = PLUGINS.polys.ellipse(
3146
- position[0], position[1],
3147
- (elem.rx || elem.r).baseVal.value,
3148
- (elem.ry || elem.r).baseVal.value,
3149
- corner
3150
- );
3151
- break;
3227
+ // Set the new z-index
3228
+ tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;
3152
3229
 
3153
- case 'line':
3154
- case 'polygon':
3155
- case 'polyline':
3156
- points = elem.points || [
3157
- { x: elem.x1.baseVal.value, y: elem.y1.baseVal.value },
3158
- { x: elem.x2.baseVal.value, y: elem.y2.baseVal.value }
3159
- ];
3230
+ // Set current
3231
+ OVERLAY.update(api);
3160
3232
 
3161
- for(result = [], i = -1, len = points.numberOfItems || points.length; ++i < len;) {
3162
- next = points.getItem ? points.getItem(i) : points[i];
3163
- result.push.apply(result, PLUGINS.svg.toPixel(elem, next.x, next.y));
3164
- }
3233
+ // Prevent default handling
3234
+ try { event.preventDefault(); } catch(e) {}
3235
+ }, this._ns, this);
3165
3236
 
3166
- result = PLUGINS.polys.polygon(result, corner);
3167
- break;
3237
+ // Focus any other visible modals when this one hides
3238
+ qtip._bind(tooltip, 'tooltiphide', function(event) {
3239
+ if(event.target === tooltip[0]) {
3240
+ $(MODALSELECTOR).filter(':visible').not(tooltip).last().qtip('focus', event);
3241
+ }
3242
+ }, this._ns, this);
3243
+ },
3168
3244
 
3169
- // Unknown shape... use bounding box as fallback
3170
- default:
3171
- box = elem.getBBox();
3172
- mtx = elem.getScreenCTM();
3173
- root = elem.farthestViewportElement || elem;
3245
+ toggle: function(event, state, duration) {
3246
+ // Make sure default event hasn't been prevented
3247
+ if(event && event.isDefaultPrevented()) { return this; }
3174
3248
 
3175
- // Return if no createSVGPoint method is found
3176
- if(!root.createSVGPoint) { return FALSE; }
3249
+ // Toggle it
3250
+ OVERLAY.toggle(this.qtip, !!state, duration);
3251
+ },
3177
3252
 
3178
- // Create our point var
3179
- point = root.createSVGPoint();
3253
+ destroy: function() {
3254
+ // Remove modal class
3255
+ this.qtip.tooltip.removeClass(MODALCLASS);
3180
3256
 
3181
- // Adjust top and left
3182
- point.x = box.x;
3183
- point.y = box.y;
3184
- tPoint = point.matrixTransform(mtx);
3185
- result.position = {
3186
- left: tPoint.x, top: tPoint.y
3187
- };
3257
+ // Remove bound events
3258
+ this.qtip._unbind(this.qtip.tooltip, this._ns);
3188
3259
 
3189
- // Adjust width and height
3190
- point.x += box.width;
3191
- point.y += box.height;
3192
- tPoint = point.matrixTransform(mtx);
3193
- result.width = tPoint.x - result.position.left;
3194
- result.height = tPoint.y - result.position.top;
3195
- break;
3260
+ // Delete element reference
3261
+ OVERLAY.toggle(this.qtip, FALSE);
3262
+ delete this.qtip.elements.overlay;
3196
3263
  }
3264
+ });
3197
3265
 
3198
- // Adjust by scroll offset
3199
- result.position.left += doc.scrollLeft();
3200
- result.position.top += doc.scrollTop();
3201
3266
 
3202
- return result;
3267
+ MODAL = PLUGINS.modal = function(api) {
3268
+ return new Modal(api, api.options.show.modal);
3203
3269
  };
3204
3270
 
3205
- PLUGINS.svg.toPixel = function(elem, x, y) {
3206
- var mtx = elem.getScreenCTM(),
3207
- root = elem.farthestViewportElement || elem,
3208
- result, point;
3209
-
3210
- // Create SVG point
3211
- if(!root.createSVGPoint) { return FALSE; }
3212
- point = root.createSVGPoint();
3271
+ // Setup sanitiztion rules
3272
+ MODAL.sanitize = function(opts) {
3273
+ if(opts.show) {
3274
+ if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; }
3275
+ else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; }
3276
+ }
3277
+ };
3213
3278
 
3214
- point.x = x; point.y = y;
3215
- result = point.matrixTransform(mtx);
3216
- return [ result.x, result.y ];
3217
- };;PLUGINS.imagemap = function(api, area, corner, adjustMethod)
3218
- {
3219
- if(!area.jquery) { area = $(area); }
3279
+ // Base z-index for all modal tooltips (use qTip core z-index as a base)
3280
+ QTIP.modal_zindex = QTIP.zindex - 200;
3220
3281
 
3221
- var shape = area.attr('shape').toLowerCase().replace('poly', 'polygon'),
3222
- image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'),
3223
- coordsString = area.attr('coords'),
3224
- coordsArray = coordsString.split(','),
3225
- imageOffset, coords, i, next;
3282
+ // Plugin needs to be initialized on render
3283
+ MODAL.initialize = 'render';
3226
3284
 
3227
- // If we can't find the image using the map...
3228
- if(!image.length) { return FALSE; }
3285
+ // Setup option set checks
3286
+ CHECKS.modal = {
3287
+ '^show.modal.(on|blur)$': function() {
3288
+ // Initialise
3289
+ this.destroy();
3290
+ this.init();
3229
3291
 
3230
- // Pass coordinates string if polygon
3231
- if(shape === 'polygon') {
3232
- result = PLUGINS.polys.polygon(coordsArray, corner);
3292
+ // Show the modal if not visible already and tooltip is visible
3293
+ this.qtip.elems.overlay.toggle(
3294
+ this.qtip.tooltip[0].offsetWidth > 0
3295
+ );
3233
3296
  }
3297
+ };
3234
3298
 
3235
- // Otherwise parse the coordinates and pass them as arguments
3236
- else if(PLUGINS.polys[shape]) {
3237
- for(i = -1, len = coordsArray.length, coords = []; ++i < len;) {
3238
- coords.push( parseInt(coordsArray[i], 10) );
3299
+ // Extend original api defaults
3300
+ $.extend(TRUE, QTIP.defaults, {
3301
+ show: {
3302
+ modal: {
3303
+ on: FALSE,
3304
+ effect: TRUE,
3305
+ blur: TRUE,
3306
+ stealfocus: TRUE,
3307
+ escape: TRUE
3239
3308
  }
3240
-
3241
- result = PLUGINS.polys[shape].apply(
3242
- this, coords.concat(corner)
3243
- );
3244
3309
  }
3310
+ });
3311
+ ;var IE6,
3245
3312
 
3246
- // If no shapre calculation method was found, return false
3247
- else { return FALSE; }
3248
-
3249
- // Make sure we account for padding and borders on the image
3250
- imageOffset = image.offset();
3251
- imageOffset.left += Math.ceil((image.outerWidth(FALSE) - image.width()) / 2);
3252
- imageOffset.top += Math.ceil((image.outerHeight(FALSE) - image.height()) / 2);
3253
-
3254
- // Add image position to offset coordinates
3255
- result.position.left += imageOffset.left;
3256
- result.position.top += imageOffset.top;
3257
-
3258
- return result;
3259
- };;var IE6,
3260
-
3261
- /*
3313
+ /*
3262
3314
  * BGIFrame adaption (http://plugins.jquery.com/project/bgiframe)
3263
3315
  * Special thanks to Brandon Aaron
3264
3316
  */
@@ -3329,7 +3381,7 @@ $.extend(Ie6.prototype, {
3329
3381
 
3330
3382
  // Max/min width simulator function
3331
3383
  redraw: function() {
3332
- if(this.qtip.rendered < 1 || this.drawing) { return self; }
3384
+ if(this.qtip.rendered < 1 || this.drawing) { return this; }
3333
3385
 
3334
3386
  var tooltip = this.qtip.tooltip,
3335
3387
  style = this.qtip.options.style,
@@ -3358,7 +3410,7 @@ $.extend(Ie6.prototype, {
3358
3410
 
3359
3411
  // Parse into proper pixel values
3360
3412
  perc = (max + min).indexOf('%') > -1 ? container.width() / 100 : 0;
3361
- max = ((max.indexOf('%') > -1 ? perc : 1) * parseInt(max, 10)) || width;
3413
+ max = ((max.indexOf('%') > -1 ? perc : 1) * parseInt(max, 10)) || width;
3362
3414
  min = ((min.indexOf('%') > -1 ? perc : 1) * parseInt(min, 10)) || 0;
3363
3415
 
3364
3416
  // Determine new dimension size based on max/min/current values
@@ -3371,7 +3423,7 @@ $.extend(Ie6.prototype, {
3371
3423
  // Set drawing flag
3372
3424
  this.drawing = 0;
3373
3425
 
3374
- return self;
3426
+ return this;
3375
3427
  },
3376
3428
 
3377
3429
  destroy: function() {
@@ -3391,10 +3443,9 @@ IE6 = PLUGINS.ie6 = function(api) {
3391
3443
  IE6.initialize = 'render';
3392
3444
 
3393
3445
  CHECKS.ie6 = {
3394
- '^content|style$': function() {
3446
+ '^content|style$': function() {
3395
3447
  this.redraw();
3396
3448
  }
3397
- };;}));
3449
+ };
3450
+ ;}));
3398
3451
  }( window, document ));
3399
-
3400
-