qtip2-jquery-rails 2.1.108 → 2.2.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
-