solidus_backend 1.1.4 → 1.2.0.beta1

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.
Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/solidus-style-guide-logo.png +0 -0
  3. data/app/assets/javascripts/spree/backend/admin.js.erb +1 -7
  4. data/app/assets/javascripts/spree/backend/checkouts/edit.js +2 -2
  5. data/app/assets/javascripts/spree/backend/navigation.coffee +10 -0
  6. data/app/assets/javascripts/spree/backend/option_type_autocomplete.js.erb +6 -6
  7. data/app/assets/javascripts/spree/backend/option_value_picker.js +6 -6
  8. data/app/assets/javascripts/spree/backend/product_picker.js +6 -6
  9. data/app/assets/javascripts/spree/backend/shipments.js.erb +17 -3
  10. data/app/assets/javascripts/spree/backend/stock_movement.js.coffee +2 -2
  11. data/app/assets/javascripts/spree/backend/style_guide.coffee +2 -0
  12. data/app/assets/javascripts/spree/backend/taxon_autocomplete.js.erb +6 -6
  13. data/app/assets/javascripts/spree/backend/taxonomy.js.coffee +82 -109
  14. data/app/assets/javascripts/spree/backend/taxons.js.coffee +4 -5
  15. data/app/assets/javascripts/spree/backend/user_picker.js +8 -8
  16. data/app/assets/javascripts/spree/backend/variant_autocomplete.js.coffee.erb +3 -3
  17. data/app/assets/javascripts/spree/backend.js +3 -6
  18. data/app/assets/stylesheets/spree/backend/components/_messages.scss +1 -4
  19. data/app/assets/stylesheets/spree/backend/components/_navigation.scss +99 -115
  20. data/app/assets/stylesheets/spree/backend/components/_progress.scss +1 -1
  21. data/app/assets/stylesheets/spree/backend/components/_sidebar.scss +6 -0
  22. data/app/assets/stylesheets/spree/backend/globals/_variables.scss +18 -0
  23. data/app/assets/stylesheets/spree/backend/globals/mixins/_caret.scss +35 -0
  24. data/app/assets/stylesheets/spree/backend/plugins/_powertip.scss +1 -0
  25. data/app/assets/stylesheets/spree/backend/sections/_style_guide.scss +132 -0
  26. data/app/assets/stylesheets/spree/backend/sections/_taxonomies.scss +60 -0
  27. data/app/assets/stylesheets/spree/backend/shared/_forms.scss +2 -0
  28. data/app/assets/stylesheets/spree/backend/shared/_layout.scss +36 -4
  29. data/app/assets/stylesheets/spree/backend/shared/_typography.scss +3 -6
  30. data/app/assets/stylesheets/spree/backend/spree_admin.scss +4 -1
  31. data/app/assets/stylesheets/spree/backend.css +1 -1
  32. data/app/controllers/spree/admin/orders_controller.rb +6 -6
  33. data/app/controllers/spree/admin/payment_methods_controller.rb +1 -1
  34. data/app/controllers/spree/admin/payments_controller.rb +1 -1
  35. data/app/controllers/spree/admin/products_controller.rb +0 -1
  36. data/app/controllers/spree/admin/reports_controller.rb +2 -2
  37. data/app/controllers/spree/admin/search_controller.rb +9 -7
  38. data/app/controllers/spree/admin/stock_transfers_controller.rb +6 -14
  39. data/app/controllers/spree/admin/style_guide_controller.rb +33 -0
  40. data/app/helpers/spree/admin/adjustments_helper.rb +2 -6
  41. data/app/helpers/spree/admin/base_helper.rb +7 -6
  42. data/app/helpers/spree/admin/navigation_helper.rb +12 -5
  43. data/app/helpers/spree/admin/orders_helper.rb +1 -1
  44. data/app/helpers/spree/admin/products_helper.rb +2 -4
  45. data/app/helpers/spree/admin/stock_movements_helper.rb +3 -3
  46. data/app/views/spree/admin/adjustments/_adjustments_table.html.erb +2 -2
  47. data/app/views/spree/admin/customer_returns/_reimbursements_table.html.erb +1 -1
  48. data/app/views/spree/admin/customer_returns/_return_item_selection.html.erb +1 -1
  49. data/app/views/spree/admin/customer_returns/edit.html.erb +1 -1
  50. data/app/views/spree/admin/images/edit.html.erb +1 -3
  51. data/app/views/spree/admin/images/index.html.erb +0 -2
  52. data/app/views/spree/admin/option_types/edit.html.erb +2 -4
  53. data/app/views/spree/admin/option_types/index.html.erb +0 -2
  54. data/app/views/spree/admin/option_types/new.html.erb +0 -2
  55. data/app/views/spree/admin/orders/_shipment_manifest.html.erb +1 -1
  56. data/app/views/spree/admin/orders/index.html.erb +1 -1
  57. data/app/views/spree/admin/product_properties/index.html.erb +0 -1
  58. data/app/views/spree/admin/products/edit.html.erb +0 -2
  59. data/app/views/spree/admin/products/index.html.erb +2 -4
  60. data/app/views/spree/admin/products/new.html.erb +0 -2
  61. data/app/views/spree/admin/promotion_categories/edit.html.erb +0 -2
  62. data/app/views/spree/admin/promotion_categories/index.html.erb +0 -2
  63. data/app/views/spree/admin/promotion_categories/new.html.erb +0 -2
  64. data/app/views/spree/admin/promotions/actions/_create_adjustment.html.erb +2 -28
  65. data/app/views/spree/admin/promotions/actions/_create_item_adjustments.html.erb +2 -26
  66. data/app/views/spree/admin/promotions/actions/_promotion_calculators_with_custom_fields.html.erb +28 -0
  67. data/app/views/spree/admin/promotions/edit.html.erb +0 -2
  68. data/app/views/spree/admin/promotions/index.html.erb +0 -2
  69. data/app/views/spree/admin/promotions/new.html.erb +0 -2
  70. data/app/views/spree/admin/properties/edit.html.erb +0 -2
  71. data/app/views/spree/admin/properties/index.html.erb +2 -4
  72. data/app/views/spree/admin/properties/new.html.erb +0 -2
  73. data/app/views/spree/admin/properties/new.js.erb +1 -1
  74. data/app/views/spree/admin/prototypes/edit.html.erb +0 -2
  75. data/app/views/spree/admin/prototypes/index.html.erb +0 -2
  76. data/app/views/spree/admin/prototypes/new.html.erb +0 -2
  77. data/app/views/spree/admin/reimbursements/edit.html.erb +2 -2
  78. data/app/views/spree/admin/reimbursements/show.html.erb +9 -9
  79. data/app/views/spree/admin/return_authorizations/_form.html.erb +16 -16
  80. data/app/views/spree/admin/search/users.rabl +1 -1
  81. data/app/views/spree/admin/shared/_head.html.erb +11 -0
  82. data/app/views/spree/admin/shared/_header.html.erb +4 -8
  83. data/app/views/spree/admin/shared/_menu.html.erb +4 -8
  84. data/app/views/spree/admin/shared/_navigation.html.erb +9 -0
  85. data/app/views/spree/admin/shared/_navigation_footer.html.erb +3 -0
  86. data/app/views/spree/admin/shared/_product_sub_menu.html.erb +20 -22
  87. data/app/views/spree/admin/shared/_promotion_sub_menu.html.erb +8 -10
  88. data/app/views/spree/admin/shared/_stock_sub_menu.html.erb +8 -10
  89. data/app/views/spree/admin/shared/_tabs.html.erb +9 -3
  90. data/app/views/spree/admin/shipping_methods/_form.html.erb +2 -2
  91. data/app/views/spree/admin/stock_items/index.html.erb +1 -2
  92. data/app/views/spree/admin/stock_transfers/edit.html.erb +0 -1
  93. data/app/views/spree/admin/stock_transfers/index.html.erb +0 -3
  94. data/app/views/spree/admin/stock_transfers/new.html.erb +0 -2
  95. data/app/views/spree/admin/stock_transfers/receive.html.erb +0 -2
  96. data/app/views/spree/admin/stock_transfers/show.html.erb +0 -2
  97. data/app/views/spree/admin/stock_transfers/tracking_info.html.erb +0 -2
  98. data/app/views/spree/admin/style_guide/index.html.erb +5 -0
  99. data/app/views/spree/admin/style_guide/topics/forms/_building_forms.html.erb +79 -0
  100. data/app/views/spree/admin/style_guide/topics/forms/_validation.html.erb +14 -0
  101. data/app/views/spree/admin/style_guide/topics/messaging/_flashes.html.erb +13 -0
  102. data/app/views/spree/admin/style_guide/topics/messaging/_loading.html.erb +20 -0
  103. data/app/views/spree/admin/style_guide/topics/messaging/_tooltips.html.erb +16 -0
  104. data/app/views/spree/admin/style_guide/topics/tables/_building_tables.html.erb +44 -0
  105. data/app/views/spree/admin/style_guide/topics/tables/_pagination.html.erb +11 -0
  106. data/app/views/spree/admin/style_guide/topics/typography/_colors.html.erb +64 -0
  107. data/app/views/spree/admin/style_guide/topics/typography/_fonts.html.erb +19 -0
  108. data/app/views/spree/admin/style_guide/topics/typography/_icons.html.erb +13 -0
  109. data/app/views/spree/admin/style_guide/topics/typography/_lists.html.erb +25 -0
  110. data/app/views/spree/admin/style_guide/topics/typography/_tags.html.erb +25 -0
  111. data/app/views/spree/admin/taxonomies/_js_head.html.erb +0 -0
  112. data/app/views/spree/admin/taxonomies/edit.erb +5 -6
  113. data/app/views/spree/admin/taxonomies/index.html.erb +0 -2
  114. data/app/views/spree/admin/taxonomies/new.html.erb +0 -2
  115. data/app/views/spree/admin/taxons/_form.html.erb +1 -1
  116. data/app/views/spree/admin/taxons/_list_template.html.erb +17 -0
  117. data/app/views/spree/admin/taxons/index.html.erb +0 -3
  118. data/app/views/spree/admin/users/orders.html.erb +1 -1
  119. data/app/views/spree/admin/variants/edit.html.erb +0 -2
  120. data/app/views/spree/admin/variants/index.html.erb +0 -2
  121. data/app/views/spree/layouts/admin.html.erb +3 -4
  122. data/app/views/spree/layouts/admin_style_guide.html.erb +58 -0
  123. data/config/routes.rb +4 -2
  124. data/lib/spree/backend/engine.rb +0 -1
  125. data/lib/spree/backend.rb +0 -3
  126. data/solidus_backend.gemspec +5 -7
  127. data/spec/controllers/spree/admin/missing_products_controller_spec.rb +1 -1
  128. data/spec/controllers/spree/admin/orders_controller_spec.rb +14 -14
  129. data/spec/controllers/spree/admin/payment_methods_controller_spec.rb +2 -4
  130. data/spec/controllers/spree/admin/payments_controller_spec.rb +2 -2
  131. data/spec/controllers/spree/admin/products_controller_spec.rb +3 -3
  132. data/spec/controllers/spree/admin/reports_controller_spec.rb +4 -4
  133. data/spec/controllers/spree/admin/return_authorizations_controller_spec.rb +1 -1
  134. data/spec/controllers/spree/admin/shipping_methods_controller_spec.rb +1 -1
  135. data/spec/controllers/spree/admin/stock_locations_controller_spec.rb +1 -1
  136. data/spec/controllers/spree/admin/stock_transfers_controller_spec.rb +13 -11
  137. data/spec/features/admin/configuration/general_settings_spec.rb +10 -7
  138. data/spec/features/admin/configuration/payment_methods_spec.rb +1 -1
  139. data/spec/features/admin/configuration/shipping_methods_spec.rb +1 -1
  140. data/spec/features/admin/configuration/states_spec.rb +1 -1
  141. data/spec/features/admin/configuration/tax_rates_spec.rb +6 -7
  142. data/spec/features/admin/configuration/taxonomies_spec.rb +3 -3
  143. data/spec/features/admin/homepage_spec.rb +6 -6
  144. data/spec/features/admin/orders/cancelling_and_resuming_spec.rb +4 -8
  145. data/spec/features/admin/orders/customer_details_spec.rb +9 -8
  146. data/spec/features/admin/orders/line_items_spec.rb +1 -1
  147. data/spec/features/admin/orders/listing_spec.rb +2 -2
  148. data/spec/features/admin/orders/log_entries_spec.rb +3 -3
  149. data/spec/features/admin/orders/new_order_spec.rb +3 -15
  150. data/spec/features/admin/orders/order_details_spec.rb +17 -20
  151. data/spec/features/admin/orders/payments_spec.rb +10 -10
  152. data/spec/features/admin/orders/shipments_spec.rb +2 -2
  153. data/spec/features/admin/products/edit/images_spec.rb +5 -3
  154. data/spec/features/admin/products/edit/products_spec.rb +11 -11
  155. data/spec/features/admin/products/edit/taxons_spec.rb +14 -38
  156. data/spec/features/admin/products/edit/variants_spec.rb +3 -3
  157. data/spec/features/admin/products/option_types_spec.rb +22 -21
  158. data/spec/features/admin/products/products_spec.rb +15 -12
  159. data/spec/features/admin/products/properties_spec.rb +3 -3
  160. data/spec/features/admin/products/prototypes_spec.rb +4 -4
  161. data/spec/features/admin/products/stock_management_spec.rb +5 -5
  162. data/spec/features/admin/products/variant_spec.rb +8 -8
  163. data/spec/features/admin/promotion_adjustments_spec.rb +6 -13
  164. data/spec/features/admin/promotions/option_value_rule_spec.rb +0 -20
  165. data/spec/features/admin/reports_spec.rb +2 -2
  166. data/spec/features/admin/stock_transfer_spec.rb +3 -3
  167. data/spec/features/admin/store_credits_spec.rb +2 -2
  168. data/spec/features/admin/users_spec.rb +6 -6
  169. data/spec/helpers/admin/navigation_helper_spec.rb +5 -0
  170. data/spec/helpers/admin/reimbursements_helper_spec.rb +1 -1
  171. data/spec/helpers/admin/store_credit_events_helper_spec.rb +1 -1
  172. data/spec/spec_helper.rb +3 -1
  173. data/spec/support/feature/base_feature_helper.rb +10 -0
  174. data/vendor/assets/images/jquery-ui/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  175. data/vendor/assets/images/jquery-ui/ui-bg_flat_0_eeeeee_40x100.png +0 -0
  176. data/vendor/assets/images/jquery-ui/ui-bg_flat_55_ffffff_40x100.png +0 -0
  177. data/vendor/assets/images/jquery-ui/ui-bg_flat_75_ffffff_40x100.png +0 -0
  178. data/vendor/assets/images/jquery-ui/ui-bg_glass_65_ffffff_1x400.png +0 -0
  179. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_100_f6f6f6_1x100.png +0 -0
  180. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_25_0073ea_1x100.png +0 -0
  181. data/vendor/assets/images/jquery-ui/ui-bg_highlight-soft_50_dddddd_1x100.png +0 -0
  182. data/vendor/assets/images/jquery-ui/ui-icons_0073ea_256x240.png +0 -0
  183. data/vendor/assets/images/jquery-ui/ui-icons_454545_256x240.png +0 -0
  184. data/vendor/assets/images/jquery-ui/ui-icons_666666_256x240.png +0 -0
  185. data/vendor/assets/images/jquery-ui/ui-icons_ff0084_256x240.png +0 -0
  186. data/vendor/assets/images/jquery-ui/ui-icons_ffffff_256x240.png +0 -0
  187. data/vendor/assets/javascripts/jquery.powertip.js +792 -423
  188. data/vendor/assets/javascripts/jquery.sticky-kit.min.js +10 -0
  189. data/vendor/assets/javascripts/prism.js +7 -0
  190. data/vendor/assets/stylesheets/prism.css +139 -0
  191. metadata +44 -51
  192. data/app/assets/stylesheets/spree/backend/plugins/_jstree.scss +0 -135
  193. data/app/views/spree/admin/shared/_sub_menu.html.erb +0 -9
  194. data/spec/features/admin/promotions/user_rule_spec.rb +0 -25
  195. data/vendor/assets/javascripts/equalize.js +0 -41
  196. data/vendor/assets/javascripts/jquery.jstree/jquery.jstree.js +0 -4540
  197. data/vendor/assets/javascripts/jquery.jstree/themes/apple/bg.jpg +0 -0
  198. data/vendor/assets/javascripts/jquery.jstree/themes/apple/d.png +0 -0
  199. data/vendor/assets/javascripts/jquery.jstree/themes/apple/dot_for_ie.gif +0 -0
  200. data/vendor/assets/javascripts/jquery.jstree/themes/apple/style.scss +0 -61
  201. data/vendor/assets/javascripts/jquery.jstree/themes/apple/throbber.gif +0 -0
  202. data/vendor/assets/javascripts/jquery.vAlign.js +0 -11
  203. data/vendor/assets/javascripts/modernizr.js +0 -4
  204. data/vendor/assets/javascripts/responsive-tables.js +0 -42
  205. data/vendor/assets/stylesheets/responsive-tables.css +0 -21
