unpoly-rails 0.57.0 → 0.60.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of unpoly-rails might be problematic. Click here for more details.

Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +393 -1
  3. data/Gemfile.lock +5 -2
  4. data/README.md +1 -1
  5. data/README_RAILS.md +1 -1
  6. data/Rakefile +10 -1
  7. data/design/es6.js +32 -0
  8. data/design/ie11.txt +9 -0
  9. data/design/measure_jquery/element_list.js +41 -0
  10. data/design/measure_jquery/up.on_vs_addEventListener.js +56 -0
  11. data/design/todo_jquery.txt +13 -0
  12. data/dist/unpoly-bootstrap3.js +8 -8
  13. data/dist/unpoly-bootstrap3.min.js +1 -1
  14. data/dist/unpoly.css +22 -20
  15. data/dist/unpoly.js +6990 -5336
  16. data/dist/unpoly.min.css +1 -1
  17. data/dist/unpoly.min.js +4 -4
  18. data/lib/assets/javascripts/unpoly-bootstrap3/viewport-ext.coffee +5 -0
  19. data/lib/assets/javascripts/unpoly.coffee +8 -6
  20. data/lib/assets/javascripts/unpoly/browser.coffee.erb +23 -118
  21. data/lib/assets/javascripts/unpoly/classes/body_shifter.coffee +36 -0
  22. data/lib/assets/javascripts/unpoly/classes/cache.coffee +4 -4
  23. data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +45 -39
  24. data/lib/assets/javascripts/unpoly/classes/config.coffee +9 -0
  25. data/lib/assets/javascripts/unpoly/classes/css_transition.coffee +18 -27
  26. data/lib/assets/javascripts/unpoly/classes/divertible_chain.coffee +39 -0
  27. data/lib/assets/javascripts/unpoly/classes/event_listener.coffee +116 -0
  28. data/lib/assets/javascripts/unpoly/classes/extract_cascade.coffee +8 -8
  29. data/lib/assets/javascripts/unpoly/classes/extract_plan.coffee +19 -19
  30. data/lib/assets/javascripts/unpoly/classes/field_observer.coffee +54 -31
  31. data/lib/assets/javascripts/unpoly/classes/{focus_tracker.coffee → focus_follower.coffee} +2 -2
  32. data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +25 -25
  33. data/lib/assets/javascripts/unpoly/classes/html_parser.coffee +4 -11
  34. data/lib/assets/javascripts/unpoly/classes/motion_controller.coffee +157 -0
  35. data/lib/assets/javascripts/unpoly/classes/params.coffee.erb +525 -0
  36. data/lib/assets/javascripts/unpoly/classes/record.coffee +8 -2
  37. data/lib/assets/javascripts/unpoly/classes/rect.js +21 -0
  38. data/lib/assets/javascripts/unpoly/classes/request.coffee +41 -35
  39. data/lib/assets/javascripts/unpoly/classes/response.coffee +7 -3
  40. data/lib/assets/javascripts/unpoly/classes/reveal_motion.coffee +102 -0
  41. data/lib/assets/javascripts/unpoly/classes/scroll_motion.coffee +67 -0
  42. data/lib/assets/javascripts/unpoly/classes/selector.coffee +60 -0
  43. data/lib/assets/javascripts/unpoly/classes/tether.coffee +105 -0
  44. data/lib/assets/javascripts/unpoly/classes/url_set.coffee +12 -7
  45. data/lib/assets/javascripts/unpoly/element.coffee.erb +1126 -0
  46. data/lib/assets/javascripts/unpoly/event.coffee.erb +437 -0
  47. data/lib/assets/javascripts/unpoly/feedback.coffee +73 -94
  48. data/lib/assets/javascripts/unpoly/form.coffee.erb +188 -181
  49. data/lib/assets/javascripts/unpoly/{dom.coffee.erb → fragment.coffee.erb} +250 -283
  50. data/lib/assets/javascripts/unpoly/framework.coffee +67 -0
  51. data/lib/assets/javascripts/unpoly/history.coffee +29 -28
  52. data/lib/assets/javascripts/unpoly/legacy.coffee +60 -0
  53. data/lib/assets/javascripts/unpoly/link.coffee.erb +127 -119
  54. data/lib/assets/javascripts/unpoly/log.coffee +99 -19
  55. data/lib/assets/javascripts/unpoly/modal.coffee.erb +95 -118
  56. data/lib/assets/javascripts/unpoly/motion.coffee.erb +158 -138
  57. data/lib/assets/javascripts/unpoly/namespace.coffee.erb +0 -5
  58. data/lib/assets/javascripts/unpoly/popup.coffee.erb +119 -102
  59. data/lib/assets/javascripts/unpoly/protocol.coffee +11 -15
  60. data/lib/assets/javascripts/unpoly/proxy.coffee +62 -65
  61. data/lib/assets/javascripts/unpoly/radio.coffee +3 -5
  62. data/lib/assets/javascripts/unpoly/rails.coffee +8 -9
  63. data/lib/assets/javascripts/unpoly/syntax.coffee.erb +173 -125
  64. data/lib/assets/javascripts/unpoly/toast.coffee +25 -24
  65. data/lib/assets/javascripts/unpoly/tooltip.coffee +89 -79
  66. data/lib/assets/javascripts/unpoly/util.coffee.erb +579 -1074
  67. data/lib/assets/javascripts/unpoly/{layout.coffee.erb → viewport.coffee.erb} +334 -264
  68. data/lib/assets/stylesheets/unpoly/dom.sass +1 -1
  69. data/lib/assets/stylesheets/unpoly/layout.sass +2 -0
  70. data/lib/assets/stylesheets/unpoly/popup.sass +0 -1
  71. data/lib/assets/stylesheets/unpoly/tooltip.sass +17 -12
  72. data/lib/unpoly/rails/version.rb +1 -1
  73. data/package.json +1 -2
  74. data/spec_app/Gemfile +2 -1
  75. data/spec_app/Gemfile.lock +38 -27
  76. data/spec_app/app/assets/javascripts/integration_test.coffee +1 -0
  77. data/spec_app/app/assets/javascripts/jasmine_specs.coffee +1 -2
  78. data/spec_app/app/assets/stylesheets/integration_test.sass +14 -1
  79. data/spec_app/app/controllers/scroll_test_controller.rb +5 -0
  80. data/spec_app/app/views/css_test/modal.erb +6 -6
  81. data/spec_app/app/views/css_test/popup.erb +44 -18
  82. data/spec_app/app/views/css_test/tooltip.erb +23 -4
  83. data/spec_app/app/views/error_test/trigger.erb +1 -1
  84. data/spec_app/app/views/form_test/basics/new.erb +1 -3
  85. data/spec_app/app/views/pages/start.erb +9 -2
  86. data/spec_app/app/views/reveal_test/long1.erb +1 -1
  87. data/spec_app/app/views/reveal_test/long2.erb +1 -1
  88. data/spec_app/app/views/reveal_test/within_document_viewport.erb +24 -0
  89. data/spec_app/app/views/reveal_test/within_overflowing_div_viewport.erb +28 -0
  90. data/spec_app/app/views/scroll_test/long1.erb +30 -0
  91. data/spec_app/config/routes.rb +1 -0
  92. data/spec_app/spec/javascripts/helpers/agent_detector.coffee +3 -0
  93. data/spec_app/spec/javascripts/helpers/async_sequence.js.coffee +1 -0
  94. data/spec_app/spec/javascripts/helpers/browser_switches.js.coffee +17 -5
  95. data/spec_app/spec/javascripts/helpers/enable_logging.js.coffee +1 -1
  96. data/spec_app/spec/javascripts/helpers/fixture.js.coffee +25 -0
  97. data/spec_app/spec/javascripts/helpers/jquery_no_conflict.js +1 -0
  98. data/spec_app/spec/javascripts/helpers/last_request.js.coffee +1 -0
  99. data/spec_app/spec/javascripts/helpers/mock_ajax.js.coffee +1 -1
  100. data/spec_app/spec/javascripts/helpers/parse_form_data.js.coffee +2 -2
  101. data/spec_app/spec/javascripts/helpers/protect_jasmine_runner.coffee +4 -1
  102. data/spec_app/spec/javascripts/helpers/remove_body_margin.js.coffee +3 -0
  103. data/spec_app/spec/javascripts/helpers/reset_history.js.coffee +2 -1
  104. data/spec_app/spec/javascripts/helpers/reset_knife.js.coffee +2 -2
  105. data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +18 -11
  106. data/spec_app/spec/javascripts/helpers/restore_body_scroll.js.coffee +3 -0
  107. data/spec_app/spec/javascripts/helpers/show_lib_versions.coffee +3 -0
  108. data/spec_app/spec/javascripts/helpers/spec_util.coffee +47 -0
  109. data/spec_app/spec/javascripts/helpers/to_be_around.js.coffee +3 -0
  110. data/spec_app/spec/javascripts/helpers/to_be_array.coffee +5 -0
  111. data/spec_app/spec/javascripts/helpers/to_be_attached.coffee +6 -2
  112. data/spec_app/spec/javascripts/helpers/to_be_blank.js.coffee +3 -0
  113. data/spec_app/spec/javascripts/helpers/to_be_detached.coffee +6 -2
  114. data/spec_app/spec/javascripts/helpers/to_be_element.js.coffee +8 -0
  115. data/spec_app/spec/javascripts/helpers/to_be_error.coffee +3 -0
  116. data/spec_app/spec/javascripts/helpers/to_be_given.js.coffee +3 -0
  117. data/spec_app/spec/javascripts/helpers/to_be_hidden.js.coffee +8 -0
  118. data/spec_app/spec/javascripts/helpers/to_be_missing.js.coffee +3 -0
  119. data/spec_app/spec/javascripts/helpers/to_be_present.js.coffee +3 -0
  120. data/spec_app/spec/javascripts/helpers/to_be_scrolled_to.coffee +3 -0
  121. data/spec_app/spec/javascripts/helpers/to_be_visible.js.coffee +9 -0
  122. data/spec_app/spec/javascripts/helpers/to_contain.js.coffee +3 -0
  123. data/spec_app/spec/javascripts/helpers/to_end_with.js.coffee +3 -0
  124. data/spec_app/spec/javascripts/helpers/to_equal_jquery.js.coffee +1 -2
  125. data/spec_app/spec/javascripts/helpers/to_equal_node_list.coffee +7 -0
  126. data/spec_app/spec/javascripts/helpers/to_equal_via_is_equal.js.coffee +7 -0
  127. data/spec_app/spec/javascripts/helpers/to_have_class.js.coffee +10 -0
  128. data/spec_app/spec/javascripts/helpers/to_have_descendant.js.coffee +10 -0
  129. data/spec_app/spec/javascripts/helpers/to_have_length.js.coffee +8 -0
  130. data/spec_app/spec/javascripts/helpers/to_have_opacity.coffee +7 -3
  131. data/spec_app/spec/javascripts/helpers/to_have_own_property.js.coffee +3 -0
  132. data/spec_app/spec/javascripts/helpers/to_have_request_method.js.coffee +1 -0
  133. data/spec_app/spec/javascripts/helpers/to_have_text.js.coffee +9 -0
  134. data/spec_app/spec/javascripts/helpers/to_have_unhandled_rejections.coffee +0 -21
  135. data/spec_app/spec/javascripts/helpers/to_match_list.coffee +14 -0
  136. data/spec_app/spec/javascripts/helpers/to_match_selector.coffee +3 -0
  137. data/spec_app/spec/javascripts/helpers/to_match_text.js.coffee +4 -1
  138. data/spec_app/spec/javascripts/helpers/to_match_url.coffee +1 -0
  139. data/spec_app/spec/javascripts/helpers/trigger.js.coffee +91 -7
  140. data/spec_app/spec/javascripts/helpers/wait_until_dom_ready.js.coffee +3 -0
  141. data/spec_app/spec/javascripts/up/browser_spec.js.coffee +23 -90
  142. data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +3 -0
  143. data/spec_app/spec/javascripts/up/classes/config_spec.coffee +24 -0
  144. data/spec_app/spec/javascripts/up/classes/divertible_chain_spec.coffee +45 -0
  145. data/spec_app/spec/javascripts/up/classes/focus_tracker_spec.coffee +5 -2
  146. data/spec_app/spec/javascripts/up/classes/params_spec.coffee +557 -0
  147. data/spec_app/spec/javascripts/up/classes/request_spec.coffee +7 -4
  148. data/spec_app/spec/javascripts/up/classes/scroll_motion_spec.js.coffee +51 -0
  149. data/spec_app/spec/javascripts/up/classes/store/memory_spec.js.coffee +3 -0
  150. data/spec_app/spec/javascripts/up/classes/store/session_spec.js.coffee +3 -2
  151. data/spec_app/spec/javascripts/up/element_spec.coffee +897 -0
  152. data/spec_app/spec/javascripts/up/event_spec.js.coffee +496 -0
  153. data/spec_app/spec/javascripts/up/feedback_spec.js.coffee +69 -48
  154. data/spec_app/spec/javascripts/up/form_spec.js.coffee +252 -194
  155. data/spec_app/spec/javascripts/up/{dom_spec.js.coffee → fragment_spec.js.coffee} +381 -388
  156. data/spec_app/spec/javascripts/up/history_spec.js.coffee +21 -19
  157. data/spec_app/spec/javascripts/up/jquery_spec.js.coffee +4 -0
  158. data/spec_app/spec/javascripts/up/legacy_spec.js.coffee +27 -0
  159. data/spec_app/spec/javascripts/up/link_spec.js.coffee +163 -160
  160. data/spec_app/spec/javascripts/up/log_spec.js.coffee +85 -12
  161. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +141 -123
  162. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +117 -113
  163. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +60 -77
  164. data/spec_app/spec/javascripts/up/protocol_spec.js.coffee +1 -0
  165. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +85 -78
  166. data/spec_app/spec/javascripts/up/radio_spec.js.coffee +29 -22
  167. data/spec_app/spec/javascripts/up/rails_spec.js.coffee +14 -13
  168. data/spec_app/spec/javascripts/up/spec_spec.js.coffee +9 -0
  169. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +96 -66
  170. data/spec_app/spec/javascripts/up/toast_spec.js.coffee +37 -0
  171. data/spec_app/spec/javascripts/up/tooltip_spec.js.coffee +31 -47
  172. data/spec_app/spec/javascripts/up/util_spec.js.coffee +725 -562
  173. data/spec_app/spec/javascripts/up/{layout_spec.js.coffee → viewport_spec.js.coffee} +175 -149
  174. metadata +57 -19
  175. data/lib/assets/javascripts/unpoly-bootstrap3/layout-ext.coffee +0 -5
  176. data/lib/assets/javascripts/unpoly/bus.coffee.erb +0 -518
  177. data/lib/assets/javascripts/unpoly/classes/extract_step.coffee +0 -4
  178. data/lib/assets/javascripts/unpoly/classes/motion_tracker.coffee +0 -125
  179. data/lib/assets/javascripts/unpoly/params.coffee.erb +0 -522
  180. data/spec_app/spec/javascripts/helpers/append_fixture.js.coffee +0 -8
  181. data/spec_app/spec/javascripts/up/bus_spec.js.coffee +0 -210
  182. data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +0 -9
  183. data/spec_app/spec/javascripts/up/params_spec.coffee +0 -768
  184. data/spec_app/vendor/asset-libs/jasmine-fixture-1.3.4/jasmine-fixture.js +0 -433
  185. data/spec_app/vendor/asset-libs/jasmine-jquery-2.1.1/.bower.json +0 -26
  186. data/spec_app/vendor/asset-libs/jasmine-jquery-2.1.1/jasmine-jquery.js +0 -838