@@ -1,119 +1,158 @@
1
- /**
2
- * PowerTip
3
- *
4
- * @fileoverview jQuery plugin that creates hover tooltips.
5
- * @link http://stevenbenner.github.com/jquery-powertip/
6
- * @author Steven Benner (http://stevenbenner.com/)
7
- * @version 1.1.0
8
- * @requires jQuery 1.7+
9
- *
10
- * @license jQuery PowerTip Plugin v1.1.0
11
- * http://stevenbenner.github.com/jquery-powertip/
12
- * Copyright 2012 Steven Benner (http://stevenbenner.com/)
13
- * Released under the MIT license.
14
- * <https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt>
15
- */
16
-
17
- (function($) {
18
- 'use strict';
19
-
1
+ /*!
2
+ PowerTip - v1.2.0 - 2013-04-03
3
+ http://stevenbenner.github.com/jquery-powertip/
4
+ Copyright (c) 2013 Steven Benner (http://stevenbenner.com/).
5
+ Released under MIT license.
6
+ https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt
7
+ */
8
+ (function(factory) {
9
+ if (typeof define === 'function' && define.amd) {
10
+ // AMD. Register as an anonymous module.
11
+ define(['jquery'], factory);
12
+ } else {
13
+ // Browser globals
14
+ factory(jQuery);
15
+ }
16
+ }(function($) {
20
17
  // useful private variables
21
18
  var $document = $(document),
22
19
  $window = $(window),
23
20
  $body = $('body');
24
21
 
22
+ // constants
23
+ var DATA_DISPLAYCONTROLLER = 'displayController',
24
+ DATA_HASACTIVEHOVER = 'hasActiveHover',
25
+ DATA_FORCEDOPEN = 'forcedOpen',
26
+ DATA_HASMOUSEMOVE = 'hasMouseMove',
27
+ DATA_MOUSEONTOTIP = 'mouseOnToPopup',
28
+ DATA_ORIGINALTITLE = 'originalTitle',
29
+ DATA_POWERTIP = 'powertip',
30
+ DATA_POWERTIPJQ = 'powertipjq',
31
+ DATA_POWERTIPTARGET = 'powertiptarget',
32
+ RAD2DEG = 180 / Math.PI;
33
+
25
34
  /**
26
35
  * Session data
27
36
  * Private properties global to all powerTip instances
28
- * @type Object
29
37
  */
30
38
  var session = {
31
- isPopOpen: false,
32
- isFixedPopOpen: false,
39
+ isTipOpen: false,
40
+ isFixedTipOpen: false,
33
41
  isClosing: false,
34
- popOpenImminent: false,
42
+ tipOpenImminent: false,
35
43
  activeHover: null,
36
44
  currentX: 0,
37
45
  currentY: 0,
38
46
  previousX: 0,
39
47
  previousY: 0,
40
48
  desyncTimeout: null,
41
- mouseTrackingActive: false
49
+ mouseTrackingActive: false,
50
+ delayInProgress: false,
51
+ windowWidth: 0,
52
+ windowHeight: 0,
53
+ scrollTop: 0,
54
+ scrollLeft: 0
42
55
  };
43
56
 
44
57
  /**
45
- * Display hover tooltips on the matched elements.
46
- * @param {Object} opts The options object to use for the plugin.
47
- * @return {Object} jQuery object for the matched selectors.
58
+ * Collision enumeration
59
+ * @enum {number}
48
60
  */
49
- $.fn.powerTip = function(opts) {
61
+ var Collision = {
62
+ none: 0,
63
+ top: 1,
64
+ bottom: 2,
65
+ left: 4,
66
+ right: 8
67
+ };
50
68
 
69
+ /**
70
+ * Display hover tooltips on the matched elements.
71
+ * @param {(Object|string)} opts The options object to use for the plugin, or
72
+ * the name of a method to invoke on the first matched element.
73
+ * @param {*=} [arg] Argument for an invoked method (optional).
74
+ * @return {jQuery} jQuery object for the matched selectors.
75
+ */
76
+ $.fn.powerTip = function(opts, arg) {
51
77
  // don't do any work if there were no matched elements
52
78
  if (!this.length) {
53
79
  return this;
54
80
  }
55
81
 
56
- // extend options
82
+ // handle api method calls on the plugin, e.g. powerTip('hide')
83
+ if ($.type(opts) === 'string' && $.powerTip[opts]) {
84
+ return $.powerTip[opts].call(this, this, arg);
85
+ }
86
+
87
+ // extend options and instantiate TooltipController
57
88
  var options = $.extend({}, $.fn.powerTip.defaults, opts),
58
89
  tipController = new TooltipController(options);
59
90
 
60
- // hook mouse tracking
61
- initMouseTracking();
91
+ // hook mouse and viewport dimension tracking
92
+ initTracking();
62
93
 
63
94
  // setup the elements
64
- this.each(function() {
95
+ this.each(function elementSetup() {
65
96
  var $this = $(this),
66
- dataPowertip = $this.data('powertip'),
67
- dataElem = $this.data('powertipjq'),
68
- dataTarget = $this.data('powertiptarget'),
69
- title = $this.attr('title');
70
-
97
+ dataPowertip = $this.data(DATA_POWERTIP),
98
+ dataElem = $this.data(DATA_POWERTIPJQ),
99
+ dataTarget = $this.data(DATA_POWERTIPTARGET),
100
+ title;
101
+
102
+ // handle repeated powerTip calls on the same element by destroying the
103
+ // original instance hooked to it and replacing it with this call
104
+ if ($this.data(DATA_DISPLAYCONTROLLER)) {
105
+ $.powerTip.destroy($this);
106
+ }
71
107
 
72
108
  // attempt to use title attribute text if there is no data-powertip,
73
109
  // data-powertipjq or data-powertiptarget. If we do use the title
74
110
  // attribute, delete the attribute so the browser will not show it
111
+ title = $this.attr('title');
75
112
  if (!dataPowertip && !dataTarget && !dataElem && title) {
76
- $this.data('powertip', title);
113
+ $this.data(DATA_POWERTIP, title);
114
+ $this.data(DATA_ORIGINALTITLE, title);
77
115
  $this.removeAttr('title');
78
116
  }
79
117
 
80
118
  // create hover controllers for each element
81
119
  $this.data(
82
- 'displayController',
120
+ DATA_DISPLAYCONTROLLER,
83
121
  new DisplayController($this, options, tipController)
84
122
  );
85
123
  });
86
124
 
87
- // attach hover events to all matched elements
88
- return this.on({
89
- // mouse events
90
- mouseenter: function(event) {
91
- trackMouse(event);
92
- session.previousX = event.pageX;
93
- session.previousY = event.pageY;
94
- $(this).data('displayController').show();
95
- },
96
- mouseleave: function() {
97
- $(this).data('displayController').hide();
98
- },
99
-
100
- // keyboard events
101
- focus: function() {
102
- var element = $(this);
103
- if (!isMouseOver(element)) {
104
- element.data('displayController').show(true);
125
+ // attach events to matched elements if the manual options is not enabled
126
+ if (!options.manual) {
127
+ this.on({
128
+ // mouse events
129
+ 'mouseenter.powertip': function elementMouseEnter(event) {
130
+ $.powerTip.show(this, event);
131
+ },
132
+ 'mouseleave.powertip': function elementMouseLeave() {
133
+ $.powerTip.hide(this);
134
+ },
135
+ // keyboard events
136
+ 'focus.powertip': function elementFocus() {
137
+ $.powerTip.show(this);
138
+ },
139
+ 'blur.powertip': function elementBlur() {
140
+ $.powerTip.hide(this, true);
141
+ },
142
+ 'keydown.powertip': function elementKeyDown(event) {
143
+ // close tooltip when the escape key is pressed
144
+ if (event.keyCode === 27) {
145
+ $.powerTip.hide(this, true);
146
+ }
105
147
  }
106
- },
107
- blur: function() {
108
- $(this).data('displayController').hide(true);
109
- }
110
- });
148
+ });
149
+ }
111
150
 
151
+ return this;
112
152
  };
113
153
 
114
154
  /**
115
155
  * Default options for the powerTip plugin.
116
- * @type Object
117
156
  */
118
157
  $.fn.powerTip.defaults = {
119
158
  fadeInTime: 200,
@@ -126,15 +165,15 @@
126
165
  placement: 'n',
127
166
  smartPlacement: false,
128
167
  offset: 10,
129
- mouseOnToPopup: false
168
+ mouseOnToPopup: false,
169
+ manual: false
130
170
  };
131
171
 
132
172
  /**
133
173
  * Default smart placement priority lists.
134
- * The first item in the array is the highest priority, the last is the
135
- * lowest. The last item is also the default, which will be used if all
136
- * previous options do not fit.
137
- * @type Object
174
+ * The first item in the array is the highest priority, the last is the lowest.
175
+ * The last item is also the default, which will be used if all previous options
176
+ * do not fit.
138
177
  */
139
178
  $.fn.powerTip.smartPlacementLists = {
140
179
  n: ['n', 'ne', 'nw', 's'],
@@ -144,47 +183,125 @@
144
183
  nw: ['nw', 'w', 'sw', 'n', 's', 'se', 'nw'],
145
184
  ne: ['ne', 'e', 'se', 'n', 's', 'sw', 'ne'],
146
185
  sw: ['sw', 'w', 'nw', 's', 'n', 'ne', 'sw'],
147
- se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se']
186
+ se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se'],
187
+ 'nw-alt': ['nw-alt', 'n', 'ne-alt', 'sw-alt', 's', 'se-alt', 'w', 'e'],
188
+ 'ne-alt': ['ne-alt', 'n', 'nw-alt', 'se-alt', 's', 'sw-alt', 'e', 'w'],
189
+ 'sw-alt': ['sw-alt', 's', 'se-alt', 'nw-alt', 'n', 'ne-alt', 'w', 'e'],
190
+ 'se-alt': ['se-alt', 's', 'sw-alt', 'ne-alt', 'n', 'nw-alt', 'e', 'w']
148
191
  };
149
192
 
150
193
  /**
151
194
  * Public API
152
- * @type Object
153
195
  */
154
196
  $.powerTip = {
155
-
156
197
  /**
157
198
  * Attempts to show the tooltip for the specified element.
158
- * @public
159
- * @param {Object} element The element that the tooltip should for.
199
+ * @param {jQuery|Element} element The element to open the tooltip for.
200
+ * @param {jQuery.Event=} event jQuery event for hover intent and mouse
201
+ * tracking (optional).
160
202
  */
161
- showTip: function(element) {
162
- // close any open tooltip
163
- $.powerTip.closeTip();
164
- // grab only the first matched element and ask it to show its tip
165
- element = element.first();
166
- if (!isMouseOver(element)) {
167
- element.data('displayController').show(true, true);
203
+ show: function apiShowTip(element, event) {
204
+ if (event) {
205
+ trackMouse(event);
206
+ session.previousX = event.pageX;
207
+ session.previousY = event.pageY;
208
+ $(element).data(DATA_DISPLAYCONTROLLER).show();
209
+ } else {
210
+ $(element).first().data(DATA_DISPLAYCONTROLLER).show(true, true);
168
211
  }
212
+ return element;
213
+ },
214
+
215
+ /**
216
+ * Repositions the tooltip on the element.
217
+ * @param {jQuery|Element} element The element the tooltip is shown for.
218
+ */
219
+ reposition: function apiResetPosition(element) {
220
+ $(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition();
221
+ return element;
169
222
  },
170
223
 
171
224
  /**
172
225
  * Attempts to close any open tooltips.
173
- * @public
226
+ * @param {(jQuery|Element)=} element The element with the tooltip that
227
+ * should be closed (optional).
228
+ * @param {boolean=} immediate Disable close delay (optional).
174
229
  */
175
- closeTip: function() {
176
- $document.triggerHandler('closePowerTip');
177
- }
230
+ hide: function apiCloseTip(element, immediate) {
231
+ if (element) {
232
+ $(element).first().data(DATA_DISPLAYCONTROLLER).hide(immediate);
233
+ } else {
234
+ if (session.activeHover) {
235
+ session.activeHover.data(DATA_DISPLAYCONTROLLER).hide(true);
236
+ }
237
+ }
238
+ return element;
239
+ },
178
240
 
241
+ /**
242
+ * Destroy and roll back any powerTip() instance on the specified element.
243
+ * @param {jQuery|Element} element The element with the powerTip instance.
244
+ */
245
+ destroy: function apiDestroy(element) {
246
+ $(element).off('.powertip').each(function destroy() {
247
+ var $this = $(this),
248
+ dataAttributes = [
249
+ DATA_ORIGINALTITLE,
250
+ DATA_DISPLAYCONTROLLER,
251
+ DATA_HASACTIVEHOVER,
252
+ DATA_FORCEDOPEN
253
+ ];
254
+
255
+ if ($this.data(DATA_ORIGINALTITLE)) {
256
+ $this.attr('title', $this.data(DATA_ORIGINALTITLE));
257
+ dataAttributes.push(DATA_POWERTIP);
258
+ }
259
+
260
+ $this.removeData(dataAttributes);
261
+ });
262
+ return element;
263
+ }
179
264
  };
180
265
 
266
+ // API aliasing
267
+ $.powerTip.showTip = $.powerTip.show;
268
+ $.powerTip.closeTip = $.powerTip.hide;
269
+
270
+ /**
271
+ * Creates a new CSSCoordinates object.
272
+ * @private
273
+ * @constructor
274
+ */
275
+ function CSSCoordinates() {
276
+ var me = this;
277
+
278
+ // initialize object properties
279
+ me.top = 'auto';
280
+ me.left = 'auto';
281
+ me.right = 'auto';
282
+ me.bottom = 'auto';
283
+
284
+ /**
285
+ * Set a property to a value.
286
+ * @private
287
+ * @param {string} property The name of the property.
288
+ * @param {number} value The value of the property.
289
+ */
290
+ me.set = function(property, value) {
291
+ if ($.isNumeric(value)) {
292
+ me[property] = Math.round(value);
293
+ }
294
+ };
295
+ }
296
+
181
297
  /**
182
298
  * Creates a new tooltip display controller.
183
299
  * @private
184
300
  * @constructor
185
- * @param {Object} element The element that this controller will handle.
301
+ * @param {jQuery} element The element that this controller will handle.
186
302
  * @param {Object} options Options object containing settings.
187
- * @param {TooltipController} tipController The TooltipController for this instance.
303
+ * @param {TooltipController} tipController The TooltipController object for
304
+ * this instance.
188
305
  */
189
306
  function DisplayController(element, options, tipController) {
190
307
  var hoverTimer = null;
@@ -192,24 +309,25 @@
192
309
  /**
193
310
  * Begins the process of showing a tooltip.
194
311
  * @private
195
- * @param {Boolean=} immediate Skip intent testing (optional).
196
- * @param {Boolean=} forceOpen Ignore cursor position and force tooltip to open (optional).
312
+ * @param {boolean=} immediate Skip intent testing (optional).
313
+ * @param {boolean=} forceOpen Ignore cursor position and force tooltip to
314
+ * open (optional).
197
315
  */
198
316
  function openTooltip(immediate, forceOpen) {
199
317
  cancelTimer();
200
- if (!element.data('hasActiveHover')) {
318
+ if (!element.data(DATA_HASACTIVEHOVER)) {
201
319
  if (!immediate) {
202
- session.popOpenImminent = true;
320
+ session.tipOpenImminent = true;
203
321
  hoverTimer = setTimeout(
204
- function() {
322
+ function intentDelay() {
205
323
  hoverTimer = null;
206
- checkForIntent(element);
324
+ checkForIntent();
207
325
  },
208
326
  options.intentPollInterval
209
327
  );
210
328
  } else {
211
329
  if (forceOpen) {
212
- element.data('forcedOpen', true);
330
+ element.data(DATA_FORCEDOPEN, true);
213
331
  }
214
332
  tipController.showTip(element);
215
333
  }
@@ -219,18 +337,20 @@
219
337
  /**
220
338
  * Begins the process of closing a tooltip.
221
339
  * @private
222
- * @param {Boolean=} disableDelay Disable close delay (optional).
340
+ * @param {boolean=} disableDelay Disable close delay (optional).
223
341
  */
224
342
  function closeTooltip(disableDelay) {
225
343
  cancelTimer();
226
- if (element.data('hasActiveHover')) {
227
- session.popOpenImminent = false;
228
- element.data('forcedOpen', false);
344
+ session.tipOpenImminent = false;
345
+ if (element.data(DATA_HASACTIVEHOVER)) {
346
+ element.data(DATA_FORCEDOPEN, false);
229
347
  if (!disableDelay) {
348
+ session.delayInProgress = true;
230
349
  hoverTimer = setTimeout(
231
- function() {
350
+ function closeDelay() {
232
351
  hoverTimer = null;
233
352
  tipController.hideTip(element);
353
+ session.delayInProgress = false;
234
354
  },
235
355
  options.closeDelay
236
356
  );
@@ -241,8 +361,8 @@
241
361
  }
242
362
 
243
363
  /**
244
- * Checks mouse position to make sure that the user intended to hover
245
- * on the specified element before showing the tooltip.
364
+ * Checks mouse position to make sure that the user intended to hover on the
365
+ * specified element before showing the tooltip.
246
366
  * @private
247
367
  */
248
368
  function checkForIntent() {
@@ -268,14 +388,238 @@
268
388
  */
269
389
  function cancelTimer() {
270
390
  hoverTimer = clearTimeout(hoverTimer);
391
+ session.delayInProgress = false;
392
+ }
393
+
394
+ /**
395
+ * Repositions the tooltip on this element.
396
+ * @private
397
+ */
398
+ function repositionTooltip() {
399
+ tipController.resetPosition(element);
271
400
  }
272
401
 
273
402
  // expose the methods
274
- return {
275
- show: openTooltip,
276
- hide: closeTooltip,
277
- cancel: cancelTimer
278
- };
403
+ this.show = openTooltip;
404
+ this.hide = closeTooltip;
405
+ this.cancel = cancelTimer;
406
+ this.resetPosition = repositionTooltip;
407
+ }
408
+
409
+ /**
410
+ * Creates a new Placement Calculator.
411
+ * @private
412
+ * @constructor
413
+ */
414
+ function PlacementCalculator() {
415
+ /**
416
+ * Compute the CSS position to display a tooltip at the specified placement
417
+ * relative to the specified element.
418
+ * @private
419
+ * @param {jQuery} element The element that the tooltip should target.
420
+ * @param {string} placement The placement for the tooltip.
421
+ * @param {number} tipWidth Width of the tooltip element in pixels.
422
+ * @param {number} tipHeight Height of the tooltip element in pixels.
423
+ * @param {number} offset Distance to offset tooltips in pixels.
424
+ * @return {CSSCoordinates} A CSSCoordinates object with the position.
425
+ */
426
+ function computePlacementCoords(element, placement, tipWidth, tipHeight, offset) {
427
+ var placementBase = placement.split('-')[0], // ignore 'alt' for corners
428
+ coords = new CSSCoordinates(),
429
+ position;
430
+
431
+ if (isSvgElement(element)) {
432
+ position = getSvgPlacement(element, placementBase);
433
+ } else {
434
+ position = getHtmlPlacement(element, placementBase);
435
+ }
436
+
437
+ // calculate the appropriate x and y position in the document
438
+ switch (placement) {
439
+ case 'n':
440
+ coords.set('left', position.left - (tipWidth / 2));
441
+ coords.set('bottom', session.windowHeight - position.top + offset);
442
+ break;
443
+ case 'e':
444
+ coords.set('left', position.left + offset);
445
+ coords.set('top', position.top - (tipHeight / 2));
446
+ break;
447
+ case 's':
448
+ coords.set('left', position.left - (tipWidth / 2));
449
+ coords.set('top', position.top + offset);
450
+ break;
451
+ case 'w':
452
+ coords.set('top', position.top - (tipHeight / 2));
453
+ coords.set('right', session.windowWidth - position.left + offset);
454
+ break;
455
+ case 'nw':
456
+ coords.set('bottom', session.windowHeight - position.top + offset);
457
+ coords.set('right', session.windowWidth - position.left - 20);
458
+ break;
459
+ case 'nw-alt':
460
+ coords.set('left', position.left);
461
+ coords.set('bottom', session.windowHeight - position.top + offset);
462
+ break;
463
+ case 'ne':
464
+ coords.set('left', position.left - 20);
465
+ coords.set('bottom', session.windowHeight - position.top + offset);
466
+ break;
467
+ case 'ne-alt':
468
+ coords.set('bottom', session.windowHeight - position.top + offset);
469
+ coords.set('right', session.windowWidth - position.left);
470
+ break;
471
+ case 'sw':
472
+ coords.set('top', position.top + offset);
473
+ coords.set('right', session.windowWidth - position.left - 20);
474
+ break;
475
+ case 'sw-alt':
476
+ coords.set('left', position.left);
477
+ coords.set('top', position.top + offset);
478
+ break;
479
+ case 'se':
480
+ coords.set('left', position.left - 20);
481
+ coords.set('top', position.top + offset);
482
+ break;
483
+ case 'se-alt':
484
+ coords.set('top', position.top + offset);
485
+ coords.set('right', session.windowWidth - position.left);
486
+ break;
487
+ }
488
+
489
+ return coords;
490
+ }
491
+
492
+ /**
493
+ * Finds the tooltip attachment point in the document for a HTML DOM element
494
+ * for the specified placement.
495
+ * @private
496
+ * @param {jQuery} element The element that the tooltip should target.
497
+ * @param {string} placement The placement for the tooltip.
498
+ * @return {Object} An object with the top,left position values.
499
+ */
500
+ function getHtmlPlacement(element, placement) {
501
+ var objectOffset = element.offset(),
502
+ objectWidth = element.outerWidth(),
503
+ objectHeight = element.outerHeight(),
504
+ left,
505
+ top;
506
+
507
+ // calculate the appropriate x and y position in the document
508
+ switch (placement) {
509
+ case 'n':
510
+ left = objectOffset.left + objectWidth / 2;
511
+ top = objectOffset.top;
512
+ break;
513
+ case 'e':
514
+ left = objectOffset.left + objectWidth;
515
+ top = objectOffset.top + objectHeight / 2;
516
+ break;
517
+ case 's':
518
+ left = objectOffset.left + objectWidth / 2;
519
+ top = objectOffset.top + objectHeight;
520
+ break;
521
+ case 'w':
522
+ left = objectOffset.left;
523
+ top = objectOffset.top + objectHeight / 2;
524
+ break;
525
+ case 'nw':
526
+ left = objectOffset.left;
527
+ top = objectOffset.top;
528
+ break;
529
+ case 'ne':
530
+ left = objectOffset.left + objectWidth;
531
+ top = objectOffset.top;
532
+ break;
533
+ case 'sw':
534
+ left = objectOffset.left;
535
+ top = objectOffset.top + objectHeight;
536
+ break;
537
+ case 'se':
538
+ left = objectOffset.left + objectWidth;
539
+ top = objectOffset.top + objectHeight;
540
+ break;
541
+ }
542
+
543
+ return {
544
+ top: top,
545
+ left: left
546
+ };
547
+ }
548
+
549
+ /**
550
+ * Finds the tooltip attachment point in the document for a SVG element for
551
+ * the specified placement.
552
+ * @private
553
+ * @param {jQuery} element The element that the tooltip should target.
554
+ * @param {string} placement The placement for the tooltip.
555
+ * @return {Object} An object with the top,left position values.
556
+ */
557
+ function getSvgPlacement(element, placement) {
558
+ var svgElement = element.closest('svg')[0],
559
+ domElement = element[0],
560
+ point = svgElement.createSVGPoint(),
561
+ boundingBox = domElement.getBBox(),
562
+ matrix = domElement.getScreenCTM(),
563
+ halfWidth = boundingBox.width / 2,
564
+ halfHeight = boundingBox.height / 2,
565
+ placements = [],
566
+ placementKeys = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'],
567
+ coords,
568
+ rotation,
569
+ steps,
570
+ x;
571
+
572
+ function pushPlacement() {
573
+ placements.push(point.matrixTransform(matrix));
574
+ }
575
+
576
+ // get bounding box corners and midpoints
577
+ point.x = boundingBox.x;
578
+ point.y = boundingBox.y;
579
+ pushPlacement();
580
+ point.x += halfWidth;
581
+ pushPlacement();
582
+ point.x += halfWidth;
583
+ pushPlacement();
584
+ point.y += halfHeight;
585
+ pushPlacement();
586
+ point.y += halfHeight;
587
+ pushPlacement();
588
+ point.x -= halfWidth;
589
+ pushPlacement();
590
+ point.x -= halfWidth;
591
+ pushPlacement();
592
+ point.y -= halfHeight;
593
+ pushPlacement();
594
+
595
+ // determine rotation
596
+ if (placements[0].y !== placements[1].y || placements[0].x !== placements[7].x) {
597
+ rotation = Math.atan2(matrix.b, matrix.a) * RAD2DEG;
598
+ steps = Math.ceil(((rotation % 360) - 22.5) / 45);
599
+ if (steps < 1) {
600
+ steps += 8;
601
+ }
602
+ while (steps--) {
603
+ placementKeys.push(placementKeys.shift());
604
+ }
605
+ }
606
+
607
+ // find placement
608
+ for (x = 0; x < placements.length; x++) {
609
+ if (placementKeys[x] === placement) {
610
+ coords = placements[x];
611
+ break;
612
+ }
613
+ }
614
+
615
+ return {
616
+ top: coords.y + session.scrollTop,
617
+ left: coords.x + session.scrollLeft
618
+ };
619
+ }
620
+
621
+ // expose methods
622
+ this.compute = computePlacementCoords;
279
623
  }
280
624
 
281
625
  /**
@@ -285,13 +629,14 @@
285
629
  * @param {Object} options Options object containing settings.
286
630
  */
287
631
  function TooltipController(options) {
632
+ var placementCalculator = new PlacementCalculator(),
633
+ tipElement = $('#' + options.popupId);
288
634
 
289
- // build and append popup div if it does not already exist
290
- var tipElement = $('#' + options.popupId);
635
+ // build and append tooltip div if it does not already exist
291
636
  if (tipElement.length === 0) {
292
- tipElement = $('<div></div>', { id: options.popupId });
637
+ tipElement = $('<div/>', { id: options.popupId });
293
638
  // grab body element if it was not populated when the script loaded
294
- // this hack exists solely for jsfiddle support
639
+ // note: this hack exists solely for jsfiddle support
295
640
  if ($body.length === 0) {
296
641
  $body = $('body');
297
642
  }
@@ -300,79 +645,79 @@
300
645
 
301
646
  // hook mousemove for cursor follow tooltips
302
647
  if (options.followMouse) {
303
- // only one positionTipOnCursor hook per popup element, please
304
- if (!tipElement.data('hasMouseMove')) {
305
- $document.on({
306
- mousemove: positionTipOnCursor,
307
- scroll: positionTipOnCursor
308
- });
648
+ // only one positionTipOnCursor hook per tooltip element, please
649
+ if (!tipElement.data(DATA_HASMOUSEMOVE)) {
650
+ $document.on('mousemove', positionTipOnCursor);
651
+ $window.on('scroll', positionTipOnCursor);
652
+ tipElement.data(DATA_HASMOUSEMOVE, true);
309
653
  }
310
- tipElement.data('hasMouseMove', true);
311
654
  }
312
655
 
313
- // if we want to be able to mouse onto the popup then we need to attach
314
- // hover events to the popup that will cancel a close request on hover
315
- // and start a new close request on mouseleave
316
- if (options.followMouse || options.mouseOnToPopup) {
656
+ // if we want to be able to mouse onto the tooltip then we need to attach
657
+ // hover events to the tooltip that will cancel a close request on hover and
658
+ // start a new close request on mouseleave
659
+ if (options.mouseOnToPopup) {
317
660
  tipElement.on({
318
- mouseenter: function() {
319
- if (tipElement.data('followMouse') || tipElement.data('mouseOnToPopup')) {
320
- // check activeHover in case the mouse cursor entered
321
- // the tooltip during the fadeOut and close cycle
661
+ mouseenter: function tipMouseEnter() {
662
+ // we only let the mouse stay on the tooltip if it is set to let
663
+ // users interact with it
664
+ if (tipElement.data(DATA_MOUSEONTOTIP)) {
665
+ // check activeHover in case the mouse cursor entered the
666
+ // tooltip during the fadeOut and close cycle
322
667
  if (session.activeHover) {
323
- session.activeHover.data('displayController').cancel();
668
+ session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel();
324
669
  }
325
670
  }
326
671
  },
327
- mouseleave: function() {
328
- if (tipElement.data('mouseOnToPopup')) {
329
- // check activeHover in case the mouse cursor entered
330
- // the tooltip during the fadeOut and close cycle
331
- if (session.activeHover) {
332
- session.activeHover.data('displayController').hide();
333
- }
672
+ mouseleave: function tipMouseLeave() {
673
+ // check activeHover in case the mouse cursor entered the
674
+ // tooltip during the fadeOut and close cycle
675
+ if (session.activeHover) {
676
+ session.activeHover.data(DATA_DISPLAYCONTROLLER).hide();
334
677
  }
335
678
  }
336
679
  });
337
680
  }
338
681
 
339
682
  /**
340
- * Gives the specified element the active-hover state and queues up
341
- * the showTip function.
683
+ * Gives the specified element the active-hover state and queues up the
684
+ * showTip function.
342
685
  * @private
343
- * @param {Object} element The element that the tooltip should target.
686
+ * @param {jQuery} element The element that the tooltip should target.
344
687
  */
345
688
  function beginShowTip(element) {
346
- element.data('hasActiveHover', true);
347
- // show popup, asap
348
- tipElement.queue(function(next) {
689
+ element.data(DATA_HASACTIVEHOVER, true);
690
+ // show tooltip, asap
691
+ tipElement.queue(function queueTipInit(next) {
349
692
  showTip(element);
350
693
  next();
351
694
  });
352
695
  }
353
696
 
354
697
  /**
355
- * Shows the tooltip popup, as soon as possible.
698
+ * Shows the tooltip, as soon as possible.
356
699
  * @private
357
- * @param {Object} element The element that the popup should target.
700
+ * @param {jQuery} element The element that the tooltip should target.
358
701
  */
359
702
  function showTip(element) {
360
- // it is possible, especially with keyboard navigation, to move on
361
- // to another element with a tooltip during the queue to get to
362
- // this point in the code. if that happens then we need to not
363
- // proceed or we may have the fadeout callback for the last tooltip
364
- // execute immediately after this code runs, causing bugs.
365
- if (!element.data('hasActiveHover')) {
703
+ var tipContent;
704
+
705
+ // it is possible, especially with keyboard navigation, to move on to
706
+ // another element with a tooltip during the queue to get to this point
707
+ // in the code. if that happens then we need to not proceed or we may
708
+ // have the fadeout callback for the last tooltip execute immediately
709
+ // after this code runs, causing bugs.
710
+ if (!element.data(DATA_HASACTIVEHOVER)) {
366
711
  return;
367
712
  }
368
713
 
369
- // if the popup is open and we got asked to open another one then
370
- // the old one is still in its fadeOut cycle, so wait and try again
371
- if (session.isPopOpen) {
714
+ // if the tooltip is open and we got asked to open another one then the
715
+ // old one is still in its fadeOut cycle, so wait and try again
716
+ if (session.isTipOpen) {
372
717
  if (!session.isClosing) {
373
718
  hideTip(session.activeHover);
374
719
  }
375
- tipElement.delay(100).queue(function(next) {
720
+ tipElement.delay(100).queue(function queueTipAgain(next) {
376
721
  showTip(element);
377
722
  next();
378
723
  });
@@ -382,19 +727,10 @@
382
727
  // trigger powerTipPreRender event
383
728
  element.trigger('powerTipPreRender');
384
729
 
385
- var tipText = element.data('powertip'),
386
- tipTarget = element.data('powertiptarget'),
387
- tipElem = element.data('powertipjq'),
388
- tipContent = tipTarget ? $('#' + tipTarget) : [];
389
-
390
- // set popup content
391
- if (tipText) {
392
- tipElement.html(tipText);
393
- } else if (tipElem && tipElem.length > 0) {
394
- tipElement.empty();
395
- tipElem.clone(true, true).appendTo(tipElement);
396
- } else if (tipContent && tipContent.length > 0) {
397
- tipElement.html($('#' + tipTarget).html());
730
+ // set tooltip content
731
+ tipContent = getTooltipContent(element);
732
+ if (tipContent) {
733
+ tipElement.empty().append(tipContent);
398
734
  } else {
399
735
  // we have no content to display, give up
400
736
  return;
@@ -403,27 +739,21 @@
403
739
  // trigger powerTipRender event
404
740
  element.trigger('powerTipRender');
405
741
 
406
- // hook close event for triggering from the api
407
- $document.on('closePowerTip', function() {
408
- element.data('displayController').hide(true);
409
- });
410
-
411
742
  session.activeHover = element;
412
- session.isPopOpen = true;
743
+ session.isTipOpen = true;
413
744
 
414
- tipElement.data('followMouse', options.followMouse);
415
- tipElement.data('mouseOnToPopup', options.mouseOnToPopup);
745
+ tipElement.data(DATA_MOUSEONTOTIP, options.mouseOnToPopup);
416
746
 
417
- // set popup position
747
+ // set tooltip position
418
748
  if (!options.followMouse) {
419
749
  positionTipOnElement(element);
420
- session.isFixedPopOpen = true;
750
+ session.isFixedTipOpen = true;
421
751
  } else {
422
752
  positionTipOnCursor();
423
753
  }
424
754
 
425
755
  // fadein
426
- tipElement.fadeIn(options.fadeInTime, function() {
756
+ tipElement.fadeIn(options.fadeInTime, function fadeInCallback() {
427
757
  // start desync polling
428
758
  if (!session.desyncTimeout) {
429
759
  session.desyncTimeout = setInterval(closeDesyncedTip, 500);
@@ -435,33 +765,37 @@
435
765
  }
436
766
 
437
767
  /**
438
- * Hides the tooltip popup, immediately.
768
+ * Hides the tooltip.
439
769
  * @private
440
- * @param {Object} element The element that the popup should target.
770
+ * @param {jQuery} element The element that the tooltip should target.
441
771
  */
442
772
  function hideTip(element) {
443
- session.isClosing = true;
444
- element.data('hasActiveHover', false);
445
- element.data('forcedOpen', false);
446
773
  // reset session
774
+ session.isClosing = true;
447
775
  session.activeHover = null;
448
- session.isPopOpen = false;
776
+ session.isTipOpen = false;
777
+
449
778
  // stop desync polling
450
779
  session.desyncTimeout = clearInterval(session.desyncTimeout);
451
- // unhook close event api listener
452
- $document.off('closePowerTip');
780
+
781
+ // reset element state
782
+ element.data(DATA_HASACTIVEHOVER, false);
783
+ element.data(DATA_FORCEDOPEN, false);
784
+
453
785
  // fade out
454
- tipElement.fadeOut(options.fadeOutTime, function() {
786
+ tipElement.fadeOut(options.fadeOutTime, function fadeOutCallback() {
787
+ var coords = new CSSCoordinates();
788
+
789
+ // reset session and tooltip element
455
790
  session.isClosing = false;
456
- session.isFixedPopOpen = false;
791
+ session.isFixedTipOpen = false;
457
792
  tipElement.removeClass();
458
- // support mouse-follow and fixed position pops at the same
459
- // time by moving the popup to the last known cursor location
460
- // after it is hidden
461
- setTipPosition(
462
- session.currentX + options.offset,
463
- session.currentY + options.offset
464
- );
793
+
794
+ // support mouse-follow and fixed position tips at the same time by
795
+ // moving the tooltip to the last cursor location after it is hidden
796
+ coords.set('top', session.currentY + options.offset);
797
+ coords.set('left', session.currentX + options.offset);
798
+ tipElement.css(coords);
465
799
 
466
800
  // trigger powerTipClose event
467
801
  element.trigger('powerTipClose');
@@ -469,268 +803,245 @@
469
803
  }
470
804
 
471
805
  /**
472
- * Checks for a tooltip desync and closes the tooltip if one occurs.
473
- * @private
474
- */
475
- function closeDesyncedTip() {
476
- // It is possible for the mouse cursor to leave an element without
477
- // firing the mouseleave event. This seems to happen (in FF) if the
478
- // element is disabled under mouse cursor, the element is moved out
479
- // from under the mouse cursor (such as a slideDown() occurring
480
- // above it), or if the browser is resized by code moving the
481
- // element from under the mouse cursor. If this happens it will
482
- // result in a desynced tooltip because we wait for any exiting
483
- // open tooltips to close before opening a new one. So we should
484
- // periodically check for a desync situation and close the tip if
485
- // such a situation arises.
486
- if (session.isPopOpen && !session.isClosing) {
487
- var isDesynced = false;
488
-
489
- // case 1: user already moused onto another tip - easy test
490
- if (session.activeHover.data('hasActiveHover') === false) {
491
- isDesynced = true;
492
- } else {
493
- // case 2: hanging tip - have to test if mouse position is
494
- // not over the active hover and not over a tooltip set to
495
- // let the user interact with it.
496
- // for keyboard navigation, this only counts if the element
497
- // does not have focus.
498
- // for tooltips opened via the api we need to check if it
499
- // has the forcedOpen flag.
500
- if (!isMouseOver(session.activeHover) && !session.activeHover.is(":focus") && !session.activeHover.data('forcedOpen')) {
501
- if (tipElement.data('mouseOnToPopup')) {
502
- if (!isMouseOver(tipElement)) {
503
- isDesynced = true;
504
- }
505
- } else {
506
- isDesynced = true;
507
- }
508
- }
509
- }
510
-
511
- if (isDesynced) {
512
- // close the desynced tip
513
- hideTip(session.activeHover);
514
- }
515
- }
516
- }
517
-
518
- /**
519
- * Moves the tooltip popup to the users mouse cursor.
806
+ * Moves the tooltip to the users mouse cursor.
520
807
  * @private
521
808
  */
522
809
  function positionTipOnCursor() {
523
- // to support having fixed powertips on the same page as cursor
524
- // powertips, where both instances are referencing the same popup
525
- // element, we need to keep track of the mouse position constantly,
526
- // but we should only set the pop location if a fixed pop is not
527
- // currently open, a pop open is imminent or active, and the popup
528
- // element in question does have a mouse-follow using it.
529
- if ((session.isPopOpen && !session.isFixedPopOpen) || (session.popOpenImminent && !session.isFixedPopOpen && tipElement.data('hasMouseMove'))) {
810
+ // to support having fixed tooltips on the same page as cursor tooltips,
811
+ // where both instances are referencing the same tooltip element, we
812
+ // need to keep track of the mouse position constantly, but we should
813
+ // only set the tip location if a fixed tip is not currently open, a tip
814
+ // open is imminent or active, and the tooltip element in question does
815
+ // have a mouse-follow using it.
816
+ if (!session.isFixedTipOpen && (session.isTipOpen || (session.tipOpenImminent && tipElement.data(DATA_HASMOUSEMOVE)))) {
530
817
  // grab measurements
531
- var scrollTop = $window.scrollTop(),
532
- windowWidth = $window.width(),
533
- windowHeight = $window.height(),
534
- popWidth = tipElement.outerWidth(),
535
- popHeight = tipElement.outerHeight(),
536
- x = 0,
537
- y = 0;
538
-
539
- // constrain pop to browser viewport
540
- if ((popWidth + session.currentX + options.offset) < windowWidth) {
541
- x = session.currentX + options.offset;
542
- } else {
543
- x = windowWidth - popWidth;
544
- }
545
- if ((popHeight + session.currentY + options.offset) < (scrollTop + windowHeight)) {
546
- y = session.currentY + options.offset;
547
- } else {
548
- y = scrollTop + windowHeight - popHeight;
818
+ var tipWidth = tipElement.outerWidth(),
819
+ tipHeight = tipElement.outerHeight(),
820
+ coords = new CSSCoordinates(),
821
+ collisions,
822
+ collisionCount;
823
+
824
+ // grab collisions
825
+ coords.set('top', session.currentY + options.offset);
826
+ coords.set('left', session.currentX + options.offset);
827
+ collisions = getViewportCollisions(
828
+ coords,
829
+ tipWidth,
830
+ tipHeight
831
+ );
832
+
833
+ // handle tooltip view port collisions
834
+ if (collisions !== Collision.none) {
835
+ collisionCount = countFlags(collisions);
836
+ if (collisionCount === 1) {
837
+ // if there is only one collision (bottom or right) then
838
+ // simply constrain the tooltip to the view port
839
+ if (collisions === Collision.right) {
840
+ coords.set('left', session.windowWidth - tipWidth);
841
+ } else if (collisions === Collision.bottom) {
842
+ coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
843
+ }
844
+ } else {
845
+ // if the tooltip has more than one collision then it is
846
+ // trapped in the corner and should be flipped to get it out
847
+ // of the users way
848
+ coords.set('left', session.currentX - tipWidth - options.offset);
849
+ coords.set('top', session.currentY - tipHeight - options.offset);
850
+ }
549
851
  }
550
852
 
551
853
  // position the tooltip
552
- setTipPosition(x, y);
854
+ tipElement.css(coords);
553
855
  }
554
856
  }
555
857
 
556
858
  /**
557
- * Sets the tooltip popup too the correct position relative to the
558
- * specified target element. Based on options settings.
859
+ * Sets the tooltip to the correct position relative to the specified target
860
+ * element. Based on options settings.
559
861
  * @private
560
- * @param {Object} element The element that the popup should target.
862
+ * @param {jQuery} element The element that the tooltip should target.
561
863
  */
562
864
  function positionTipOnElement(element) {
563
- var tipWidth = tipElement.outerWidth(),
564
- tipHeight = tipElement.outerHeight(),
565
- priorityList,
566
- placementCoords,
567
- finalPlacement,
568
- collisions;
569
-
570
- // with smart placement we will try a series of placement
571
- // options and use the first one that does not collide with the
572
- // browser view port boundaries.
573
- if (options.smartPlacement) {
865
+ var priorityList,
866
+ finalPlacement;
574
867
 
575
- // grab the placement priority list
868
+ if (options.smartPlacement) {
576
869
  priorityList = $.fn.powerTip.smartPlacementLists[options.placement];
577
870
 
578
- // iterate over the priority list and use the first placement
579
- // option that does not collide with the viewport. if they all
580
- // collide then the last placement in the list will be used.
871
+ // iterate over the priority list and use the first placement option
872
+ // that does not collide with the view port. if they all collide
873
+ // then the last placement in the list will be used.
581
874
  $.each(priorityList, function(idx, pos) {
582
- // get placement coordinates
583
- placementCoords = computePlacementCoords(
584
- element,
585
- pos,
586
- tipWidth,
587
- tipHeight
875
+ // place tooltip and find collisions
876
+ var collisions = getViewportCollisions(
877
+ placeTooltip(element, pos),
878
+ tipElement.outerWidth(),
879
+ tipElement.outerHeight()
588
880
  );
589
- finalPlacement = pos;
590
881
 
591
- // find collisions
592
- collisions = getViewportCollisions(
593
- placementCoords,
594
- tipWidth,
595
- tipHeight
596
- );
882
+ // update the final placement variable
883
+ finalPlacement = pos;
597
884
 
598
885
  // break if there were no collisions
599
- if (collisions.length === 0) {
886
+ if (collisions === Collision.none) {
600
887
  return false;
601
888
  }
602
889
  });
603
-
604
890
  } else {
605
-
606
- // if we're not going to use the smart placement feature then
607
- // just compute the coordinates and do it
608
- placementCoords = computePlacementCoords(
609
- element,
610
- options.placement,
611
- tipWidth,
612
- tipHeight
613
- );
891
+ // if we're not going to use the smart placement feature then just
892
+ // compute the coordinates and do it
893
+ placeTooltip(element, options.placement);
614
894
  finalPlacement = options.placement;
615
-
616
895
  }
617
896
 
618
897
  // add placement as class for CSS arrows
619
898
  tipElement.addClass(finalPlacement);
620
-
621
- // position the tooltip
622
- setTipPosition(placementCoords.x, placementCoords.y);
623
899
  }
624
900
 
625
901
  /**
626
- * Compute the top/left coordinates to display the tooltip at the
627
- * specified placement relative to the specified element.
902
+ * Sets the tooltip position to the appropriate values to show the tip at
903
+ * the specified placement. This function will iterate and test the tooltip
904
+ * to support elastic tooltips.
628
905
  * @private
629
- * @param {Object} element The element that the tooltip should target.
630
- * @param {String} placement The placement for the tooltip.
631
- * @param {Number} popWidth Width of the tooltip element in pixels.
632
- * @param {Number} popHeight Height of the tooltip element in pixels.
633
- * @retun {Object} An object with the x and y coordinates.
906
+ * @param {jQuery} element The element that the tooltip should target.
907
+ * @param {string} placement The placement for the tooltip.
908
+ * @return {CSSCoordinates} A CSSCoordinates object with the top, left, and
909
+ * right position values.
634
910
  */
635
- function computePlacementCoords(element, placement, popWidth, popHeight) {
636
- // grab measurements
637
- var objectOffset = element.offset(),
638
- objectWidth = element.outerWidth(),
639
- objectHeight = element.outerHeight(),
640
- x = 0,
641
- y = 0;
911
+ function placeTooltip(element, placement) {
912
+ var iterationCount = 0,
913
+ tipWidth,
914
+ tipHeight,
915
+ coords = new CSSCoordinates();
916
+
917
+ // set the tip to 0,0 to get the full expanded width
918
+ coords.set('top', 0);
919
+ coords.set('left', 0);
920
+ tipElement.css(coords);
921
+
922
+ // to support elastic tooltips we need to check for a change in the
923
+ // rendered dimensions after the tooltip has been positioned
924
+ do {
925
+ // grab the current tip dimensions
926
+ tipWidth = tipElement.outerWidth();
927
+ tipHeight = tipElement.outerHeight();
928
+
929
+ // get placement coordinates
930
+ coords = placementCalculator.compute(
931
+ element,
932
+ placement,
933
+ tipWidth,
934
+ tipHeight,
935
+ options.offset
936
+ );
642
937
 
643
- // calculate the appropriate x and y position in the document
644
- switch (placement) {
645
- case 'n':
646
- x = (objectOffset.left + (objectWidth / 2)) - (popWidth / 2);
647
- y = objectOffset.top - popHeight - options.offset;
648
- break;
649
- case 'e':
650
- x = objectOffset.left + objectWidth + options.offset;
651
- y = (objectOffset.top + (objectHeight / 2)) - (popHeight / 2);
652
- break;
653
- case 's':
654
- x = (objectOffset.left + (objectWidth / 2)) - (popWidth / 2);
655
- y = objectOffset.top + objectHeight + options.offset;
656
- break;
657
- case 'w':
658
- x = objectOffset.left - popWidth - options.offset;
659
- y = (objectOffset.top + (objectHeight / 2)) - (popHeight / 2);
660
- break;
661
- case 'nw':
662
- x = (objectOffset.left - popWidth) + 20;
663
- y = objectOffset.top - popHeight - options.offset;
664
- break;
665
- case 'ne':
666
- x = (objectOffset.left + objectWidth) - 20;
667
- y = objectOffset.top - popHeight - options.offset;
668
- break;
669
- case 'sw':
670
- x = (objectOffset.left - popWidth) + 20;
671
- y = objectOffset.top + objectHeight + options.offset;
672
- break;
673
- case 'se':
674
- x = (objectOffset.left + objectWidth) - 20;
675
- y = objectOffset.top + objectHeight + options.offset;
676
- break;
677
- }
938
+ // place the tooltip
939
+ tipElement.css(coords);
940
+ } while (
941
+ // sanity check: limit to 5 iterations, and...
942
+ ++iterationCount <= 5 &&
943
+ // try again if the dimensions changed after placement
944
+ (tipWidth !== tipElement.outerWidth() || tipHeight !== tipElement.outerHeight())
945
+ );
678
946
 
679
- return {
680
- x: Math.round(x),
681
- y: Math.round(y)
682
- };
947
+ return coords;
683
948
  }
684
949
 
685
950
  /**
686
- * Sets the tooltip CSS position on the document.
951
+ * Checks for a tooltip desync and closes the tooltip if one occurs.
687
952
  * @private
688
- * @param {Number} x Left position in pixels.
689
- * @param {Number} y Top position in pixels.
690
953
  */
691
- function setTipPosition(x, y) {
692
- tipElement.css('left', x + 'px');
693
- tipElement.css('top', y + 'px');
954
+ function closeDesyncedTip() {
955
+ var isDesynced = false;
956
+ // It is possible for the mouse cursor to leave an element without
957
+ // firing the mouseleave or blur event. This most commonly happens when
958
+ // the element is disabled under mouse cursor. If this happens it will
959
+ // result in a desynced tooltip because the tooltip was never asked to
960
+ // close. So we should periodically check for a desync situation and
961
+ // close the tip if such a situation arises.
962
+ if (session.isTipOpen && !session.isClosing && !session.delayInProgress) {
963
+ // user moused onto another tip or active hover is disabled
964
+ if (session.activeHover.data(DATA_HASACTIVEHOVER) === false || session.activeHover.is(':disabled')) {
965
+ isDesynced = true;
966
+ } else {
967
+ // hanging tip - have to test if mouse position is not over the
968
+ // active hover and not over a tooltip set to let the user
969
+ // interact with it.
970
+ // for keyboard navigation: this only counts if the element does
971
+ // not have focus.
972
+ // for tooltips opened via the api: we need to check if it has
973
+ // the forcedOpen flag.
974
+ if (!isMouseOver(session.activeHover) && !session.activeHover.is(':focus') && !session.activeHover.data(DATA_FORCEDOPEN)) {
975
+ if (tipElement.data(DATA_MOUSEONTOTIP)) {
976
+ if (!isMouseOver(tipElement)) {
977
+ isDesynced = true;
978
+ }
979
+ } else {
980
+ isDesynced = true;
981
+ }
982
+ }
983
+ }
984
+
985
+ if (isDesynced) {
986
+ // close the desynced tip
987
+ hideTip(session.activeHover);
988
+ }
989
+ }
694
990
  }
695
991
 
696
992
  // expose methods
697
- return {
698
- showTip: beginShowTip,
699
- hideTip: hideTip
700
- };
993
+ this.showTip = beginShowTip;
994
+ this.hideTip = hideTip;
995
+ this.resetPosition = positionTipOnElement;
701
996
  }
702
997
 
703
998
  /**
704
- * Hooks mouse position tracking to mousemove and scroll events.
705
- * Prevents attaching the events more than once.
999
+ * Determine whether a jQuery object is an SVG element
706
1000
  * @private
1001
+ * @param {jQuery} element The element to check
1002
+ * @return {boolean} Whether this is an SVG element
707
1003
  */
708
- function initMouseTracking() {
709
- var lastScrollX = 0,
710
- lastScrollY = 0;
1004
+ function isSvgElement(element) {
1005
+ return window.SVGElement && element[0] instanceof SVGElement;
1006
+ }
711
1007
 
1008
+ /**
1009
+ * Initializes the viewport dimension cache and hooks up the mouse position
1010
+ * tracking and viewport dimension tracking events.
1011
+ * Prevents attaching the events more than once.
1012
+ * @private
1013
+ */
1014
+ function initTracking() {
712
1015
  if (!session.mouseTrackingActive) {
713
1016
  session.mouseTrackingActive = true;
714
1017
 
715
- // grab the current scroll position on load
716
- $(function() {
717
- lastScrollX = $document.scrollLeft();
718
- lastScrollY = $document.scrollTop();
1018
+ // grab the current viewport dimensions on load
1019
+ $(function getViewportDimensions() {
1020
+ session.scrollLeft = $window.scrollLeft();
1021
+ session.scrollTop = $window.scrollTop();
1022
+ session.windowWidth = $window.width();
1023
+ session.windowHeight = $window.height();
719
1024
  });
720
1025
 
721
- // hook mouse position tracking
722
- $document.on({
723
- mousemove: trackMouse,
724
- scroll: function() {
725
- var x = $document.scrollLeft(),
726
- y = $document.scrollTop();
727
- if (x !== lastScrollX) {
728
- session.currentX += x - lastScrollX;
729
- lastScrollX = x;
1026
+ // hook mouse move tracking
1027
+ $document.on('mousemove', trackMouse);
1028
+
1029
+ // hook viewport dimensions tracking
1030
+ $window.on({
1031
+ resize: function trackResize() {
1032
+ session.windowWidth = $window.width();
1033
+ session.windowHeight = $window.height();
1034
+ },
1035
+ scroll: function trackScroll() {
1036
+ var x = $window.scrollLeft(),
1037
+ y = $window.scrollTop();
1038
+ if (x !== session.scrollLeft) {
1039
+ session.currentX += x - session.scrollLeft;
1040
+ session.scrollLeft = x;
730
1041
  }
731
- if (y !== lastScrollY) {
732
- session.currentY += y - lastScrollY;
733
- lastScrollY = y;
1042
+ if (y !== session.scrollTop) {
1043
+ session.currentY += y - session.scrollTop;
1044
+ session.scrollTop = y;
734
1045
  }
735
1046
  }
736
1047
  });
@@ -738,9 +1049,9 @@
738
1049
  }
739
1050
 
740
1051
  /**
741
- * Saves the current mouse coordinates to the powerTip session object.
1052
+ * Saves the current mouse coordinates to the session object.
742
1053
  * @private
743
- * @param {Object} event The mousemove event for the document.
1054
+ * @param {jQuery.Event} event The mousemove event for the document.
744
1055
  */
745
1056
  function trackMouse(event) {
746
1057
  session.currentX = event.pageX;
@@ -750,47 +1061,105 @@
750
1061
  /**
751
1062
  * Tests if the mouse is currently over the specified element.
752
1063
  * @private
753
- * @param {Object} element The element to check for hover.
754
- * @return {Boolean}
1064
+ * @param {jQuery} element The element to check for hover.
1065
+ * @return {boolean}
755
1066
  */
756
1067
  function isMouseOver(element) {
757
- var elementPosition = element.offset();
1068
+ // use getBoundingClientRect() because jQuery's width() and height()
1069
+ // methods do not work with SVG elements
1070
+ // compute width/height because those properties do not exist on the object
1071
+ // returned by getBoundingClientRect() in older versions of IE
1072
+ var elementPosition = element.offset(),
1073
+ elementBox = element[0].getBoundingClientRect(),
1074
+ elementWidth = elementBox.right - elementBox.left,
1075
+ elementHeight = elementBox.bottom - elementBox.top;
1076
+
758
1077
  return session.currentX >= elementPosition.left &&
759
- session.currentX <= elementPosition.left + element.outerWidth() &&
1078
+ session.currentX <= elementPosition.left + elementWidth &&
760
1079
  session.currentY >= elementPosition.top &&
761
- session.currentY <= elementPosition.top + element.outerHeight();
1080
+ session.currentY <= elementPosition.top + elementHeight;
762
1081
  }
763
1082
 
764
1083
  /**
765
- * Finds any viewport collisions that an element (the tooltip) would have
766
- * if it were absolutely positioned at the specified coordinates.
1084
+ * Fetches the tooltip content from the specified element's data attributes.
767
1085
  * @private
768
- * @param {Object} coords Coordinates for the element. (e.g. {x: 123, y: 123})
769
- * @param {Number} elementWidth Width of the element in pixels.
770
- * @param {Number} elementHeight Height of the element in pixels.
771
- * @return {Array} Array of words representing directional collisions.
1086
+ * @param {jQuery} element The element to get the tooltip content for.
1087
+ * @return {(string|jQuery|undefined)} The text/HTML string, jQuery object, or
1088
+ * undefined if there was no tooltip content for the element.
1089
+ */
1090
+ function getTooltipContent(element) {
1091
+ var tipText = element.data(DATA_POWERTIP),
1092
+ tipObject = element.data(DATA_POWERTIPJQ),
1093
+ tipTarget = element.data(DATA_POWERTIPTARGET),
1094
+ targetElement,
1095
+ content;
1096
+
1097
+ if (tipText) {
1098
+ if ($.isFunction(tipText)) {
1099
+ tipText = tipText.call(element[0]);
1100
+ }
1101
+ content = tipText;
1102
+ } else if (tipObject) {
1103
+ if ($.isFunction(tipObject)) {
1104
+ tipObject = tipObject.call(element[0]);
1105
+ }
1106
+ if (tipObject.length > 0) {
1107
+ content = tipObject.clone(true, true);
1108
+ }
1109
+ } else if (tipTarget) {
1110
+ targetElement = $('#' + tipTarget);
1111
+ if (targetElement.length > 0) {
1112
+ content = targetElement.html();
1113
+ }
1114
+ }
1115
+
1116
+ return content;
1117
+ }
1118
+
1119
+ /**
1120
+ * Finds any viewport collisions that an element (the tooltip) would have if it
1121
+ * were absolutely positioned at the specified coordinates.
1122
+ * @private
1123
+ * @param {CSSCoordinates} coords Coordinates for the element.
1124
+ * @param {number} elementWidth Width of the element in pixels.
1125
+ * @param {number} elementHeight Height of the element in pixels.
1126
+ * @return {number} Value with the collision flags.
772
1127
  */
773
1128
  function getViewportCollisions(coords, elementWidth, elementHeight) {
774
- var scrollLeft = $window.scrollLeft(),
775
- scrollTop = $window.scrollTop(),
776
- windowWidth = $window.width(),
777
- windowHeight = $window.height(),
778
- collisions = [];
779
-
780
- if (coords.y < scrollTop) {
781
- collisions.push('top');
1129
+ var viewportTop = session.scrollTop,
1130
+ viewportLeft = session.scrollLeft,
1131
+ viewportBottom = viewportTop + session.windowHeight,
1132
+ viewportRight = viewportLeft + session.windowWidth,
1133
+ collisions = Collision.none;
1134
+
1135
+ if (coords.top < viewportTop || Math.abs(coords.bottom - session.windowHeight) - elementHeight < viewportTop) {
1136
+ collisions |= Collision.top;
782
1137
  }
783
- if (coords.y + elementHeight > scrollTop + windowHeight) {
784
- collisions.push('bottom');
1138
+ if (coords.top + elementHeight > viewportBottom || Math.abs(coords.bottom - session.windowHeight) > viewportBottom) {
1139
+ collisions |= Collision.bottom;
785
1140
  }
786
- if (coords.x < scrollLeft) {
787
- collisions.push('left');
1141
+ if (coords.left < viewportLeft || coords.right + elementWidth > viewportRight) {
1142
+ collisions |= Collision.left;
788
1143
  }
789
- if (coords.x + elementWidth > scrollLeft + windowWidth) {
790
- collisions.push('right');
1144
+ if (coords.left + elementWidth > viewportRight || coords.right < viewportLeft) {
1145
+ collisions |= Collision.right;
791
1146
  }
792
1147
 
793
1148
  return collisions;
794
1149
  }
795
1150
 
796
- }(jQuery));
1151
+ /**
1152
+ * Counts the number of bits set on a flags value.
1153
+ * @param {number} value The flags value.
1154
+ * @return {number} The number of bits that have been set.
1155
+ */
1156
+ function countFlags(value) {
1157
+ var count = 0;
1158
+ while (value) {
1159
+ value &= value - 1;
1160
+ count++;
1161
+ }
1162
+ return count;
1163
+ }
1164
+
1165
+ }));