@@ -2,17 +2,16 @@
2
2
  Toast alerts
3
3
  ============
4
4
 
5
- @class up.toast
5
+ @module up.toast
6
6
  ###
7
- up.toast = (($) ->
8
-
7
+ up.toast = do ->
9
8
  u = up.util
10
- b = up.browser
9
+ e = up.element
11
10
 
12
11
  VARIABLE_FORMATTER = (arg) -> "<span class='up-toast-variable'>#{u.escapeHtml(arg)}</span>"
13
12
 
14
- state = u.config
15
- $toast: null
13
+ state = new up.Config
14
+ element: null
16
15
 
17
16
  reset = ->
18
17
  close()
@@ -21,39 +20,43 @@ up.toast = (($) ->
21
20
  messageToHtml = (message) ->
22
21
  if u.isArray(message)
23
22
  message[0] = u.escapeHtml(message[0])
24
- message = b.sprintfWithFormattedArgs(VARIABLE_FORMATTER, message...)
23
+ message = up.log.sprintfWithFormattedArgs(VARIABLE_FORMATTER, message...)
25
24
  else
26
25
  message = u.escapeHtml(message)
27
26
  message
28
27
 
29
28
  isOpen = ->
30
- !!state.$toast
29
+ !!state.element
31
30
 
32
- addAction = ($actions, label, callback) ->
33
- $action = $('<span class="up-toast-action"></span>').text(label)
34
- $action.on 'click', callback
35
- $action.appendTo($actions)
36
-
31
+ addAction = (label, callback) ->
32
+ actions = state.element.querySelector('.up-toast-actions')
33
+ action = e.affix(actions, '.up-toast-action')
34
+ action.innerText = label
35
+ action.addEventListener('click', callback)
36
+
37
37
  open = (message, options = {}) ->
38
38
  close()
39
- $toast = $('<div class="up-toast"></div>').prependTo('body')
40
- $message = $('<div class="up-toast-message"></div>').appendTo($toast)
41
- $actions = $('<div class="up-toast-actions"></div>').appendTo($toast)
42
39
 
43
40
  message = messageToHtml(message)
44
- $message.html(message)
41
+
42
+ state.element = e.createFromHtml """
43
+ <div class="up-toast">
44
+ <div class="up-toast-message">#{message}</div>
45
+ <div class="up-toast-actions"></div>
46
+ </div>
47
+ """
45
48
 
46
49
  if action = (options.action || options.inspect)
47
- addAction($actions, action.label, action.callback)
50
+ addAction(action.label, action.callback)
48
51
 
49
- addAction($actions, 'Close', close)
52
+ addAction('Close', close)
50
53
 
51
- state.$toast = $toast
54
+ document.body.appendChild(state.element)
52
55
 
53
56
  close = ->
54
57
  if isOpen()
55
- state.$toast.remove()
56
- state.$toast = null
58
+ e.remove(state.element)
59
+ state.element = null
57
60
 
58
61
  # The framework is reset between tests
59
62
  up.on 'up:framework:reset', reset
@@ -62,5 +65,3 @@ up.toast = (($) ->
62
65
  close: close
63
66
  reset: reset
64
67
  isOpen: isOpen
65
-
66
- )(jQuery)
@@ -19,27 +19,36 @@ A gray triangle points to the element.
19
19
  To change the styling, simply override the [CSS rules](https://github.com/unpoly/unpoly/blob/master/lib/assets/stylesheets/unpoly/tooltip.sass) for the `.up-tooltip` selector and its `:after`
20
20
  selector that is used for the triangle.
21
21
 
22
- The HTML of a tooltip element is simply this:
22
+ The HTML of a tooltip element looks like this:
23
23
 
24
24
  <div class="up-tooltip">
25
- Show all decks
25
+ <div class="up-tooltip-content">
26
+ Tooltip text here
27
+ </div>
26
28
  </div>
27
29
 
28
- The tooltip element is appended to the end of `<body>`.
30
+ The tooltip element is appended to the [viewport](/up.viewport) of the anchor element.
29
31
 
30
- @class up.tooltip
32
+ @module up.tooltip
31
33
  ###
32
- up.tooltip = (($) ->
34
+ up.tooltip = do ->
33
35
 
34
36
  u = up.util
37
+ e = up.element
35
38
 
36
39
  ###**
37
40
  Configures defaults for future tooltips.
38
41
 
39
42
  @property up.tooltip.config
40
43
  @param {string} [config.position]
41
- The default position of tooltips relative to the element.
42
- Can be `'top'`, `'right'`, `'bottom'` or `'left'`.
44
+ The default position of tooltips relative to the opening element.
45
+
46
+ Valid values are `'top'`, `'right'`, `'bottom'` or `'left'`.
47
+ @param {string} [config.align]
48
+ Defines the alignment of the tooltip along its side.
49
+
50
+ When the tooltip's `{ position }` is `'top'` or `'bottom'`, valid `{ align }` values are `'left'`, `center'` and `'right'`.
51
+ When the tooltip's `{ position }` is `'left'` or `'right'`, valid `{ align }` values are `top'`, `center'` and `bottom'`.
43
52
  @param {string} [config.openAnimation='fade-in']
44
53
  The animation used to open a tooltip.
45
54
  @param {string} [config.closeAnimation='fade-out']
@@ -54,8 +63,9 @@ up.tooltip = (($) ->
54
63
  The timing function controlling the acceleration of the closing animation.
55
64
  @stable
56
65
  ###
57
- config = u.config
66
+ config = new up.Config
58
67
  position: 'top'
68
+ align: 'center'
59
69
  openAnimation: 'fade-in'
60
70
  closeAnimation: 'fade-out'
61
71
  openDuration: 100
@@ -63,58 +73,45 @@ up.tooltip = (($) ->
63
73
  openEasing: null
64
74
  closeEasing: null
65
75
 
66
- state = u.config
76
+ state = new up.Config
67
77
  phase: 'closed' # can be 'opening', 'opened', 'closing' and 'closed'
68
- $anchor: null # the element to which the tooltip is anchored
69
- $tooltip: null # the tooltiop element
78
+ anchor: null # the element to which the tooltip is anchored
79
+ tooltip: null # the .up-tooltip element
80
+ content: null # the .up-tooltip-content element
81
+ tether: null # the up.Tether instance controlling the tooltip's position
70
82
  position: null # the position of the tooltip element relative to its anchor
83
+ align: null
71
84
 
72
- chain = new u.DivertibleChain()
85
+ chain = new up.DivertibleChain()
73
86
 
74
87
  reset = ->
75
- # Destroy the tooltip container regardless whether it's currently in a closing animation
76
- state.$tooltip?.remove()
88
+ state.tether?.destroy()
77
89
  state.reset()
78
90
  chain.reset()
79
91
  config.reset()
80
92
 
81
- align = ->
82
- style = {}
83
- tooltipBox = u.measure(state.$tooltip)
93
+ createElement = (options) ->
94
+ state.tether = new up.Tether(u.only(state, 'anchor', 'position', 'align'))
95
+ state.tooltip = e.affix(state.tether.root, '.up-tooltip', 'up-position': state.position, 'up-align': state.align)
96
+ state.content = e.affix(state.tooltip, '.up-tooltip-content')
84
97
 
85
- if u.isFixed(state.$anchor)
86
- linkBox = state.$anchor.get(0).getBoundingClientRect()
87
- style.position = 'fixed'
98
+ if options.text
99
+ state.content.innerText = options.text
88
100
  else
89
- linkBox = u.measure(state.$anchor)
90
-
91
- switch state.position
92
- when 'top'
93
- style.top = linkBox.top - tooltipBox.height
94
- style.left = linkBox.left + 0.5 * (linkBox.width - tooltipBox.width)
95
- when 'left'
96
- style.top = linkBox.top + 0.5 * (linkBox.height - tooltipBox.height)
97
- style.left = linkBox.left - tooltipBox.width
98
- when 'right'
99
- style.top = linkBox.top + 0.5 * (linkBox.height - tooltipBox.height)
100
- style.left = linkBox.left + linkBox.width
101
- when 'bottom'
102
- style.top = linkBox.top + linkBox.height
103
- style.left = linkBox.left + 0.5 * (linkBox.width - tooltipBox.width)
104
- else
105
- up.fail("Unknown position option '%s'", state.position)
106
-
107
- state.$tooltip.attr('up-position', state.position)
108
- u.writeInlineStyle(state.$tooltip, style)
101
+ state.content.innerHTML = options.html
109
102
 
110
- createElement = (options) ->
111
- $element = u.$createElementFromSelector('.up-tooltip')
112
- if u.isGiven(options.text)
113
- $element.text(options.text)
114
- else
115
- $element.html(options.html)
116
- $element.appendTo(document.body)
117
- state.$tooltip = $element
103
+ ###**
104
+ Forces the tooltip to update its position relative to its anchor element.
105
+
106
+ Unpoly will automatically keep tooltips aligned when
107
+ the document is resized or scrolled. Complex layout changes may make
108
+ it necessary to call this function.
109
+
110
+ @function up.tooltip.sync
111
+ @experimental
112
+ ###
113
+ syncPosition = ->
114
+ state.tether?.sync()
118
115
 
119
116
  ###**
120
117
  Opens a tooltip over the given element.
@@ -125,9 +122,7 @@ up.tooltip = (($) ->
125
122
 
126
123
  In order to attach a tooltip to a `<span class="help">?</span>`:
127
124
 
128
- up.tooltip.attach('.help', {
129
- text: 'Enter multiple words or phrases'
130
- });
125
+ up.tooltip.attach('.help', { text: 'Useful info' })
131
126
 
132
127
  @function up.tooltip.attach
133
128
  @param {Element|jQuery|string} elementOrSelector
@@ -141,33 +136,40 @@ up.tooltip = (($) ->
141
136
 
142
137
  Make sure to escape any user-provided text before passing it as this option,
143
138
  or use `options.text` (which automatically escapes).
144
- @param {string} [options.position='top']
145
- The position of the tooltip.
146
- Can be `'top'`, `'right'`, `'bottom'` or `'left'`.
139
+ @param {string} [options.position]
140
+ The tooltip's position relative to the opening element.
141
+
142
+ Valid values are `'top'`, `'right'`, `'bottom'` or `'left'`.
143
+ @param {string} [options.align]
144
+ Defines the alignment of the tooltip along its side.
145
+
146
+ When the tooltip's `{ position }` is `'top'` or `'bottom'`, valid `{ align }` values are `'left'`, `center'` and `'right'`.
147
+ When the tooltip's `{ position }` is `'left'` or `'right'`, valid `{ align }` values are `top'`, `center'` and `bottom'`.
147
148
  @param {string} [options.animation]
148
149
  The [animation](/up.motion) to use when opening the tooltip.
149
150
  @return {Promise}
150
151
  A promise that will be fulfilled when the tooltip's opening animation has finished.
151
152
  @stable
152
153
  ###
153
- attachAsap = (elementOrSelector, options = {}) ->
154
+ attachAsap = (elementOrSelector, options) ->
154
155
  chain.asap closeNow, (-> attachNow(elementOrSelector, options))
155
156
 
156
- attachNow = (elementOrSelector, options) ->
157
- $anchor = $(elementOrSelector)
158
- options = u.options(options)
159
- html = u.option(options.html, $anchor.attr('up-tooltip-html'))
160
- text = u.option(options.text, $anchor.attr('up-tooltip'))
161
- position = u.option(options.position, $anchor.attr('up-position'), config.position)
162
- animation = u.option(options.animation, u.castedAttr($anchor, 'up-animation'), config.openAnimation)
163
- animateOptions = up.motion.animateOptions(options, $anchor, duration: config.openDuration, easing: config.openEasing)
157
+ attachNow = (elementOrSelector, options = {}) ->
158
+ anchor = e.get(elementOrSelector)
159
+ html = options.html ? anchor.getAttribute('up-tooltip-html')
160
+ text = options.text ? anchor.getAttribute('up-tooltip')
161
+ position = options.position ? anchor.getAttribute('up-position') ? config.position
162
+ align = options.align ? anchor.getAttribute('up-align') ? config.align
163
+ animation = options.animation ? e.booleanOrStringAttr(anchor, 'up-animation') ? config.openAnimation
164
+ animateOptions = up.motion.animateOptions(options, anchor, duration: config.openDuration, easing: config.openEasing)
164
165
 
165
166
  state.phase = 'opening'
166
- state.$anchor = $anchor
167
- createElement(text: text, html: html)
167
+ state.anchor = anchor
168
168
  state.position = position
169
- align()
170
- up.animate(state.$tooltip, animation, animateOptions).then ->
169
+ state.align = align
170
+ createElement({ text, html })
171
+ syncPosition()
172
+ up.animate(state.tooltip, animation, animateOptions).then ->
171
173
  state.phase = 'opened'
172
174
 
173
175
  ###**
@@ -193,10 +195,13 @@ up.tooltip = (($) ->
193
195
  animateOptions = up.motion.animateOptions(options, duration: config.closeDuration, easing: config.closeEasing)
194
196
  u.assign(options, animateOptions)
195
197
  state.phase = 'closing'
196
- up.destroy(state.$tooltip, options).then ->
198
+ up.destroy(state.tooltip, options).then ->
197
199
  state.phase = 'closed'
198
- state.$tooltip = null
199
- state.$anchor = null
200
+ state.tether.destroy()
201
+ state.tether = null
202
+ state.tooltip = null
203
+ state.content = null
204
+ state.anchor = null
200
205
 
201
206
  ###**
202
207
  Returns whether a tooltip is currently showing.
@@ -224,9 +229,14 @@ up.tooltip = (($) ->
224
229
  The animation used to open the tooltip.
225
230
  Defaults to [`up.tooltip.config.openAnimation`](/up.tooltip.config).
226
231
  @param {string} [up-position]
227
- The default position of tooltips relative to the element.
228
- Can be either `"top"` or `"bottom"`.
229
- Defaults to [`up.tooltip.config.position`](/up.tooltip.config).
232
+ The tooltip's position relative to the opening element.
233
+
234
+ Valid values are `'top'`, `'right'`, `'bottom'` or `'left'`.
235
+ @param {string} [up-align]
236
+ Defines the alignment of the tooltip along its side.
237
+
238
+ When the tooltip's `{ position }` is `'top'` or `'bottom'`, valid `{ align }` values are `'left'`, `center'` and `'right'`.
239
+ When the tooltip's `{ position }` is `'left'` or `'right'`, valid `{ align }` values are `top'`, `center'` and `bottom'`.
230
240
  @stable
231
241
  ###
232
242
 
@@ -238,16 +248,16 @@ up.tooltip = (($) ->
238
248
  @selector [up-tooltip-html]
239
249
  @stable
240
250
  ###
241
- up.compiler '[up-tooltip], [up-tooltip-html]', ($opener) ->
251
+ up.compiler '[up-tooltip], [up-tooltip-html]', (opener) ->
242
252
  # Don't register these events on document since *every*
243
253
  # mouse move interaction bubbles up to the document.
244
- $opener.on('mouseenter', -> attachAsap($opener))
245
- $opener.on('mouseleave', -> closeAsap())
254
+ opener.addEventListener 'mouseenter', -> attachAsap(opener)
255
+ opener.addEventListener 'mouseleave', -> closeAsap()
246
256
 
247
257
  # We close the tooltip when someone clicks on the document.
248
258
  # We also need to listen to up:action:consumed in case an [up-instant] link
249
259
  # was followed on mousedown.
250
- up.on 'click up:action:consumed', (event) ->
260
+ up.on 'click up:action:consumed', (_event) -> # do take an argument so we won't parse [up-data]
251
261
  closeAsap()
252
262
  # Do not halt the event chain here. The user is allowed to directly activate
253
263
  # a link in the background, even with a (now closing) tooltip open.
@@ -256,11 +266,11 @@ up.tooltip = (($) ->
256
266
  up.on 'up:framework:reset', reset
257
267
 
258
268
  # Close the tooltip when the user presses ESC.
259
- up.bus.onEscape(-> closeAsap())
269
+ up.event.onEscape(-> closeAsap())
260
270
 
261
271
  config: config
262
272
  attach: attachAsap
263
273
  isOpen: isOpen
264
274
  close: closeAsap
275
+ sync: syncPosition
265
276
 
266
- )(jQuery)
@@ -1,13 +1,17 @@
1
1
  ###**
2
2
  Utility functions
3
3
  =================
4
-
5
- Unpoly comes with a number of utility functions
6
- that might save you from loading something like [Lodash](https://lodash.com/).
7
4
 
8
- @class up.util
5
+ The `up.util` module contains functions to facilitate the work with basic JavaScript
6
+ values like lists, strings or functions.
7
+
8
+ You will recognize many functions form other utility libraries like [Lodash](https://lodash.com/).
9
+ While feature parity with Lodash is not a goal of `up.util`, you might find it sufficient
10
+ to not include another library in your asset bundle.
11
+
12
+ @module up.util
9
13
  ###
10
- up.util = (($) ->
14
+ up.util = do ->
11
15
 
12
16
  ###**
13
17
  A function that does nothing.
@@ -75,10 +79,6 @@ up.util = (($) ->
75
79
  normalized = parts.protocol + "//" + parts.hostname
76
80
  normalized += ":#{parts.port}" unless isStandardPort(parts.protocol, parts.port)
77
81
  pathname = parts.pathname
78
- # Some IEs don't include a leading slash in the #pathname property.
79
- # We have seen this in IE11 and W3Schools claims it happens in IE9 or earlier
80
- # http://www.w3schools.com/jsref/prop_anchor_pathname.asp
81
- pathname = "/#{pathname}" unless pathname[0] == '/'
82
82
  pathname = pathname.replace(/\/$/, '') if options?.stripTrailingSlash == true
83
83
  normalized += pathname
84
84
  normalized += parts.search unless options?.search == false
@@ -88,7 +88,7 @@ up.util = (($) ->
88
88
  isCrossDomain = (targetUrl) ->
89
89
  currentUrl = parseUrl(location.href)
90
90
  targetUrl = parseUrl(targetUrl)
91
- currentUrl.protocol != targetUrl.protocol || currentUrl.host != targetUrl.host
91
+ currentUrl.protocol != targetUrl.protocol || currentUrl.hostname != targetUrl.hostname
92
92
 
93
93
  ###**
94
94
  Parses the given URL into components such as hostname and path.
@@ -101,24 +101,33 @@ up.util = (($) ->
101
101
  The parsed URL as an object with
102
102
  `protocol`, `hostname`, `port`, `pathname`, `search` and `hash`
103
103
  properties.
104
- @experimental
104
+ @stable
105
105
  ###
106
- parseUrl = (urlOrAnchor) ->
107
- # In case someone passed us a $link, unwrap it
108
- if isJQuery(urlOrAnchor)
109
- urlOrAnchor = getElement(urlOrAnchor)
106
+ parseUrl = (urlOrLink) ->
107
+ if isJQuery(urlOrLink)
108
+ # In case someone passed us a $link, unwrap it
109
+ link = up.element.get(urlOrLink)
110
+ else if urlOrLink.pathname
111
+ # If we are handed a parsed URL, just return it
112
+ link = urlOrLink
113
+ else
114
+ link = document.createElement('a')
115
+ link.href = urlOrLink
110
116
 
111
- # If we are handed a parsed URL, just return it
112
- if urlOrAnchor.pathname
113
- return urlOrAnchor
117
+ # In IE11 the #hostname and #port properties of unqualified URLs are empty strings.
118
+ # We can fix this by setting the link's { href } on the link itself.
119
+ unless link.hostname
120
+ link.href = link.href
114
121
 
115
- anchor = $('<a>').attr(href: urlOrAnchor).get(0)
116
- # In IE11 the #hostname and #port properties of such a link are empty
117
- # strings. However, we can fix this by assigning the anchor its own
118
- # href because computer:
119
- # https://gist.github.com/jlong/2428561#comment-1461205
120
- anchor.href = anchor.href if isBlank(anchor.hostname)
121
- anchor
122
+ # Some IEs don't include a leading slash in the #pathname property.
123
+ # We have confirmed this in IE11 and earlier.
124
+ unless link.pathname[0] == '/'
125
+ # Only copy the link into an object when we need to (to change a property).
126
+ # Note that we're parsing a lot of URLs for [up-active].
127
+ link = only(link, 'protocol', 'hostname', 'port', 'pathname', 'search', 'hash')
128
+ link.pathname = '/' + link.pathname
129
+
130
+ link
122
131
 
123
132
  ###**
124
133
  @function up.util.normalizeMethod
@@ -137,122 +146,6 @@ up.util = (($) ->
137
146
  methodAllowsPayload = (method) ->
138
147
  method != 'GET' && method != 'HEAD'
139
148
 
140
- ###**
141
- @function $createElementFromSelector
142
- @internal
143
- ###
144
- $createElementFromSelector = (selector) ->
145
- path = selector.split(/[ >]/)
146
- $root = null
147
- for depthSelector, iteration in path
148
- conjunction = depthSelector.match(/(^|\.|\#)[A-Za-z0-9\-_]+/g)
149
- tag = "div"
150
- classes = []
151
- id = null
152
- for expression in conjunction
153
- switch expression[0]
154
- when "."
155
- classes.push expression.substr(1)
156
- when "#"
157
- id = expression.substr(1)
158
- else
159
- tag = expression
160
- html = "<" + tag
161
- html += " class=\"" + classes.join(" ") + "\"" if classes.length
162
- html += " id=\"" + id + "\"" if id
163
- html += ">"
164
- $element = $(html)
165
- $element.appendTo($parent) if $parent
166
- $root = $element if iteration == 0
167
- $parent = $element
168
- $root
169
-
170
- ###**
171
- @function $createPlaceHolder
172
- @internal
173
- ###
174
- $createPlaceholder = (selector, container = document.body) ->
175
- $placeholder = $createElementFromSelector(selector)
176
- $placeholder.addClass('up-placeholder')
177
- $placeholder.appendTo(container)
178
- $placeholder
179
-
180
- ###**
181
- Returns a CSS selector that matches the given element as good as possible.
182
-
183
- This uses, in decreasing order of priority:
184
-
185
- - The element's `up-id` attribute
186
- - The element's ID
187
- - The element's name
188
- - The element's classes
189
- - The element's tag names
190
-
191
- @function up.util.selectorForElement
192
- @param {string|Element|jQuery}
193
- The element for which to create a selector.
194
- @experimental
195
- ###
196
- selectorForElement = (element) ->
197
- $element = $(element)
198
- selector = undefined
199
-
200
- if isSingletonElement($element)
201
- selector = elementTagName($element)
202
- else if upId = presence($element.attr("up-id"))
203
- selector = attributeSelector('up-id', upId)
204
- else if id = presence($element.attr("id"))
205
- if id.match(/^[a-z0-9\-_]+$/i)
206
- selector = "##{id}"
207
- else
208
- selector = attributeSelector('id', id)
209
- else if name = presence($element.attr("name"))
210
- selector = elementTagName($element) + attributeSelector('name', name)
211
- else if classes = presence(nonUpClasses($element))
212
- selector = ''
213
- for klass in classes
214
- selector += ".#{klass}"
215
- else if ariaLabel = presence($element.attr("aria-label"))
216
- selector = attributeSelector('aria-label', ariaLabel)
217
- else
218
- selector = elementTagName($element)
219
-
220
- return selector
221
-
222
- isSingletonElement = ($element) ->
223
- # jQuery's is() returns true if at least one element
224
- # in the collection matches the selector
225
- $element.is('html, body, head, title')
226
-
227
- # isGoodSelector = (selector) ->
228
- # selector.indexOf('#') >= -1 || selector.indexOf('[') >= -1
229
-
230
- # defineMemoizedGetter = (obj, property, fetchFn) ->
231
- # cacheProperty = "_#{property}Cache"
232
- # Object.defineProperty methods, property,
233
- # get: ->
234
- # @[cacheProperty] ||= [fetchFn(this)]
235
- # @[cacheProperty][0]
236
-
237
- elementTagName = ($element) ->
238
- $element.prop('tagName').toLowerCase()
239
-
240
- attributeSelector = (attribute, value) ->
241
- value = value.replace(/"/g, '\\"')
242
- "[#{attribute}=\"#{value}\"]"
243
-
244
- nonUpClasses = ($element) ->
245
- classString = $element.attr('class') || ''
246
- classes = splitValues(classString)
247
- reject classes, (klass) -> klass.match(/^up-/)
248
-
249
- # jQuery's implementation of $(...) cannot create elements that have
250
- # an <html> or <body> tag. So we're using native elements.
251
- # Also IE9 cannot set innerHTML on a <html> or <head> element.
252
- createElementFromHtml = (html) ->
253
- parser = new DOMParser()
254
- parser.parseFromString(html, 'text/html')
255
-
256
149
  assignPolyfill = (target, sources...) ->
257
150
  for source in sources
258
151
  for own key, value of source
@@ -278,23 +171,11 @@ up.util = (($) ->
278
171
  @function up.util.values
279
172
  @param {Object} object
280
173
  @return {Array<string>}
281
- @experimental
282
- ###
283
- objectValues = Object.values || valuesPolyfill
284
-
285
- ###**
286
- Returns a new string with whitespace removed from the beginning
287
- and end of the given string.
288
-
289
- @param {string}
290
- A string that might have whitespace at the beginning and end.
291
- @return {string}
292
- The trimmed string.
293
174
  @stable
294
175
  ###
295
- trim = $.trim
176
+ objectValues = Object.values || valuesPolyfill
296
177
 
297
- listBlock = (block) ->
178
+ iteratee = (block) ->
298
179
  if isString(block)
299
180
  (item) -> item[block]
300
181
  else
@@ -304,8 +185,8 @@ up.util = (($) ->
304
185
  Translate all items in an array to new array of items.
305
186
 
306
187
  @function up.util.map
307
- @param {Array<T>} array
308
- @param {Function(T, number): any|String} block
188
+ @param {Array} array
189
+ @param {Function(element, index): any|String} block
309
190
  A function that will be called with each element and (optional) iteration index.
310
191
 
311
192
  You can also pass a property name as a String,
@@ -316,17 +197,27 @@ up.util = (($) ->
316
197
  ###
317
198
  map = (array, block) ->
318
199
  return [] if array.length == 0
319
- block = listBlock(block)
200
+ block = iteratee(block)
320
201
  for item, index in array
321
202
  block(item, index)
322
203
 
204
+ ###**
205
+ @function up.util.mapObject
206
+ @internal
207
+ ###
208
+ mapObject = (array, pairer) ->
209
+ merger = (object, pair) ->
210
+ object[pair[0]] = pair[1]
211
+ return object
212
+ map(array, pairer).reduce(merger, {})
213
+
323
214
  ###**
324
215
  Calls the given function for each element (and, optional, index)
325
216
  of the given array.
326
217
 
327
218
  @function up.util.each
328
- @param {Array<T>} array
329
- @param {Function(T, number)} block
219
+ @param {Array} array
220
+ @param {Function(element, index)} block
330
221
  A function that will be called with each element and (optional) iteration index.
331
222
  @stable
332
223
  ###
@@ -341,7 +232,7 @@ up.util = (($) ->
341
232
 
342
233
  @function up.util.times
343
234
  @param {number} count
344
- @param {Function} block
235
+ @param {Function()} block
345
236
  @stable
346
237
  ###
347
238
  times = (count, block) ->
@@ -416,41 +307,91 @@ up.util = (($) ->
416
307
  ###**
417
308
  Return whether the given argument is considered to be blank.
418
309
 
419
- This returns `true` for:
310
+ By default, this function returns `true` for:
420
311
 
421
312
  - `undefined`
422
313
  - `null`
423
314
  - Empty strings
424
315
  - Empty arrays
425
- - An object without own enumerable properties
316
+ - A plain object without own enumerable properties
426
317
 
427
318
  All other arguments return `false`.
428
319
 
320
+ To check implement blank-ness checks for user-defined classes,
321
+ see `up.util.isBlank.key`.
322
+
429
323
  @function up.util.isBlank
430
- @param object
324
+ @param value
325
+ The value is to check.
431
326
  @return {boolean}
327
+ Whether the value is blank.
432
328
  @stable
433
329
  ###
434
- isBlank = (object) ->
435
- return true if isMissing(object)
436
- return false if isFunction(object)
437
- return true if isObject(object) && Object.keys(object).length == 0 # object
438
- return true if object.length == 0 # string, array, jQuery
330
+ isBlank = (value) ->
331
+ if isMissing(value)
332
+ return true
333
+ if isObject(value) && value[isBlank.key]
334
+ return value[isBlank.key]()
335
+ if isString(value) || isList(value)
336
+ return value.length == 0
337
+ if isOptions(value)
338
+ return Object.keys(value).length == 0
439
339
  return false
440
340
 
341
+ ###**
342
+ This property contains the name of a method that user-defined classes
343
+ may implement to hook into the `up.util.isBlank()` protocol.
344
+
345
+ \#\#\# Example
346
+
347
+ We have a user-defined `Account` class that we want to use with `up.util.isBlank()`:
348
+
349
+ ```
350
+ class Account {
351
+ constructor(email) {
352
+ this.email = email
353
+ }
354
+
355
+ [up.util.isBlank.key]() {
356
+ return up.util.isBlank(this.email)
357
+ }
358
+ }
359
+ ```
360
+
361
+ Note that the protocol method is not actually named `'up.util.isBlank.key'`.
362
+ Instead it is named after the *value* of the `up.util.isBlank.key` property.
363
+ To do so, the code sample above is using a
364
+ [computed property name](https://medium.com/front-end-weekly/javascript-object-creation-356e504173a8)
365
+ in square brackets.
366
+
367
+ We may now use `Account` instances with `up.util.isBlank()`:
368
+
369
+ ```
370
+ foo = new Account('foo@foo.com')
371
+ bar = new Account('')
372
+
373
+ console.log(up.util.isBlank(foo)) // prints false
374
+ console.log(up.util.isBlank(bar)) // prints true
375
+ ```
376
+
377
+ @property up.util.isBlank.key
378
+ @experimental
379
+ ###
380
+ isBlank.key = 'up.util.isBlank'
381
+
441
382
  ###**
442
383
  Returns the given argument if the argument is [present](/up.util.isPresent),
443
384
  otherwise returns `undefined`.
444
385
 
445
386
  @function up.util.presence
446
- @param object
447
- @param {Function(T): boolean} [tester=up.util.isPresent]
387
+ @param value
388
+ @param {Function(value): boolean} [tester=up.util.isPresent]
448
389
  The function that will be used to test whether the argument is present.
449
- @return {T|undefined}
390
+ @return {any|undefined}
450
391
  @stable
451
392
  ###
452
- presence = (object, tester = isPresent) ->
453
- if tester(object) then object else undefined
393
+ presence = (value, tester = isPresent) ->
394
+ if tester(value) then value else undefined
454
395
 
455
396
  ###**
456
397
  Returns whether the given argument is not [blank](/up.util.isBlank).
@@ -491,7 +432,7 @@ up.util = (($) ->
491
432
  @function up.util.isBoolean
492
433
  @param object
493
434
  @return {boolean}
494
- @experimental
435
+ @stable
495
436
  ###
496
437
  isBoolean = (object) ->
497
438
  typeof(object) == 'boolean' || object instanceof Boolean
@@ -539,7 +480,7 @@ up.util = (($) ->
539
480
  (typeOfResult == 'object' && !isNull(object)) || typeOfResult == 'function'
540
481
 
541
482
  ###**
542
- Returns whether the given argument is a DOM element.
483
+ Returns whether the given argument is a [DOM element](https://developer.mozilla.org/de/docs/Web/API/Element).
543
484
 
544
485
  @function up.util.isElement
545
486
  @param object
@@ -547,10 +488,10 @@ up.util = (($) ->
547
488
  @stable
548
489
  ###
549
490
  isElement = (object) ->
550
- !!(object && object.nodeType == 1)
491
+ object instanceof Element
551
492
 
552
493
  ###**
553
- Returns whether the given argument is a jQuery collection.
494
+ Returns whether the given argument is a [jQuery collection](https://learn.jquery.com/using-jquery-core/jquery-object/).
554
495
 
555
496
  @function up.util.isJQuery
556
497
  @param object
@@ -558,7 +499,7 @@ up.util = (($) ->
558
499
  @stable
559
500
  ###
560
501
  isJQuery = (object) ->
561
- object instanceof jQuery
502
+ up.browser.canJQuery() && (object instanceof jQuery)
562
503
 
563
504
  ###**
564
505
  Returns whether the given argument is an object with a `then` method.
@@ -596,66 +537,183 @@ up.util = (($) ->
596
537
  object instanceof FormData
597
538
 
598
539
  ###**
599
- Converts the given array-like argument into an array.
540
+ Converts the given [array-like value](/up.util.isList) into an array.
600
541
 
601
- Returns the array.
542
+ If the given value is already an array, it is returned unchanged.
602
543
 
603
544
  @function up.util.toArray
604
545
  @param object
605
546
  @return {Array}
606
547
  @stable
607
548
  ###
608
- toArray = (object) ->
609
- Array.prototype.slice.call(object)
549
+ toArray = (value) ->
550
+ if isArray(value)
551
+ value
552
+ else
553
+ Array.prototype.slice.call(value)
554
+
555
+ ###***
556
+ Returns whether the given argument is an array-like value.
557
+
558
+ Return true for `Array`, a
559
+ [`NodeList`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList),
560
+ the [arguments object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments)
561
+ or a jQuery collection.
562
+
563
+ Use [`up.util.isArray()`](/up.util.isArray) to test whether a value is an actual `Array`.
564
+
565
+ @function up.util.isList
566
+ @param value
567
+ @return {Boolean}
568
+ @experimental
569
+ ###
570
+ isList = (value) ->
571
+ isArray(value) ||
572
+ isNodeList(value) ||
573
+ isArguments(value) ||
574
+ isJQuery(value) ||
575
+ isHTMLCollection(value)
610
576
 
611
577
  ###**
612
- Returns a shallow copy of the given array or object.
578
+ Returns whether the given value is a [`NodeList`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList).
613
579
 
614
- @function up.util.copy
615
- @param {Object|Array} object
616
- @return {Object|Array}
617
- @stable
580
+ `NodeLists` are array-like objects returned by [`document.querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll).
581
+
582
+ @function up.util.isNodeList
583
+ @param value
584
+ @return {Boolean}
585
+ @internal
618
586
  ###
619
- copy = (object, deep) ->
620
- if isArray(object)
621
- object = object.slice()
622
- # copied = true
623
- else if isOptions(object)
624
- object = assign({}, object)
625
- # copied = true
626
- # if copied && deep
627
- # for key, value of object
628
- # object[key] = copy(value, true)
629
- object
587
+ isNodeList = (value) ->
588
+ value instanceof NodeList
589
+
590
+ isHTMLCollection = (value) ->
591
+ value instanceof HTMLCollection
630
592
 
631
593
  # ###**
632
- # Returns a deep copy of the given array or object.
594
+ # Returns whether the given value is an [`HTMLCollection`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection).
633
595
  #
634
- # @function up.util.deepCopy
635
- # @param {Object|Array} object
636
- # @return {Object|Array}
637
- # @internal
596
+ # @function up.util.isHtmlCollection
597
+ # @param value
598
+ # @return {Boolean}
599
+ # @experimental
638
600
  # ###
639
- # deepCopy = (object) ->
640
- # copy(object, true)
601
+ # isHtmlCollection = (value) ->
602
+ # value instanceof HTMLCollection
641
603
 
642
604
  ###**
643
- If given a jQuery collection, returns the first native DOM element in the collection.
644
- If given a string, returns the first element matching that string.
645
- If given any other argument, returns the argument unchanged.
605
+ Returns whether the given value is an [arguments object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments).
646
606
 
647
- @function up.util.element
648
- @param {jQuery|Element|String} object
649
- @return {Element}
607
+ @function up.util.isArguments
608
+ @param value
609
+ @return {Boolean}
650
610
  @internal
651
611
  ###
652
- getElement = (object) ->
653
- if isJQuery(object)
654
- object.get(0)
655
- else if isString(object)
656
- $(object).get(0)
612
+ isArguments = (value) ->
613
+ Object.prototype.toString.call(value) == '[object Arguments]'
614
+
615
+ ###**
616
+ @function up.util.wrapList
617
+ @return {Array|NodeList|jQuery}
618
+ @internal
619
+ ###
620
+ wrapList = (value) ->
621
+ if isList(value)
622
+ value
623
+ else if isMissing(value)
624
+ []
657
625
  else
658
- object
626
+ [value]
627
+
628
+ ###**
629
+ Returns a shallow copy of the given value.
630
+
631
+ \#\#\# Copying protocol
632
+
633
+ - By default `up.util.copy()` can copy [array-like values](/up.util.isList),
634
+ plain objects and `Date` instances.
635
+ - Array-like objects are copied into new arrays.
636
+ - Unsupported types of values are returned unchanged.
637
+ - To make the copying protocol work with user-defined class,
638
+ see `up.util.copy.key`.
639
+ - Immutable objects, like strings or numbers, do not need to be copied.
640
+
641
+ @function up.util.copy
642
+ @param {any} object
643
+ @return {any}
644
+ @stable
645
+ ###
646
+ copy = (value, deep) ->
647
+ if isObject(value) && value[copy.key]
648
+ value = value[copy.key]()
649
+ else if isList(value)
650
+ value = Array.prototype.slice.call(value)
651
+ copied = true
652
+ else if isOptions(value)
653
+ value = assign({}, value)
654
+ copied = true
655
+ if copied && deep
656
+ for k, v of value
657
+ value[k] = copy(v, true)
658
+ value
659
+
660
+ ###**
661
+ This property contains the name of a method that user-defined classes
662
+ may implement to hook into the `up.util.copy()` protocol.
663
+
664
+ \#\#\# Example
665
+
666
+ We have a user-defined `Account` class that we want to use with `up.util.copy()`:
667
+
668
+ ```
669
+ class Account {
670
+ constructor(email) {
671
+ this.email = email
672
+ }
673
+
674
+ [up.util.copy.key]() {
675
+ return new Account(this.email)
676
+ }
677
+ }
678
+ ```
679
+
680
+ Note that the protocol method is not actually named `'up.util.copy.key'`.
681
+ Instead it is named after the *value* of the `up.util.copy.key` property.
682
+ To do so, the code sample above is using a
683
+ [computed property name](https://medium.com/front-end-weekly/javascript-object-creation-356e504173a8)
684
+ in square brackets.
685
+
686
+ We may now use `Account` instances with `up.util.copy()`:
687
+
688
+ ```
689
+ original = new User('foo@foo.com')
690
+
691
+ copy = up.util.copy(original)
692
+ console.log(copy.email) // prints 'foo@foo.com'
693
+
694
+ original.email = 'bar@bar.com' // change the original
695
+ console.log(copy.email) // still prints 'foo@foo.com'
696
+ ```
697
+
698
+ @property up.util.copy.key
699
+ @param {string} key
700
+ @experimental
701
+ ###
702
+ copy.key = 'up.util.copy'
703
+
704
+ # Implement up.util.copy protocol for Date
705
+ Date.prototype[copy.key] = -> new Date(+@)
706
+
707
+ ###**
708
+ Returns a deep copy of the given array or object.
709
+
710
+ @function up.util.deepCopy
711
+ @param {Object|Array} object
712
+ @return {Object|Array}
713
+ @internal
714
+ ###
715
+ deepCopy = (object) ->
716
+ copy(object, true)
659
717
 
660
718
  ###**
661
719
  Creates a new object by merging together the properties from the given objects.
@@ -716,34 +774,20 @@ up.util = (($) ->
716
774
  {}
717
775
 
718
776
  ###**
719
- Returns the first argument that is considered [given](/up.util.isGiven).
720
-
721
- This function is useful when you have multiple option sources and the value can be boolean.
722
- In that case you cannot change the sources with a `||` operator
723
- (since that doesn't short-circuit at `false`).
724
-
725
- @function up.util.option
726
- @param {Array} args...
727
- @internal
728
- ###
729
- option = (args...) ->
730
- detect(args, isGiven)
731
-
732
- ###**
733
- Passes each element in the given array to the given function.
777
+ Passes each element in the given [array-like value](/up.util.isList) to the given function.
734
778
  Returns the first element for which the function returns a truthy value.
735
779
 
736
780
  If no object matches, returns `undefined`.
737
781
 
738
- @function up.util.detect
739
- @param {Array<T>} array
740
- @param {Function(T): boolean} tester
782
+ @function up.util.find
783
+ @param {List<T>} list
784
+ @param {Function(value): boolean} tester
741
785
  @return {T|undefined}
742
786
  @stable
743
787
  ###
744
- detect = (array, tester) ->
788
+ findInList = (list, tester) ->
745
789
  match = undefined
746
- for element in array
790
+ for element in list
747
791
  if tester(element)
748
792
  match = element
749
793
  break
@@ -751,41 +795,57 @@ up.util = (($) ->
751
795
 
752
796
  ###**
753
797
  Returns whether the given function returns a truthy value
754
- for any element in the given array.
798
+ for any element in the given [array-like value](/up.util.isList).
755
799
 
756
- @function up.util.any
757
- @param {Array<T>} array
758
- @param {Function(T, number): boolean} tester
800
+ @function up.util.some
801
+ @param {List} list
802
+ @param {Function(value, index): boolean} tester
759
803
  A function that will be called with each element and (optional) iteration index.
760
804
 
761
805
  @return {boolean}
806
+ @stable
807
+ ###
808
+ some = (list, tester) ->
809
+ !!findResult(list, tester)
810
+
811
+ ###**
812
+ Consecutively calls the given function which each element
813
+ in the given array. Returns the first truthy return value.
814
+
815
+ Returned `undefined` iff the function does not return a truthy
816
+ value for any element in the array.
817
+
818
+ @function up.util.findResult
819
+ @param {Array} array
820
+ @param {Function(element): any} tester
821
+ A function that will be called with each element and (optional) iteration index.
822
+
823
+ @return {any|undefined}
762
824
  @experimental
763
825
  ###
764
- any = (array, tester) ->
765
- tester = listBlock(tester)
766
- match = false
826
+ findResult = (array, tester) ->
827
+ tester = iteratee(tester)
767
828
  for element, index in array
768
- if tester(element, index)
769
- match = true
770
- break
771
- match
829
+ if result = tester(element, index)
830
+ return result
831
+ return undefined
772
832
 
773
833
  ###**
774
834
  Returns whether the given function returns a truthy value
775
- for all elements in the given array.
835
+ for all elements in the given [array-like value](/up.util.isList).
776
836
 
777
- @function up.util.all
778
- @param {Array<T>} array
779
- @param {Function(T, number): boolean} tester
837
+ @function up.util.every
838
+ @param {List} list
839
+ @param {Function(element, index): boolean} tester
780
840
  A function that will be called with each element and (optional) iteration index.
781
841
 
782
842
  @return {boolean}
783
843
  @experimental
784
844
  ###
785
- all = (array, tester) ->
786
- tester = listBlock(tester)
845
+ every = (list, tester) ->
846
+ tester = iteratee(tester)
787
847
  match = true
788
- for element, index in array
848
+ for element, index in list
789
849
  unless tester(element, index)
790
850
  match = false
791
851
  break
@@ -801,7 +861,7 @@ up.util = (($) ->
801
861
  @stable
802
862
  ###
803
863
  compact = (array) ->
804
- select array, isGiven
864
+ filterList array, isGiven
805
865
 
806
866
  ###**
807
867
  Returns the given array without duplicates.
@@ -821,16 +881,16 @@ up.util = (($) ->
821
881
  for which uniquness is computed.
822
882
 
823
883
  @function up.util.uniqBy
824
- @param {Array<T>} array
825
- @param {Function<T>: any} array
826
- @return {Array<T>}
884
+ @param {Array} array
885
+ @param {Function(value): any} array
886
+ @return {Array}
827
887
  @experimental
828
888
  ###
829
889
  uniqBy = (array, mapper) ->
830
890
  return array if array.length < 2
831
- mapper = listBlock(mapper)
891
+ mapper = iteratee(mapper)
832
892
  set = new Set()
833
- select array, (elem, index) ->
893
+ filterList array, (elem, index) ->
834
894
  mapped = mapper(elem, index)
835
895
  if set.has(mapped)
836
896
  false
@@ -857,36 +917,36 @@ up.util = (($) ->
857
917
  set
858
918
 
859
919
  ###**
860
- Returns all elements from the given array that return
920
+ Returns all elements from the given [array-like value](/up.util.isList) that return
861
921
  a truthy value when passed to the given function.
862
922
 
863
- @function up.util.select
864
- @param {Array<T>} array
865
- @param {Function(T, number): boolean} tester
866
- @return {Array<T>}
923
+ @function up.util.filter
924
+ @param {List} list
925
+ @param {Function(value, index): boolean} tester
926
+ @return {Array}
867
927
  @stable
868
928
  ###
869
- select = (array, tester) ->
870
- tester = listBlock(tester)
929
+ filterList = (list, tester) ->
930
+ tester = iteratee(tester)
871
931
  matches = []
872
- each array, (element, index) ->
932
+ each list, (element, index) ->
873
933
  if tester(element, index)
874
934
  matches.push(element)
875
935
  matches
876
936
 
877
937
  ###**
878
- Returns all elements from the given array that do not return
938
+ Returns all elements from the given [array-like value](/up.util.isList) that do not return
879
939
  a truthy value when passed to the given function.
880
940
 
881
941
  @function up.util.reject
882
- @param {Array<T>} array
883
- @param {Function(T, number): boolean} tester
884
- @return {Array<T>}
942
+ @param {List} list
943
+ @param {Function(element, index): boolean} tester
944
+ @return {Array}
885
945
  @stable
886
946
  ###
887
- reject = (array, tester) ->
888
- tester = listBlock(tester)
889
- select(array, (element, index) -> !tester(element, index))
947
+ reject = (list, tester) ->
948
+ tester = iteratee(tester)
949
+ filterList(list, (element, index) -> !tester(element, index))
890
950
 
891
951
  ###**
892
952
  Returns the intersection of the given two arrays.
@@ -897,74 +957,45 @@ up.util = (($) ->
897
957
  @internal
898
958
  ###
899
959
  intersect = (array1, array2) ->
900
- select array1, (element) ->
960
+ filterList array1, (element) ->
901
961
  contains(array2, element)
902
962
 
903
- addClass = (element, klassOrKlasses) ->
904
- changeClassList(element, klassOrKlasses, 'add')
905
-
906
- removeClass = (element, klassOrKlasses) ->
907
- changeClassList(element, klassOrKlasses, 'remove')
908
-
909
- changeClassList = (element, klassOrKlasses, fnName) ->
910
- classList = getElement(element).classList
911
- if isArray(klassOrKlasses)
912
- each klassOrKlasses, (klass) ->
913
- classList[fnName](klass)
914
- else
915
- classList[fnName](klassOrKlasses)
916
-
917
- addTemporaryClass = (element, klassOrKlasses) ->
918
- addClass(element, klassOrKlasses)
919
- -> removeClass(element, klassOrKlasses)
920
-
921
- hasClass = (element, klass) ->
922
- classList = getElement(element).classList
923
- classList.contains(klass)
924
-
925
- ###**
926
- Returns the first [present](/up.util.isPresent) element attribute
927
- among the given list of attribute names.
928
-
929
- @function up.util.presentAttr
930
- @internal
931
- ###
932
- presentAttr = ($element, attrNames...) ->
933
- values = ($element.attr(attrName) for attrName in attrNames)
934
- detect(values, isPresent)
935
-
936
963
  ###**
937
964
  Waits for the given number of milliseconds, the runs the given callback.
938
965
 
939
- Instead of `up.util.setTimer(0, fn)` you can also use [`up.util.nextFrame(fn)`](/up.util.nextFrame).
966
+ Instead of `up.util.timer(0, fn)` you can also use [`up.util.task(fn)`](/up.util.task).
940
967
 
941
- @function up.util.setTimer
968
+ @function up.util.timer
942
969
  @param {number} millis
943
- @param {Function} callback
970
+ @param {Function()} callback
944
971
  @stable
945
972
  ###
946
- setTimer = (millis, callback) ->
973
+ scheduleTimer = (millis, callback) ->
947
974
  setTimeout(callback, millis)
948
975
 
949
976
  ###**
950
- Schedules the given function to be called in the
951
- next JavaScript execution frame.
977
+ Pushes the given function to the [JavaScript task queue](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) (also "macrotask queue").
978
+
979
+ Equivalent to calling `setTimeout(fn, 0)`.
952
980
 
953
- @function up.util.nextFrame
954
- @param {Function} block
981
+ Also see `up.util.microtask()`.
982
+
983
+ @function up.util.task
984
+ @param {Function()} block
955
985
  @stable
956
986
  ###
957
- nextFrame = (block) ->
987
+ queueTask = (block) ->
958
988
  setTimeout(block, 0)
959
989
 
960
990
  ###**
961
- Queue a function to be executed in the next microtask.
991
+ Pushes the given function to the [JavaScript microtask queue](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/).
962
992
 
963
- @function up.util.queueMicrotask
964
- @param {Function} task
965
- @internal
993
+ @function up.util.microtask
994
+ @param {Function()} task
995
+ @return {Promise}
996
+ @experimental
966
997
  ###
967
- microtask = (task) ->
998
+ queueMicrotask = (task) ->
968
999
  Promise.resolve().then(task)
969
1000
 
970
1001
  ###**
@@ -977,202 +1008,6 @@ up.util = (($) ->
977
1008
  last = (array) ->
978
1009
  array[array.length - 1]
979
1010
 
980
- ###**
981
- Measures the drawable area of the document.
982
-
983
- @function up.util.clientSize
984
- @internal
985
- ###
986
- clientSize = ->
987
- element = document.documentElement
988
- width: element.clientWidth
989
- height: element.clientHeight
990
-
991
- ###**
992
- Returns the width of a scrollbar.
993
-
994
- This only runs once per page load.
995
-
996
- @function up.util.scrollbarWidth
997
- @internal
998
- ###
999
- scrollbarWidth = memoize ->
1000
- # This is how Bootstrap does it also:
1001
- # https://github.com/twbs/bootstrap/blob/c591227602996c542b9fd0cb65cff3cc9519bdd5/dist/js/bootstrap.js#L1187
1002
- $outer = $('<div>')
1003
- outer = $outer.get(0)
1004
- $outer.attr('up-viewport', '')
1005
- writeInlineStyle outer,
1006
- position: 'absolute'
1007
- top: '0'
1008
- left: '0'
1009
- width: '100px'
1010
- height: '100px' # Firefox needs at least 100px to show a scrollbar
1011
- overflowY: 'scroll'
1012
- $outer.appendTo(document.body)
1013
- width = outer.offsetWidth - outer.clientWidth
1014
- $outer.remove()
1015
- width
1016
-
1017
- ###**
1018
- Returns whether the given element is currently showing a vertical scrollbar.
1019
-
1020
- @function up.util.documentHasVerticalScrollbar
1021
- @internal
1022
- ###
1023
- documentHasVerticalScrollbar = ->
1024
- body = document.body
1025
- $body = $(body)
1026
- html = document.documentElement
1027
-
1028
- bodyOverflow = readComputedStyle($body, 'overflowY')
1029
-
1030
- forcedScroll = (bodyOverflow == 'scroll')
1031
- forcedHidden = (bodyOverflow == 'hidden')
1032
-
1033
- forcedScroll || (!forcedHidden && html.scrollHeight > html.clientHeight)
1034
-
1035
- ###**
1036
- Temporarily sets the CSS for the given element.
1037
-
1038
- @function up.util.writeTemporaryStyle
1039
- @param {jQuery} $element
1040
- @param {Object} css
1041
- @param {Function} [block]
1042
- If given, the CSS is set, the block is called and
1043
- the old CSS is restored.
1044
- @return {Function}
1045
- A function that restores the original CSS when called.
1046
- @internal
1047
- ###
1048
- writeTemporaryStyle = (elementOrSelector, newCss, block) ->
1049
- $element = $(elementOrSelector)
1050
- oldStyles = readInlineStyle($element, Object.keys(newCss))
1051
- restoreOldStyles = -> writeInlineStyle($element, oldStyles)
1052
- writeInlineStyle($element, newCss)
1053
- if block
1054
- # If a block was passed, we run the block and restore old styles.
1055
- block()
1056
- restoreOldStyles()
1057
- else
1058
- # If no block was passed, we return the restoration function.
1059
- restoreOldStyles
1060
-
1061
- ###**
1062
- Forces a repaint of the given element.
1063
-
1064
- @function up.util.forceRepaint
1065
- @internal
1066
- ###
1067
- forceRepaint = (element) ->
1068
- element = getElement(element)
1069
- element.offsetHeight
1070
-
1071
- ###**
1072
- @function up.util.finishTransition
1073
- @internal
1074
- ###
1075
- concludeCssTransition = (element) ->
1076
- undo = writeTemporaryStyle(element, transition: 'none')
1077
- # Browsers need to paint at least one frame without a transition to stop the
1078
- # animation. In theory we could just wait until the next paint, but in case
1079
- # someone will set another transition after us, let's force a repaint here.
1080
- forceRepaint(element)
1081
- return undo
1082
-
1083
- ###**
1084
- @internal
1085
- ###
1086
- margins = (selectorOrElement) ->
1087
- element = getElement(selectorOrElement)
1088
- top: readComputedStyleNumber(element, 'marginTop')
1089
- right: readComputedStyleNumber(element, 'marginRight')
1090
- bottom: readComputedStyleNumber(element, 'marginBottom')
1091
- left: readComputedStyleNumber(element, 'marginLeft')
1092
-
1093
- ###**
1094
- Measures the given element.
1095
-
1096
- @function up.util.measure
1097
- @internal
1098
- ###
1099
- measure = ($element, opts) ->
1100
- opts = newOptions(opts, relative: false, inner: false, includeMargin: false)
1101
-
1102
- if opts.relative
1103
- if opts.relative == true
1104
- coordinates = $element.position()
1105
- else
1106
- # A relative context element is given
1107
- $context = $(opts.relative)
1108
- elementCoords = $element.offset()
1109
- if $context.is(document)
1110
- # The document is always at the origin
1111
- coordinates = elementCoords
1112
- else
1113
- contextCoords = $context.offset()
1114
- coordinates =
1115
- left: elementCoords.left - contextCoords.left
1116
- top: elementCoords.top - contextCoords.top
1117
- else
1118
- coordinates = $element.offset()
1119
-
1120
- box =
1121
- left: coordinates.left
1122
- top: coordinates.top
1123
-
1124
- if opts.inner
1125
- box.width = $element.width()
1126
- box.height = $element.height()
1127
- else
1128
- box.width = $element.outerWidth()
1129
- box.height = $element.outerHeight()
1130
-
1131
- if opts.includeMargin
1132
- mgs = margins($element)
1133
- box.left -= mgs.left
1134
- box.top -= mgs.top
1135
- box.height += mgs.top + mgs.bottom
1136
- box.width += mgs.left + mgs.right
1137
-
1138
- box
1139
-
1140
- ###**
1141
- Copies all attributes from the source element to the target element.
1142
-
1143
- @function up.util.copyAttributes
1144
- @internal
1145
- ###
1146
- copyAttributes = ($source, $target) ->
1147
- for attr in $source.get(0).attributes
1148
- if attr.specified
1149
- $target.attr(attr.name, attr.value)
1150
-
1151
- ###**
1152
- Looks for the given selector in the element and its descendants.
1153
-
1154
- @function up.util.selectInSubtree
1155
- @internal
1156
- ###
1157
- selectInSubtree = ($element, selector) ->
1158
- # This implementation is faster than $element.find(selector).addBack(Seelctor)
1159
- $matches = $()
1160
- if $element.is(selector)
1161
- $matches = $matches.add($element)
1162
- $matches = $matches.add($element.find(selector))
1163
- $matches
1164
-
1165
- ###**
1166
- Looks for the given selector in the element, its descendants and its ancestors.
1167
-
1168
- @function up.util.selectInDynasty
1169
- @internal
1170
- ###
1171
- selectInDynasty = ($element, selector) ->
1172
- $subtree = selectInSubtree($element, selector)
1173
- $ancestors = $element.parents(selector)
1174
- $subtree.add($ancestors)
1175
-
1176
1011
  ###**
1177
1012
  Returns whether the given keyboard event involved the ESC key.
1178
1013
 
@@ -1180,7 +1015,8 @@ up.util = (($) ->
1180
1015
  @internal
1181
1016
  ###
1182
1017
  escapePressed = (event) ->
1183
- event.keyCode == 27
1018
+ key = event.key
1019
+ key == 'Escape' || key == 'Esc'
1184
1020
 
1185
1021
  ###**
1186
1022
  Returns whether the given array or string contains the given element or substring.
@@ -1193,34 +1029,6 @@ up.util = (($) ->
1193
1029
  contains = (arrayOrString, elementOrSubstring) ->
1194
1030
  arrayOrString.indexOf(elementOrSubstring) >= 0
1195
1031
 
1196
- ###**
1197
- @function up.util.castedAttr
1198
- @internal
1199
- ###
1200
- castedAttr = ($element, attribute) ->
1201
- value = $element.attr(attribute)
1202
- switch value
1203
- when 'false' then false
1204
- when 'true', '', attribute then true
1205
- else value # other strings, undefined, null, ...
1206
-
1207
- ###**
1208
- @function up.util.jsonAttr
1209
- @internal
1210
- ###
1211
- jsonAttr = (elementOrSelector, attribute) ->
1212
- if element = getElement(elementOrSelector)
1213
- # The document does not respond to #getAttribute()
1214
- json = element.getAttribute?(attribute)
1215
- if isString(json) && trim(json) != ''
1216
- JSON.parse(json)
1217
-
1218
- # castsToTrue = (object) ->
1219
- # String(object) == "true"
1220
- #
1221
- # castsToFalse = (object) ->
1222
- # String(object) == "false"
1223
-
1224
1032
  ###**
1225
1033
  Returns a copy of the given object that only contains
1226
1034
  the given properties.
@@ -1278,31 +1086,11 @@ up.util = (($) ->
1278
1086
  Returns a promise that will never be resolved.
1279
1087
 
1280
1088
  @function up.util.unresolvablePromise
1281
- @experimental
1089
+ @internal
1282
1090
  ###
1283
1091
  unresolvablePromise = ->
1284
1092
  new Promise(noop)
1285
1093
 
1286
- ###**
1287
- Returns an empty jQuery collection.
1288
-
1289
- @function up.util.nullJQuery
1290
- @internal
1291
- ###
1292
- nullJQuery = ->
1293
- $()
1294
-
1295
- ###**
1296
- On the given element, set attributes that are still missing.
1297
-
1298
- @function up.util.setMissingAttrs
1299
- @internal
1300
- ###
1301
- setMissingAttrs = ($element, attrs) ->
1302
- for key, value of attrs
1303
- if isMissing($element.attr(key))
1304
- $element.attr(key, value)
1305
-
1306
1094
  ###**
1307
1095
  Removes the given element from the given array.
1308
1096
 
@@ -1332,102 +1120,6 @@ up.util = (($) ->
1332
1120
  else
1333
1121
  value
1334
1122
 
1335
- ###**
1336
- @function up.util.config
1337
- @param {Object|Function} blueprint
1338
- Default configuration options.
1339
- Will be restored by calling `reset` on the returned object.
1340
- @return {Object}
1341
- An object with a `reset` function.
1342
- @internal
1343
- ###
1344
- config = (blueprint) ->
1345
- hash = openConfig(blueprint)
1346
- Object.preventExtensions(hash)
1347
- hash
1348
-
1349
- ###**
1350
- @function up.util.openConfig
1351
- @internal
1352
- ###
1353
- openConfig = (blueprint = {}) ->
1354
- hash = {}
1355
- hash.reset = ->
1356
- opts = blueprint
1357
- opts = opts() if isFunction(opts)
1358
- assign(hash, opts)
1359
- hash.reset()
1360
- hash
1361
-
1362
- ###**
1363
- @function up.util.unwrapElement
1364
- @internal
1365
- ###
1366
- unwrapElement = (wrapper) ->
1367
- wrapper = getElement(wrapper)
1368
- parent = wrapper.parentNode;
1369
- wrappedNodes = toArray(wrapper.childNodes)
1370
- each wrappedNodes, (wrappedNode) ->
1371
- parent.insertBefore(wrappedNode, wrapper)
1372
- parent.removeChild(wrapper)
1373
-
1374
- ###**
1375
- @function up.util.offsetParent
1376
- @internal
1377
- ###
1378
- offsetParent = ($element) ->
1379
- $match = undefined
1380
- while ($element = $element.parent()) && $element.length
1381
- position = readComputedStyle($element, 'position')
1382
- if position == 'absolute' || position == 'relative' || $element.is('body')
1383
- $match = $element
1384
- break
1385
- $match
1386
-
1387
- ###**
1388
- Returns if the given element has a `fixed` position.
1389
-
1390
- @function up.util.isFixed
1391
- @internal
1392
- ###
1393
- isFixed = (element) ->
1394
- $element = $(element)
1395
- loop
1396
- position = readComputedStyle($element, 'position')
1397
- if position == 'fixed'
1398
- return true
1399
- else
1400
- $element = $element.parent()
1401
- if $element.length == 0 || $element.is(document)
1402
- return false
1403
-
1404
- ###**
1405
- @function up.util.fixedToAbsolute
1406
- @internal
1407
- ###
1408
- fixedToAbsolute = (element, $viewport) ->
1409
- $element = $(element)
1410
- $futureOffsetParent = offsetParent($element)
1411
- # To get a fixed elements distance from the edge of the screen,
1412
- # use position(), not offset(). offset() would include the current
1413
- # scrollTop of the viewport.
1414
- elementCoords = $element.position()
1415
- futureParentCoords = $futureOffsetParent.offset()
1416
- writeInlineStyle $element,
1417
- position: 'absolute'
1418
- left: elementCoords.left - futureParentCoords.left
1419
- top: elementCoords.top - futureParentCoords.top + $viewport.scrollTop()
1420
- right: ''
1421
- bottom: ''
1422
-
1423
- # argNames = (fun) ->
1424
- # code = fun.toString()
1425
- # pattern = new RegExp('\\(([^\\)]*)\\)')
1426
- # if match = code.match(pattern)
1427
- # match[1].split(/\s*,\s*/)
1428
- # else
1429
- # error('Could not parse argument names of %o', fun)
1430
-
1431
1123
  ###**
1432
1124
  Throws a [JavaScript error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)
1433
1125
  with the given message.
@@ -1461,9 +1153,9 @@ up.util = (($) ->
1461
1153
 
1462
1154
  up.log.error(messageArgs...)
1463
1155
 
1464
- whenReady().then -> up.toast.open(messageArgs, toastOptions)
1156
+ up.event.onReady(-> up.toast.open(messageArgs, toastOptions))
1465
1157
 
1466
- asString = up.browser.sprintf(messageArgs...)
1158
+ asString = up.log.sprintf(messageArgs...)
1467
1159
  throw new Error(asString)
1468
1160
 
1469
1161
  ESCAPE_HTML_ENTITY_MAP =
@@ -1478,11 +1170,19 @@ up.util = (($) ->
1478
1170
  @function up.util.escapeHtml
1479
1171
  @param {string} string
1480
1172
  The text that should be escaped
1481
- @experimental
1173
+ @stable
1482
1174
  ###
1483
1175
  escapeHtml = (string) ->
1484
1176
  string.replace /[&<>"]/g, (char) -> ESCAPE_HTML_ENTITY_MAP[char]
1485
1177
 
1178
+ ###**
1179
+ @function up.util.escapeRegexp
1180
+ @internal
1181
+ ###
1182
+ escapeRegexp = (string) ->
1183
+ # From https://github.com/benjamingr/RegExp.escape
1184
+ string.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')
1185
+
1486
1186
  pluckKey = (object, key) ->
1487
1187
  value = object[key]
1488
1188
  delete object[key]
@@ -1491,118 +1191,41 @@ up.util = (($) ->
1491
1191
  renameKey = (object, oldKey, newKey) ->
1492
1192
  object[newKey] = pluckKey(object, oldKey)
1493
1193
 
1494
- deprecateRenamedKey = (object, oldKey, newKey) ->
1495
- if isDefined(object[oldKey])
1496
- up.warn('Deprecated: Object key { %s } has been renamed to { %s } (found in %o)', oldKey, newKey, object)
1497
- renameKey(object, oldKey, newKey)
1498
-
1499
- pluckData = (elementOrSelector, key) ->
1500
- $element = $(elementOrSelector)
1501
- value = $element.data(key)
1502
- $element.removeData(key)
1503
- value
1504
-
1505
- extractOptions = (args) ->
1194
+ extractLastArg = (args, tester) ->
1506
1195
  lastArg = last(args)
1507
- if isOptions(lastArg)
1508
- args.pop()
1509
- else
1510
- {}
1196
+ if tester(lastArg)
1197
+ return args.pop()
1511
1198
 
1512
- CASE_CONVERSION_GROUP = /[^\-\_]+?(?=[A-Z\-\_]|$)/g
1199
+ # extractFirstArg = (args, tester) ->
1200
+ # firstArg = args[0]
1201
+ # if tester(firstArg)
1202
+ # return args.shift()
1513
1203
 
1514
- convertCase = (string, separator, fn) ->
1515
- parts = string.match(CASE_CONVERSION_GROUP)
1516
- parts = map parts, fn
1517
- parts.join(separator)
1204
+ extractCallback = (args) ->
1205
+ extractLastArg(args, isFunction)
1518
1206
 
1519
- ###**
1520
- Returns a copy of the given string that is transformed to `kebab-case`.
1521
-
1522
- @function up.util.kebabCase
1523
- @param {string} string
1524
- @return {string}
1525
- @internal
1526
- ###
1527
- kebabCase = (string) ->
1528
- convertCase string, '-', (part) -> part.toLowerCase()
1529
-
1530
- ###**
1531
- Returns a copy of the given string that is transformed to `camelCase`.
1532
-
1533
- @function up.util.camelCase
1534
- @param {string} string
1535
- @return {string}
1536
- @internal
1537
- ###
1538
- camelCase = (string) ->
1539
- convertCase string, '', (part, i) ->
1540
- if i == 0
1541
- part.toLowerCase()
1542
- else
1543
- part.charAt(0).toUpperCase() + part.substr(1).toLowerCase()
1544
-
1545
- ###**
1546
- Returns a copy of the given object with all keys renamed
1547
- in `kebab-case`.
1548
-
1549
- Does not change the given object.
1550
-
1551
- @function up.util.kebabCaseKeys
1552
- @param {object} obj
1553
- @return {object}
1554
- @internal
1555
- ###
1556
- kebabCaseKeys = (obj) ->
1557
- copyWithRenamedKeys(obj, kebabCase)
1558
-
1559
- ###**
1560
- Returns a copy of the given object with all keys renamed
1561
- in `camelCase`.
1562
-
1563
- Does not change the given object.
1564
-
1565
- @function up.util.camelCaseKeys
1566
- @param {object} obj
1567
- @return {object}
1568
- @internal
1569
- ###
1570
- camelCaseKeys = (obj) ->
1571
- copyWithRenamedKeys(obj, camelCase)
1572
-
1573
- # lowerCaseKeys = (obj) ->
1574
- # copyWithRenamedKeys(obj, (key) -> key.toLowerCase())
1575
-
1576
- copyWithRenamedKeys = (obj, keyTransformer) ->
1577
- result = {}
1578
- for k, v of obj
1579
- k = keyTransformer(k)
1580
- result[k] = v
1581
- result
1582
-
1583
- opacity = (element) ->
1584
- readComputedStyleNumber(element, 'opacity')
1585
-
1586
- whenReady = memoize ->
1587
- if $.isReady
1588
- Promise.resolve()
1589
- else
1590
- new Promise (resolve) -> $(resolve)
1207
+ extractOptions = (args) ->
1208
+ extractLastArg(args, isOptions) || {}
1209
+
1210
+ partial = (fn, fixedArgs...) ->
1211
+ return (callArgs...) ->
1212
+ fn.apply(this, fixedArgs.concat(callArgs))
1213
+
1214
+ #function throttle(callback, limit) { // From https://jsfiddle.net/jonathansampson/m7G64/
1215
+ # var wait = false // Initially, we're not waiting
1216
+ # return function () { // We return a throttled function
1217
+ # if (!wait) { // If we're not waiting
1218
+ # callback.call() // Execute users function
1219
+ # wait = true // Prevent future invocations
1220
+ # setTimeout(function () { // After a period of time
1221
+ # wait = false // And allow future invocations
1222
+ # }, limit)
1223
+ # }
1224
+ # }
1225
+ #}
1591
1226
 
1592
1227
  identity = (arg) -> arg
1593
1228
 
1594
- ###**
1595
- Returns whether the given element has been detached from the DOM
1596
- (or whether it was never attached).
1597
-
1598
- @function up.util.isDetached
1599
- @internal
1600
- ###
1601
- isDetached = (element) ->
1602
- element = getElement(element)
1603
- # This is by far the fastest way to do this
1604
- not $.contains(document.documentElement, element)
1605
-
1606
1229
  # ###**
1607
1230
  # ###
1608
1231
  # parsePath = (input) ->
@@ -1637,66 +1260,19 @@ up.util = (($) ->
1637
1260
  preview.promise = deferred.promise()
1638
1261
  preview
1639
1262
 
1640
- ###**
1641
- A linear task queue whose (2..n)th tasks can be changed at any time.
1642
-
1643
- @function up.util.DivertibleChain
1644
- @internal
1645
- ###
1646
- class DivertibleChain
1647
-
1648
- constructor: ->
1649
- @reset()
1650
-
1651
- reset: =>
1652
- @queue = []
1653
- @currentTask = undefined
1654
-
1655
- promise: =>
1656
- lastTask = last(@allTasks())
1657
- lastTask?.promise || Promise.resolve()
1658
-
1659
- allTasks: =>
1660
- tasks = []
1661
- tasks.push(@currentTask) if @currentTask
1662
- tasks = tasks.concat(@queue)
1663
- tasks
1664
-
1665
- poke: =>
1666
- unless @currentTask # don't start a new task while we're still running one
1667
- if @currentTask = @queue.shift()
1668
- promise = @currentTask()
1669
- always promise, =>
1670
- @currentTask = undefined
1671
- @poke()
1672
-
1673
- asap: (newTasks...) =>
1674
- @queue = map(newTasks, previewable)
1675
- @poke()
1676
- @promise()
1677
-
1678
- ###**
1679
- @function up.util.submittedValue
1680
- @internal
1681
- ###
1682
- submittedValue = (fieldOrSelector) ->
1683
- $field = $(fieldOrSelector)
1684
- if $field.is('[type=checkbox], [type=radio]') && !$field.is(':checked')
1685
- undefined
1686
- else
1687
- $field.val()
1688
-
1689
1263
  ###**
1690
1264
  @function up.util.sequence
1691
- @param {Array<Function>} functions...
1692
- @return {Function}
1265
+ @param {Array<Function()>} functions
1266
+ @return {Function()}
1693
1267
  A function that will call all `functions` if called.
1694
1268
 
1695
1269
  @internal
1696
1270
  ###
1697
- sequence = (functions...) ->
1698
- ->
1699
- map functions, (f) -> f()
1271
+ sequence = (functions) ->
1272
+ if functions.length == 1
1273
+ return functions[0]
1274
+ else
1275
+ return -> map(functions, (f) -> f())
1700
1276
 
1701
1277
  # ###**
1702
1278
  # @function up.util.race
@@ -1708,17 +1284,6 @@ up.util = (($) ->
1708
1284
  # promise.then -> raceDone.resolve()
1709
1285
  # raceDone.promise()
1710
1286
 
1711
- ###**
1712
- @function up.util.promiseTimer
1713
- @internal
1714
- ###
1715
- promiseTimer = (ms) ->
1716
- timeout = undefined
1717
- promise = new Promise (resolve, reject) ->
1718
- timeout = setTimer(ms, resolve)
1719
- promise.cancel = -> clearTimeout(timeout)
1720
- promise
1721
-
1722
1287
  ###**
1723
1288
  Returns `'left'` if the center of the given element is in the left 50% of the screen.
1724
1289
  Otherwise returns `'right'`.
@@ -1726,160 +1291,15 @@ up.util = (($) ->
1726
1291
  @function up.util.horizontalScreenHalf
1727
1292
  @internal
1728
1293
  ###
1729
- horizontalScreenHalf = ($element) ->
1730
- elementDims = measure($element)
1731
- screenDims = clientSize()
1294
+ horizontalScreenHalf = (element) ->
1295
+ elementDims = element.getBoundingClientRect()
1732
1296
  elementMid = elementDims.left + 0.5 * elementDims.width
1733
- screenMid = 0.5 * screenDims.width
1297
+ screenMid = 0.5 * up.viewport.rootWidth()
1734
1298
  if elementMid < screenMid
1735
1299
  'left'
1736
1300
  else
1737
1301
  'right'
1738
1302
 
1739
- ###**
1740
- Like `$old.replaceWith($new)`, but keeps event handlers bound to `$old`.
1741
-
1742
- Note that this is a memory leak unless you re-attach `$old` to the DOM aferwards.
1743
-
1744
- @function up.util.detachWith
1745
- @internal
1746
- ###
1747
- detachWith = ($old, $new) ->
1748
- $insertion = $('<div></div>')
1749
- $insertion.insertAfter($old)
1750
- $old.detach()
1751
- $insertion.replaceWith($new)
1752
- $old
1753
-
1754
- ###**
1755
- Hides the given element faster than `jQuery.fn.hide()`.
1756
-
1757
- @function up.util.hide
1758
- @param {jQuery|Element} element
1759
- ###
1760
- hide = (element) ->
1761
- writeInlineStyle(element, display: 'none')
1762
-
1763
- ###**
1764
- Gets the computed style(s) for the given element.
1765
-
1766
- @function up.util.readComputedStyle
1767
- @param {jQuery|Element} element
1768
- @param {String|Array} propOrProps
1769
- One or more CSS property names in camelCase.
1770
- @return {string|object}
1771
- @internal
1772
- ###
1773
- readComputedStyle = (element, props) ->
1774
- element = getElement(element)
1775
- style = window.getComputedStyle(element)
1776
- extractFromStyleObject(style, props)
1777
-
1778
- ###**
1779
- Gets a computed style value for the given element.
1780
- If a value is set, the value is parsed to a number before returning.
1781
-
1782
- @function up.util.readComputedStyleNumber
1783
- @param {jQuery|Element} element
1784
- @param {String} prop
1785
- A CSS property name in camelCase.
1786
- @return {string|object}
1787
- @internal
1788
- ###
1789
- readComputedStyleNumber = (element, prop) ->
1790
- rawValue = readComputedStyle(element, prop)
1791
- if isGiven(rawValue)
1792
- parseFloat(rawValue)
1793
- else
1794
- undefined
1795
-
1796
- ###**
1797
- Gets the given inline style(s) from the given element's `[style]` attribute.
1798
-
1799
- @function up.util.readInlineStyle
1800
- @param {jQuery|Element} element
1801
- @param {String|Array} propOrProps
1802
- One or more CSS property names in camelCase.
1803
- @return {string|object}
1804
- @internal
1805
- ###
1806
- readInlineStyle = (element, props) ->
1807
- element = getElement(element)
1808
- style = element.style
1809
- extractFromStyleObject(style, props)
1810
-
1811
- extractFromStyleObject = (style, keyOrKeys) ->
1812
- if isString(keyOrKeys)
1813
- style[keyOrKeys]
1814
- else # array
1815
- only(style, keyOrKeys...)
1816
-
1817
- ###**
1818
- Merges the given inline style(s) into the given element's `[style]` attribute.
1819
-
1820
- @function up.util.readInlineStyle
1821
- @param {jQuery|Element} element
1822
- @param {Object} props
1823
- One or more CSS properties with camelCase keys.
1824
- @return {string|object}
1825
- @internal
1826
- ###
1827
- writeInlineStyle = (element, props) ->
1828
- element = getElement(element)
1829
- style = element.style
1830
- for key, value of props
1831
- value = normalizeStyleValueForWrite(key, value)
1832
- style[key] = value
1833
-
1834
- normalizeStyleValueForWrite = (key, value) ->
1835
- if isMissing(value)
1836
- value = ''
1837
- else if CSS_LENGTH_PROPS.has(key)
1838
- value = cssLength(value)
1839
- value
1840
-
1841
- CSS_LENGTH_PROPS = arrayToSet [
1842
- 'top', 'right', 'bottom', 'left',
1843
- 'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
1844
- 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
1845
- 'width', 'height',
1846
- 'maxWidth', 'maxHeight',
1847
- 'minWidth', 'minHeight',
1848
- ]
1849
-
1850
- ###**
1851
- Converts the given value to a CSS length value, adding a `px` unit if required.
1852
-
1853
- @function up.util.cssLength
1854
- @internal
1855
- ###
1856
- cssLength = (obj) ->
1857
- if isNumber(obj) || (isString(obj) && /^\d+$/.test(obj))
1858
- obj.toString() + "px"
1859
- else
1860
- obj
1861
-
1862
- ###**
1863
- Returns whether the given element has a CSS transition set.
1864
-
1865
- @function up.util.hasCssTransition
1866
- @return {boolean}
1867
- @internal
1868
- ###
1869
- hasCssTransition = (elementOrStyleHash) ->
1870
- if isOptions(elementOrStyleHash)
1871
- style = elementOrStyleHash
1872
- else
1873
- element = getElement(element)
1874
- style = getComputedStyle(element)
1875
-
1876
- prop = style.transitionProperty
1877
- duration = style.transitionDuration
1878
- # The default transition for elements is actually "all 0s ease 0s"
1879
- # instead of "none", although that has the same effect as "none".
1880
- noTransition = (prop == 'none' || (prop == 'all' && duration == 0))
1881
- not noTransition
1882
-
1883
1303
  ###**
1884
1304
  Flattens the given `array` a single level deep.
1885
1305
 
@@ -1888,17 +1308,27 @@ up.util = (($) ->
1888
1308
  An array which might contain other arrays
1889
1309
  @return {Array}
1890
1310
  The flattened array
1891
- @internal
1311
+ @experimental
1892
1312
  ###
1893
1313
  flatten = (array) ->
1894
1314
  flattened = []
1895
1315
  for object in array
1896
- if isArray(object)
1897
- flattened = flattened.concat(object)
1316
+ if isList(object)
1317
+ flattened.push(object...)
1898
1318
  else
1899
1319
  flattened.push(object)
1900
1320
  flattened
1901
1321
 
1322
+ ###**
1323
+ Maps each element using a mapping function,
1324
+ then flattens the result into a new array.
1325
+
1326
+ @function up.util.flatMap
1327
+ @param {Array} array
1328
+ @param {Function(element)} mapping
1329
+ @return {Array}
1330
+ @experimental
1331
+ ###
1902
1332
  flatMap = (array, block) ->
1903
1333
  flatten map(array, block)
1904
1334
 
@@ -1975,7 +1405,7 @@ up.util = (($) ->
1975
1405
  Promise.reject(error)
1976
1406
 
1977
1407
  sum = (list, block) ->
1978
- block = listBlock(block)
1408
+ block = iteratee(block)
1979
1409
  totalValue = 0
1980
1410
  for entry in list
1981
1411
  entryValue = block(entry)
@@ -1983,34 +1413,126 @@ up.util = (($) ->
1983
1413
  totalValue += entryValue
1984
1414
  totalValue
1985
1415
 
1416
+ isBasicObjectProperty = (k) ->
1417
+ Object.prototype.hasOwnProperty(k)
1418
+
1986
1419
  ###**
1987
- Returns whether the given element is a descendant of the `<body>` element.
1420
+ Returns whether the two arguments are equal by value.
1988
1421
 
1989
- @function up.util.isBodyDescendant
1990
- @internal
1991
- ###
1992
- isBodyDescendant = (element) ->
1993
- $(element).parents('body').length > 0
1422
+ \#\#\# Comparison protocol
1994
1423
 
1995
- isBasicObjectProperty = (k) ->
1996
- Object.prototype.hasOwnProperty(k)
1424
+ - By default `up.util.isEqual()` can compare strings, numbers,
1425
+ [array-like values](/up.util.isList), plain objects and `Date` objects.
1426
+ - To make the copying protocol work with user-defined classes,
1427
+ see `up.util.isEqual.key`.
1428
+ - Objects without a defined comparison protocol are
1429
+ defined by reference (`===`).
1997
1430
 
1431
+ @function up.util.isEqual
1432
+ @param {any} a
1433
+ @param {any} b
1434
+ @return {boolean}
1435
+ Whether the arguments are equal by value.
1436
+ @experimental
1437
+ ###
1998
1438
  isEqual = (a, b) ->
1439
+ a = a.valueOf() if a?.valueOf # Date, String objects, Number objects
1440
+ b = b.valueOf() if b?.valueOf # Date, String objects, Number objects
1999
1441
  if typeof(a) != typeof(b)
2000
1442
  false
2001
- else if isArray(a)
2002
- a.length == b.length && all(a, (elem, index) -> isEqual(elem, b[index]))
2003
- else if isObject(a)
2004
- fail('isEqual cannot compare objects yet')
1443
+ else if isList(a) && isList(b)
1444
+ isEqualList(a, b)
1445
+ else if isObject(a) && a[isEqual.key]
1446
+ a[isEqual.key](b)
1447
+ else if isOptions(a) && isOptions(b)
1448
+ aKeys = Object.keys(a)
1449
+ bKeys = Object.keys(b)
1450
+ if isEqualList(aKeys, bKeys)
1451
+ every aKeys, (aKey) -> isEqual(a[aKey], b[aKey])
1452
+ else
1453
+ false
2005
1454
  else
2006
1455
  a == b
2007
1456
 
2008
- splitValues = (string, separator = ' ') ->
2009
- values = string.split(separator)
2010
- values = map(values, trim)
2011
- values = select(values, isPresent)
1457
+ ###**
1458
+ This property contains the name of a method that user-defined classes
1459
+ may implement to hook into the `up.util.isEqual()` protocol.
1460
+
1461
+ \#\#\# Example
1462
+
1463
+ We have a user-defined `Account` class that we want to use with `up.util.isEqual()`:
1464
+
1465
+ ```
1466
+ class Account {
1467
+ constructor(email) {
1468
+ this.email = email
1469
+ }
1470
+
1471
+ [up.util.isEqual.key](other) {
1472
+ return this.email === other.email;
1473
+ }
1474
+ }
1475
+ ```
1476
+
1477
+ Note that the protocol method is not actually named `'up.util.isEqual.key'`.
1478
+ Instead it is named after the *value* of the `up.util.isEqual.key` property.
1479
+ To do so, the code sample above is using a
1480
+ [computed property name](https://medium.com/front-end-weekly/javascript-object-creation-356e504173a8)
1481
+ in square brackets.
1482
+
1483
+ We may now use `Account` instances with `up.util.isEqual()`:
1484
+
1485
+ ```
1486
+ one = new User('foo@foo.com')
1487
+ two = new User('foo@foo.com')
1488
+ three = new User('bar@bar.com')
1489
+
1490
+ isEqual = up.util.isEqual(one, two)
1491
+ // isEqual is now true
1492
+
1493
+ isEqual = up.util.isEqual(one, three)
1494
+ // isEqual is now false
1495
+ ```
1496
+
1497
+ @property up.util.isEqual.key
1498
+ @param {string} key
1499
+ @experimental
1500
+ ###
1501
+ isEqual.key = 'up.util.isEqual'
1502
+
1503
+ isEqualList = (a, b) ->
1504
+ a.length == b.length && every(a, (elem, index) -> isEqual(elem, b[index]))
1505
+
1506
+ splitValues = (value, separator = ' ') ->
1507
+ values = value.split(separator)
1508
+ values = map values, (v) -> v.trim()
1509
+ values = filterList(values, isPresent)
2012
1510
  values
2013
1511
 
1512
+ endsWith = (string, search) ->
1513
+ if search.length > string.length
1514
+ false
1515
+ else
1516
+ string.substring(string.length - search.length) == search
1517
+
1518
+ simpleEase = (x) ->
1519
+ # easing: http://fooplot.com/?lang=de#W3sidHlwZSI6MCwiZXEiOiJ4PDAuNT8yKngqeDp4Kig0LXgqMiktMSIsImNvbG9yIjoiIzEzRjIxNyJ9LHsidHlwZSI6MCwiZXEiOiJzaW4oKHheMC43LTAuNSkqcGkpKjAuNSswLjUiLCJjb2xvciI6IiMxQTUyRUQifSx7InR5cGUiOjEwMDAsIndpbmRvdyI6WyItMS40NyIsIjEuNzgiLCItMC41NSIsIjEuNDUiXX1d
1520
+ # easing nice: sin((x^0.7-0.5)*pi)*0.5+0.5
1521
+ # easing performant: x < 0.5 ? 2*x*x : x*(4 - x*2)-1
1522
+ # https://jsperf.com/easings/1
1523
+ # Math.sin((Math.pow(x, 0.7) - 0.5) * Math.PI) * 0.5 + 0.5
1524
+ if x < 0.5
1525
+ 2*x*x
1526
+ else
1527
+ x*(4 - x*2)-1
1528
+
1529
+ wrapValue = (object, constructor) ->
1530
+ if object instanceof constructor
1531
+ # This object has gone through instantiation and normalization before.
1532
+ object
1533
+ else
1534
+ new constructor(object)
1535
+
2014
1536
  # wrapArray = (objOrArray) ->
2015
1537
  # if isUndefined(objOrArray)
2016
1538
  # []
@@ -2019,40 +1541,49 @@ up.util = (($) ->
2019
1541
  # else
2020
1542
  # [objOrArray]
2021
1543
 
1544
+ nextUid = 0
1545
+
1546
+ uid = ->
1547
+ nextUid++
1548
+
2022
1549
  <% if ENV['JS_KNIFE'] %>knife: eval(Knife.point)<% end %>
2023
- offsetParent: offsetParent
2024
- fixedToAbsolute: fixedToAbsolute
2025
- isFixed: isFixed
2026
- presentAttr: presentAttr
2027
1550
  parseUrl: parseUrl
2028
1551
  normalizeUrl: normalizeUrl
2029
1552
  normalizeMethod: normalizeMethod
2030
1553
  methodAllowsPayload: methodAllowsPayload
2031
- createElementFromHtml: createElementFromHtml
2032
- $createElementFromSelector: $createElementFromSelector
2033
- $createPlaceholder: $createPlaceholder
2034
- selectorForElement: selectorForElement
2035
1554
  # isGoodSelector: isGoodSelector
2036
- attributeSelector: attributeSelector
2037
1555
  assign: assign
2038
1556
  assignPolyfill: assignPolyfill
2039
1557
  copy: copy
2040
- # deepCopy: deepCopy
1558
+ deepCopy: deepCopy
2041
1559
  merge: merge
2042
1560
  # deepAssign: deepAssign
2043
1561
  # deepMerge: deepMerge
2044
1562
  options: newOptions
2045
- option: option
2046
1563
  fail: fail
2047
1564
  each: each
2048
1565
  eachIterator: eachIterator
2049
1566
  map: map
2050
1567
  flatMap: flatMap
1568
+ mapObject: mapObject
2051
1569
  times: times
2052
- any: any
2053
- all: all
2054
- detect: detect
2055
- select: select
1570
+ findResult: findResult
1571
+ some: some
1572
+ any: ->
1573
+ up.legacy.warn('up.util.any() has been renamed to up.util.some()')
1574
+ some.apply(null, arguments)
1575
+ every: every
1576
+ all: ->
1577
+ up.legacy.warn('up.util.all() has been renamed to up.util.every()')
1578
+ every.apply(null, arguments)
1579
+ detect: ->
1580
+ up.legacy.warn('up.util.find() has been renamed to up.util.find()')
1581
+ findInList.apply(null, arguments)
1582
+ find: findInList
1583
+ select: ->
1584
+ up.legacy.warn('up.util.select() has been renamed to up.util.filter()')
1585
+ filterList.apply(null, arguments)
1586
+ filter: filterList
2056
1587
  reject: reject
2057
1588
  intersect: intersect
2058
1589
  compact: compact
@@ -2078,93 +1609,67 @@ up.util = (($) ->
2078
1609
  isOptions: isOptions
2079
1610
  isArray: isArray
2080
1611
  isFormData: isFormData
1612
+ isNodeList: isNodeList
1613
+ isArguments: isArguments
1614
+ isList: isList
2081
1615
  isUnmodifiedKeyEvent: isUnmodifiedKeyEvent
2082
1616
  isUnmodifiedMouseEvent: isUnmodifiedMouseEvent
2083
- nullJQuery: nullJQuery
2084
- element: getElement
2085
- setTimer: setTimer
2086
- nextFrame: nextFrame
2087
- measure: measure
2088
- addClass: addClass
2089
- removeClass: removeClass
2090
- hasClass: hasClass
2091
- addTemporaryClass: addTemporaryClass
2092
- writeTemporaryStyle: writeTemporaryStyle
2093
- forceRepaint: forceRepaint
2094
- concludeCssTransition: concludeCssTransition
1617
+ timer: scheduleTimer
1618
+ setTimer: ->
1619
+ up.legacy.warn('up.util.setTimer() has been renamed to up.util.timer()')
1620
+ scheduleTimer.apply(null, arguments)
2095
1621
  escapePressed: escapePressed
2096
- copyAttributes: copyAttributes
2097
- selectInSubtree: selectInSubtree
2098
- selectInDynasty: selectInDynasty
2099
1622
  contains: contains
2100
1623
  toArray: toArray
2101
- castedAttr: castedAttr
2102
- jsonAttr: jsonAttr
2103
- clientSize: clientSize
2104
1624
  only: only
2105
1625
  except: except
2106
1626
  # pickBy: pickBy
2107
- trim: trim
2108
1627
  unresolvablePromise: unresolvablePromise
2109
- setMissingAttrs: setMissingAttrs
2110
1628
  remove: remove
2111
1629
  memoize: memoize
2112
- scrollbarWidth: scrollbarWidth
2113
- documentHasVerticalScrollbar: documentHasVerticalScrollbar
2114
- config: config
2115
- openConfig: openConfig
2116
- unwrapElement: unwrapElement
2117
- camelCase: camelCase
2118
- camelCaseKeys: camelCaseKeys
2119
- # lowerCaseKeys: lowerCaseKeys
2120
- kebabCase: kebabCase
2121
- kebabCaseKeys: kebabCaseKeys
2122
1630
  error: fail
2123
- pluckData: pluckData
2124
1631
  pluckKey: pluckKey
2125
1632
  renameKey: renameKey
2126
- deprecateRenamedKey: deprecateRenamedKey
2127
1633
  extractOptions: extractOptions
2128
- isDetached: isDetached
1634
+ extractCallback: extractCallback
2129
1635
  noop: noop
2130
1636
  asyncNoop: asyncNoop
2131
- opacity: opacity
2132
- whenReady: whenReady
2133
1637
  identity: identity
2134
1638
  escapeHtml: escapeHtml
2135
- DivertibleChain: DivertibleChain
2136
- submittedValue: submittedValue
1639
+ escapeRegexp: escapeRegexp
2137
1640
  sequence: sequence
2138
- promiseTimer: promiseTimer
2139
1641
  previewable: previewable
2140
1642
  # parsePath: parsePath
2141
1643
  evalOption: evalOption
2142
1644
  horizontalScreenHalf: horizontalScreenHalf
2143
- detachWith: detachWith
2144
1645
  flatten: flatten
2145
1646
  isTruthy: isTruthy
2146
- isSingletonElement: isSingletonElement
2147
1647
  newDeferred: newDeferred
2148
1648
  always: always
2149
1649
  muteRejection: muteRejection
2150
1650
  rejectOnError: rejectOnError
2151
- isBodyDescendant: isBodyDescendant
2152
1651
  isBasicObjectProperty: isBasicObjectProperty
2153
1652
  isCrossDomain: isCrossDomain
2154
- microtask: microtask
1653
+ selectorForElement: ->
1654
+ up.legacy.warn('up.util.selectorForElement() has been renamed to up.element.toSelector()')
1655
+ up.element.toSelector.apply(null, arguments)
1656
+ nextFrame: ->
1657
+ up.legacy.warn('up.util.nextFrame() has been renamed to up.util.task()')
1658
+ queueTask.apply(null, arguments)
1659
+ task: queueTask
1660
+ microtask: queueMicrotask
2155
1661
  isEqual: isEqual
2156
- hide: hide
2157
- cssLength: cssLength
2158
- readComputedStyle: readComputedStyle
2159
- readComputedStyleNumber: readComputedStyleNumber
2160
- readInlineStyle: readInlineStyle
2161
- writeInlineStyle: writeInlineStyle
2162
- hasCssTransition: hasCssTransition
2163
1662
  splitValues : splitValues
1663
+ endsWith: endsWith
2164
1664
  sum: sum
2165
1665
  # wrapArray: wrapArray
1666
+ wrapList: wrapList
1667
+ wrapValue: wrapValue
1668
+ simpleEase: simpleEase
2166
1669
  values: objectValues
2167
-
2168
- )(jQuery)
1670
+ partial: partial
1671
+ arrayToSet: arrayToSet
1672
+ setToArray: setToArray
1673
+ uid: uid
2169
1674
 
2170
1675
  up.fail = up.util.fail