unpoly-rails 0.55.1 → 0.56.0

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

Potentially problematic release.


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

Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -2
  3. data/dist/unpoly-bootstrap3.js +6 -4
  4. data/dist/unpoly-bootstrap3.min.js +1 -1
  5. data/dist/unpoly.js +1323 -805
  6. data/dist/unpoly.min.js +4 -3
  7. data/lib/assets/javascripts/unpoly-bootstrap3/{navigation-ext.coffee → feedback-ext.coffee} +2 -0
  8. data/lib/assets/javascripts/unpoly/browser.coffee.erb +7 -7
  9. data/lib/assets/javascripts/unpoly/bus.coffee.erb +5 -6
  10. data/lib/assets/javascripts/unpoly/classes/css_transition.coffee +127 -0
  11. data/lib/assets/javascripts/unpoly/classes/extract_plan.coffee +1 -1
  12. data/lib/assets/javascripts/unpoly/classes/motion_tracker.coffee +62 -32
  13. data/lib/assets/javascripts/unpoly/classes/url_set.coffee +27 -0
  14. data/lib/assets/javascripts/unpoly/dom.coffee.erb +78 -99
  15. data/lib/assets/javascripts/unpoly/feedback.coffee +147 -96
  16. data/lib/assets/javascripts/unpoly/form.coffee.erb +26 -2
  17. data/lib/assets/javascripts/unpoly/history.coffee +2 -1
  18. data/lib/assets/javascripts/unpoly/layout.coffee.erb +68 -12
  19. data/lib/assets/javascripts/unpoly/link.coffee.erb +10 -4
  20. data/lib/assets/javascripts/unpoly/modal.coffee.erb +11 -9
  21. data/lib/assets/javascripts/unpoly/{motion.coffee → motion.coffee.erb} +184 -322
  22. data/lib/assets/javascripts/unpoly/popup.coffee.erb +13 -12
  23. data/lib/assets/javascripts/unpoly/radio.coffee +1 -1
  24. data/lib/assets/javascripts/unpoly/syntax.coffee +8 -17
  25. data/lib/assets/javascripts/unpoly/tooltip.coffee +11 -11
  26. data/lib/assets/javascripts/unpoly/util.coffee +332 -145
  27. data/lib/unpoly/rails/version.rb +1 -1
  28. data/package.json +1 -1
  29. data/spec_app/Gemfile.lock +1 -1
  30. data/spec_app/app/assets/javascripts/integration_test.coffee +1 -0
  31. data/spec_app/app/assets/stylesheets/integration_test.sass +1 -0
  32. data/spec_app/app/assets/stylesheets/jasmine_specs.sass +4 -0
  33. data/spec_app/app/views/motion_test/transitions.erb +13 -0
  34. data/spec_app/app/views/pages/start.erb +1 -0
  35. data/spec_app/spec/javascripts/helpers/to_be_attached.coffee +5 -0
  36. data/spec_app/spec/javascripts/helpers/to_be_detached.coffee +5 -0
  37. data/spec_app/spec/javascripts/helpers/to_contain.js.coffee +1 -1
  38. data/spec_app/spec/javascripts/helpers/to_have_opacity.coffee +11 -0
  39. data/spec_app/spec/javascripts/helpers/to_have_own_property.js.coffee +5 -0
  40. data/spec_app/spec/javascripts/up/dom_spec.js.coffee +217 -102
  41. data/spec_app/spec/javascripts/up/feedback_spec.js.coffee +162 -44
  42. data/spec_app/spec/javascripts/up/layout_spec.js.coffee +97 -10
  43. data/spec_app/spec/javascripts/up/link_spec.js.coffee +3 -3
  44. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +22 -20
  45. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +344 -228
  46. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +1 -1
  47. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +1 -1
  48. data/spec_app/spec/javascripts/up/tooltip_spec.js.coffee +1 -1
  49. data/spec_app/spec/javascripts/up/util_spec.js.coffee +194 -0
  50. metadata +11 -4
@@ -124,34 +124,35 @@ up.popup = (($) ->
124
124
  config.reset()
125
125
 
126
126
  align = ->
127
- css = {}
127
+ style = {}
128
128
 
129
129
  popupBox = u.measure(state.$popup)
130
130
 
131
131
  if u.isFixed(state.$anchor)
132
132
  linkBox = state.$anchor.get(0).getBoundingClientRect()
133
- css['position'] = 'fixed'
133
+ style.position = 'fixed'
134
134
  else
135
135
  linkBox = u.measure(state.$anchor)
136
136
 
137
137
  switch state.position
138
138
  when 'bottom-right' # anchored to bottom-right of link, opens towards bottom-left
139
- css['top'] = linkBox.top + linkBox.height
140
- css['left'] = linkBox.left + linkBox.width - popupBox.width
139
+ style.top = linkBox.top + linkBox.height
140
+ style.left = linkBox.left + linkBox.width - popupBox.width
141
141
  when 'bottom-left' # anchored to bottom-left of link, opens towards bottom-right
142
- css['top'] = linkBox.top + linkBox.height
143
- css['left'] = linkBox.left
142
+ style.top = linkBox.top + linkBox.height
143
+ style.left = linkBox.left
144
144
  when 'top-right' # anchored to top-right of link, opens to top-left
145
- css['top'] = linkBox.top - popupBox.height
146
- css['left'] = linkBox.left + linkBox.width - popupBox.width
145
+ style.top = linkBox.top - popupBox.height
146
+ style.left = linkBox.left + linkBox.width - popupBox.width
147
147
  when 'top-left' # anchored to top-left of link, opens to top-right
148
- css['top'] = linkBox.top - popupBox.height
149
- css['left'] = linkBox.left
148
+ style.top = linkBox.top - popupBox.height
149
+ style.left = linkBox.left
150
150
  else
151
151
  up.fail("Unknown position option '%s'", state.position)
152
152
 
153
153
  state.$popup.attr('up-position', state.position)
154
- state.$popup.css(css)
154
+
155
+ u.writeInlineStyle(state.$popup, style)
155
156
 
156
157
  discardHistory = ->
157
158
  state.coveredTitle = null
@@ -344,7 +345,7 @@ up.popup = (($) ->
344
345
  attachNow($link, options)
345
346
 
346
347
  toggleAsap = ($link, options) ->
347
- if $link.is('.up-current')
348
+ if u.hasClass($link, 'up-current')
348
349
  closeAsap()
349
350
  else
350
351
  attachAsap($link, options)
@@ -39,7 +39,7 @@ up.radio = (($) ->
39
39
  @internal
40
40
  ###
41
41
  hungrySelector = ->
42
- u.multiSelector(config.hungry)
42
+ config.hungry.join(',')
43
43
 
44
44
  ###**
45
45
  Elements with this attribute are [updated](/up.replace) whenever there is a
@@ -376,22 +376,14 @@ up.syntax = (($) ->
376
376
  @internal
377
377
  ###
378
378
  clean = ($fragment) ->
379
- prepareClean($fragment)()
380
-
381
- ###**
382
- @function up.syntax.prepareClean
383
- @param {jQuery} $fragment
384
- @return {Function}
385
- @internal
386
- ###
387
- prepareClean = ($fragment) ->
388
- $candidates = u.selectInSubtree($fragment, ".#{DESTRUCTIBLE_CLASS}")
389
- destructors = u.map $candidates, (candidate) -> $(candidate).data(DESTRUCTORS_KEY)
390
- # Although destructible elements should always have an destructor function, we might be
391
- # destroying a clone of such an element. E.g. Unpoly creates a clone when keeping an
392
- # [up-keep] element, and that clone still has the .up-destructible class.
393
- destructors = u.compact destructors
394
- u.sequence(destructors...)
379
+ $destructibles = u.selectInSubtree($fragment, ".#{DESTRUCTIBLE_CLASS}")
380
+ u.each $destructibles, (destructible) ->
381
+ # The destructor function may be undefined at this point.
382
+ # Although destructible elements should always have an destructor function, we might be
383
+ # destroying a clone of such an element. E.g. Unpoly creates a clone when keeping an
384
+ # [up-keep] element, and that clone still has the .up-destructible class.
385
+ if destructor = $(destructible).data(DESTRUCTORS_KEY)
386
+ destructor()
395
387
 
396
388
  ###**
397
389
  Checks if the given element has an [`up-data`](/up-data) attribute.
@@ -487,7 +479,6 @@ up.syntax = (($) ->
487
479
  macro: macro
488
480
  compile: compile
489
481
  clean: clean
490
- prepareClean: prepareClean
491
482
  data: data
492
483
 
493
484
  )(jQuery)
@@ -79,33 +79,33 @@ up.tooltip = (($) ->
79
79
  config.reset()
80
80
 
81
81
  align = ->
82
- css = {}
82
+ style = {}
83
83
  tooltipBox = u.measure(state.$tooltip)
84
84
 
85
85
  if u.isFixed(state.$anchor)
86
86
  linkBox = state.$anchor.get(0).getBoundingClientRect()
87
- css['position'] = 'fixed'
87
+ style.position = 'fixed'
88
88
  else
89
89
  linkBox = u.measure(state.$anchor)
90
90
 
91
91
  switch state.position
92
92
  when 'top'
93
- css['top'] = linkBox.top - tooltipBox.height
94
- css['left'] = linkBox.left + 0.5 * (linkBox.width - tooltipBox.width)
93
+ style.top = linkBox.top - tooltipBox.height
94
+ style.left = linkBox.left + 0.5 * (linkBox.width - tooltipBox.width)
95
95
  when 'left'
96
- css['top'] = linkBox.top + 0.5 * (linkBox.height - tooltipBox.height)
97
- css['left'] = linkBox.left - tooltipBox.width
96
+ style.top = linkBox.top + 0.5 * (linkBox.height - tooltipBox.height)
97
+ style.left = linkBox.left - tooltipBox.width
98
98
  when 'right'
99
- css['top'] = linkBox.top + 0.5 * (linkBox.height - tooltipBox.height)
100
- css['left'] = linkBox.left + linkBox.width
99
+ style.top = linkBox.top + 0.5 * (linkBox.height - tooltipBox.height)
100
+ style.left = linkBox.left + linkBox.width
101
101
  when 'bottom'
102
- css['top'] = linkBox.top + linkBox.height
103
- css['left'] = linkBox.left + 0.5 * (linkBox.width - tooltipBox.width)
102
+ style.top = linkBox.top + linkBox.height
103
+ style.left = linkBox.left + 0.5 * (linkBox.width - tooltipBox.width)
104
104
  else
105
105
  up.fail("Unknown position option '%s'", state.position)
106
106
 
107
107
  state.$tooltip.attr('up-position', state.position)
108
- state.$tooltip.css(css)
108
+ u.writeInlineStyle(state.$tooltip, style)
109
109
 
110
110
  createElement = (options) ->
111
111
  $element = u.$createElementFromSelector('.up-tooltip')
@@ -15,7 +15,15 @@ up.util = (($) ->
15
15
  @function up.util.noop
16
16
  @experimental
17
17
  ###
18
- noop = $.noop
18
+ noop = (->)
19
+
20
+ ###**
21
+ A function that returns a resolved promise.
22
+
23
+ @function up.util.asyncNoop
24
+ @internal
25
+ ###
26
+ asyncNoop = -> Promise.resolve()
19
27
 
20
28
  ###**
21
29
  Ensures that the given function can only be called a single time.
@@ -97,7 +105,8 @@ up.util = (($) ->
97
105
  ###
98
106
  parseUrl = (urlOrAnchor) ->
99
107
  # In case someone passed us a $link, unwrap it
100
- urlOrAnchor = unJQuery(urlOrAnchor)
108
+ if isJQuery(urlOrAnchor)
109
+ urlOrAnchor = getElement(urlOrAnchor)
101
110
 
102
111
  # If we are handed a parsed URL, just return it
103
112
  if urlOrAnchor.pathname
@@ -159,7 +168,8 @@ up.util = (($) ->
159
168
  $root
160
169
 
161
170
  ###**
162
- @function $create
171
+ @function $createPlaceHolder
172
+ @internal
163
173
  ###
164
174
  $createPlaceholder = (selector, container = document.body) ->
165
175
  $placeholder = $createElementFromSelector(selector)
@@ -273,6 +283,7 @@ up.util = (($) ->
273
283
  @stable
274
284
  ###
275
285
  map = (array, block) ->
286
+ return [] if array.length == 0
276
287
  block = listBlock(block)
277
288
  for item, index in array
278
289
  block(item, index)
@@ -346,7 +357,7 @@ up.util = (($) ->
346
357
  @stable
347
358
  ###
348
359
  isMissing = (object) ->
349
- isUndefined(object) || isNull(object) # || isNaN(object)
360
+ isUndefined(object) || isNull(object)
350
361
 
351
362
  ###**
352
363
  Returns whether the given argument is neither `undefined` nor `null`.
@@ -385,9 +396,11 @@ up.util = (($) ->
385
396
  @stable
386
397
  ###
387
398
  isBlank = (object) ->
388
- isMissing(object) || # null or undefined
389
- (isObject(object) && Object.keys(object).length == 0) ||
390
- (object.length == 0) # string, Array, jQuery
399
+ return true if isMissing(object)
400
+ return false if isFunction(object)
401
+ return true if isObject(object) && Object.keys(object).length == 0 # object
402
+ return true if object.length == 0 # string, array, jQuery
403
+ return false
391
404
 
392
405
  ###**
393
406
  Returns the given argument if the argument is [present](/up.util.isPresent),
@@ -566,16 +579,20 @@ up.util = (($) ->
566
579
  object
567
580
 
568
581
  ###**
569
- If given a jQuery collection, returns the underlying array of DOM element.
582
+ If given a jQuery collection, returns the first native DOM element in the collection.
583
+ If given a string, returns the first element matching that string.
570
584
  If given any other argument, returns the argument unchanged.
571
585
 
572
- @function up.util.unJQuery
573
- @param object
586
+ @function up.util.element
587
+ @param {jQuery|Element|String} object
588
+ @return {Element}
574
589
  @internal
575
590
  ###
576
- unJQuery = (object) ->
591
+ getElement = (object) ->
577
592
  if isJQuery(object)
578
593
  object.get(0)
594
+ else if isString(object)
595
+ $(object).get(0)
579
596
  else
580
597
  object
581
598
 
@@ -607,10 +624,9 @@ up.util = (($) ->
607
624
  if defaults
608
625
  for key, defaultValue of defaults
609
626
  value = merged[key]
610
- if !isGiven(value)
611
- merged[key] = defaultValue
612
- else if isObject(defaultValue) && isObject(value)
613
- merged[key] = options(value, defaultValue)
627
+ if isMissing(value)
628
+ value = defaultValue
629
+ merged[key] = value
614
630
  merged
615
631
 
616
632
  ###**
@@ -711,9 +727,7 @@ up.util = (($) ->
711
727
  ###
712
728
  uniq = (array) ->
713
729
  return array if array.length < 2
714
- set = new Set()
715
- each array, (element) -> set.add(element)
716
- setToArray(set)
730
+ setToArray(arrayToSet(array))
717
731
 
718
732
  ###*
719
733
  This function is like [`uniq`](/up.util.uniq), accept that
@@ -747,6 +761,15 @@ up.util = (($) ->
747
761
  set.forEach (elem) -> array.push(elem)
748
762
  array
749
763
 
764
+ ###*
765
+ @function up.util.arrayToSet
766
+ @internal
767
+ ###
768
+ arrayToSet = (array) ->
769
+ set = new Set()
770
+ array.forEach (elem) -> set.add(elem)
771
+ set
772
+
750
773
  ###**
751
774
  Returns all elements from the given array that return
752
775
  a truthy value when passed to the given function.
@@ -791,6 +814,28 @@ up.util = (($) ->
791
814
  select array1, (element) ->
792
815
  contains(array2, element)
793
816
 
817
+ addClass = (element, klassOrKlasses) ->
818
+ changeClassList(element, klassOrKlasses, 'add')
819
+
820
+ removeClass = (element, klassOrKlasses) ->
821
+ changeClassList(element, klassOrKlasses, 'remove')
822
+
823
+ changeClassList = (element, klassOrKlasses, fnName) ->
824
+ classList = getElement(element).classList
825
+ if isArray(klassOrKlasses)
826
+ each klassOrKlasses, (klass) ->
827
+ classList[fnName](klass)
828
+ else
829
+ classList[fnName](klassOrKlasses)
830
+
831
+ addTemporaryClass = (element, klassOrKlasses) ->
832
+ addClass(element, klassOrKlasses)
833
+ -> removeClass(element, klassOrKlasses)
834
+
835
+ hasClass = (element, klass) ->
836
+ classList = getElement(element).classList
837
+ classList.contains(klass)
838
+
794
839
  ###**
795
840
  Returns the first [present](/up.util.isPresent) element attribute
796
841
  among the given list of attribute names.
@@ -869,8 +914,9 @@ up.util = (($) ->
869
914
  # This is how Bootstrap does it also:
870
915
  # https://github.com/twbs/bootstrap/blob/c591227602996c542b9fd0cb65cff3cc9519bdd5/dist/js/bootstrap.js#L1187
871
916
  $outer = $('<div>')
917
+ outer = $outer.get(0)
872
918
  $outer.attr('up-viewport', '')
873
- $outer.css
919
+ writeInlineStyle outer,
874
920
  position: 'absolute'
875
921
  top: '0'
876
922
  left: '0'
@@ -878,7 +924,6 @@ up.util = (($) ->
878
924
  height: '100px' # Firefox needs at least 100px to show a scrollbar
879
925
  overflowY: 'scroll'
880
926
  $outer.appendTo(document.body)
881
- outer = $outer.get(0)
882
927
  width = outer.offsetWidth - outer.clientWidth
883
928
  $outer.remove()
884
929
  width
@@ -894,32 +939,17 @@ up.util = (($) ->
894
939
  $body = $(body)
895
940
  html = document.documentElement
896
941
 
897
- bodyOverflow = $body.css('overflow-y')
942
+ bodyOverflow = readComputedStyle($body, 'overflowY')
898
943
 
899
944
  forcedScroll = (bodyOverflow == 'scroll')
900
945
  forcedHidden = (bodyOverflow == 'hidden')
901
946
 
902
947
  forcedScroll || (!forcedHidden && html.scrollHeight > html.clientHeight)
903
948
 
904
- ###**
905
- Modifies the given function so it only runs once.
906
- Subsequent calls will return the previous return value.
907
-
908
- @function up.util.once
909
- @param {Function} fun
910
- @experimental
911
- ###
912
- once = (fun) ->
913
- result = undefined
914
- (args...) ->
915
- result = fun(args...) if fun?
916
- fun = undefined
917
- result
918
-
919
949
  ###**
920
950
  Temporarily sets the CSS for the given element.
921
951
 
922
- @function up.util.temporaryCss
952
+ @function up.util.writeTemporaryStyle
923
953
  @param {jQuery} $element
924
954
  @param {Object} css
925
955
  @param {Function} [block]
@@ -929,35 +959,18 @@ up.util = (($) ->
929
959
  A function that restores the original CSS when called.
930
960
  @internal
931
961
  ###
932
- temporaryCss = (elementOrSelector, css, block) ->
962
+ writeTemporaryStyle = (elementOrSelector, newCss, block) ->
933
963
  $element = $(elementOrSelector)
934
- oldCss = $element.css(Object.keys(css))
935
- $element.css(css)
936
- memo = -> $element.css(oldCss)
964
+ oldStyles = readInlineStyle($element, Object.keys(newCss))
965
+ restoreOldStyles = -> writeInlineStyle($element, oldStyles)
966
+ writeInlineStyle($element, newCss)
937
967
  if block
968
+ # If a block was passed, we run the block and restore old styles.
938
969
  block()
939
- memo()
940
- else
941
- once(memo)
942
-
943
- ###**
944
- Forces the given jQuery element into an accelerated compositing layer.
945
-
946
- @function up.util.forceCompositing
947
- @internal
948
- ###
949
- forceCompositing = ($element) ->
950
- oldTransforms = $element.css(['transform', '-webkit-transform'])
951
- if isBlank(oldTransforms) || oldTransforms['transform'] == 'none'
952
- memo = -> $element.css(oldTransforms)
953
- $element.css
954
- 'transform': 'translateZ(0)'
955
- '-webkit-transform': 'translateZ(0)' # Safari
970
+ restoreOldStyles()
956
971
  else
957
- # Since the element already has a transform, it is already
958
- # drawn using compositing. Do nothing.
959
- memo = ->
960
- memo
972
+ # If no block was passed, we return the restoration function.
973
+ restoreOldStyles
961
974
 
962
975
  ###**
963
976
  Forces a repaint of the given element.
@@ -966,21 +979,30 @@ up.util = (($) ->
966
979
  @internal
967
980
  ###
968
981
  forceRepaint = (element) ->
969
- element = unJQuery(element)
982
+ element = getElement(element)
970
983
  element.offsetHeight
971
984
 
972
- cssAnimate = (elementOrSelector, lastFrame, opts) ->
985
+ ###*
986
+ @function up.util.finishTransition
987
+ @internal
988
+ ###
989
+ concludeCssTransition = (element) ->
990
+ undo = writeTemporaryStyle(element, transition: 'none')
991
+ # Browsers need to paint at least one frame without a transition to stop the
992
+ # animation. In theory we could just wait until the next paint, but in case
993
+ # someone will set another transition after us, let's force a repaint here.
994
+ forceRepaint(element)
995
+ return undo
973
996
 
974
997
  ###**
975
998
  @internal
976
999
  ###
977
1000
  margins = (selectorOrElement) ->
978
- $element = $(selectorOrElement)
979
- withUnits = $element.css(['margin-top', 'margin-right', 'margin-bottom', 'margin-left'])
980
- top: parseFloat(withUnits['margin-top'])
981
- right: parseFloat(withUnits['margin-right'])
982
- bottom: parseFloat(withUnits['margin-bottom'])
983
- left: parseFloat(withUnits['margin-left'])
1001
+ element = getElement(selectorOrElement)
1002
+ top: readComputedStyleNumber(element, 'marginTop')
1003
+ right: readComputedStyleNumber(element, 'marginRight')
1004
+ bottom: readComputedStyleNumber(element, 'marginBottom')
1005
+ left: readComputedStyleNumber(element, 'marginLeft')
984
1006
 
985
1007
  ###**
986
1008
  Measures the given element.
@@ -1047,7 +1069,12 @@ up.util = (($) ->
1047
1069
  @internal
1048
1070
  ###
1049
1071
  selectInSubtree = ($element, selector) ->
1050
- $element.find(selector).addBack(selector)
1072
+ # This implementation is faster than $element.find(selector).addBack(Seelctor)
1073
+ $matches = $()
1074
+ if $element.is(selector)
1075
+ $matches = $matches.add($element)
1076
+ $matches = $matches.add($element.find(selector))
1077
+ $matches
1051
1078
 
1052
1079
  ###**
1053
1080
  Looks for the given selector in the element, its descendants and its ancestors.
@@ -1109,7 +1136,7 @@ up.util = (($) ->
1109
1136
  only = (object, properties...) ->
1110
1137
  filtered = {}
1111
1138
  for property in properties
1112
- if object.hasOwnProperty(property)
1139
+ if property of object
1113
1140
  filtered[property] = object[property]
1114
1141
  filtered
1115
1142
 
@@ -1188,63 +1215,6 @@ up.util = (($) ->
1188
1215
  array.splice(index, 1)
1189
1216
  element
1190
1217
 
1191
- ###**
1192
- @function up.util.multiSelector
1193
- @internal
1194
- ###
1195
- multiSelector = (parts) ->
1196
-
1197
- obj = {}
1198
- selectors = []
1199
- elements = []
1200
-
1201
- for part in parts
1202
- if isString(part)
1203
- selectors.push(part)
1204
- else
1205
- elements.push(part)
1206
-
1207
- obj.parsed = elements
1208
-
1209
- if selectors.length
1210
- # Although other methods would work with an array of
1211
- # selectors, we combine them to a single separator for
1212
- # performance reasons.
1213
- combinedSelector = selectors.join(', ')
1214
- obj.parsed.push(combinedSelector)
1215
-
1216
- obj.select = ->
1217
- obj.find(undefined)
1218
-
1219
- obj.find = ($root) ->
1220
- $result = nullJQuery()
1221
- for selector in obj.parsed
1222
- $matches = if $root then $root.find(selector) else $(selector)
1223
- $result = $result.add($matches)
1224
- $result
1225
-
1226
- obj.selectInSubtree = ($start) ->
1227
- $matches = obj.find($start)
1228
- $matches = $matches.add($start) if obj.doesMatch($start)
1229
- $matches
1230
-
1231
- obj.doesMatch = (element) ->
1232
- $element = $(element)
1233
- any obj.parsed, (selector) -> $element.is(selector)
1234
-
1235
- obj.seekUp = (start) ->
1236
- $start = $(start)
1237
- $element = $start
1238
- $result = undefined
1239
- while $element.length
1240
- if obj.doesMatch($element)
1241
- $result = $element
1242
- break
1243
- $element = $element.parent()
1244
- $result || nullJQuery()
1245
-
1246
- obj
1247
-
1248
1218
  ###**
1249
1219
  If the given `value` is a function, calls the function with the given `args`.
1250
1220
  Otherwise it just returns `value`.
@@ -1290,7 +1260,7 @@ up.util = (($) ->
1290
1260
  @internal
1291
1261
  ###
1292
1262
  unwrapElement = (wrapper) ->
1293
- wrapper = unJQuery(wrapper)
1263
+ wrapper = getElement(wrapper)
1294
1264
  parent = wrapper.parentNode;
1295
1265
  wrappedNodes = toArray(wrapper.childNodes)
1296
1266
  each wrappedNodes, (wrappedNode) ->
@@ -1304,7 +1274,7 @@ up.util = (($) ->
1304
1274
  offsetParent = ($element) ->
1305
1275
  $match = undefined
1306
1276
  while ($element = $element.parent()) && $element.length
1307
- position = $element.css('position')
1277
+ position = readComputedStyle($element, 'position')
1308
1278
  if position == 'absolute' || position == 'relative' || $element.is('body')
1309
1279
  $match = $element
1310
1280
  break
@@ -1319,7 +1289,7 @@ up.util = (($) ->
1319
1289
  isFixed = (element) ->
1320
1290
  $element = $(element)
1321
1291
  loop
1322
- position = $element.css('position')
1292
+ position = readComputedStyle($element, 'position')
1323
1293
  if position == 'fixed'
1324
1294
  return true
1325
1295
  else
@@ -1339,7 +1309,7 @@ up.util = (($) ->
1339
1309
  # scrollTop of the viewport.
1340
1310
  elementCoords = $element.position()
1341
1311
  futureParentCoords = $futureOffsetParent.offset()
1342
- $element.css
1312
+ writeInlineStyle $element,
1343
1313
  position: 'absolute'
1344
1314
  left: elementCoords.left - futureParentCoords.left
1345
1315
  top: elementCoords.top - futureParentCoords.top + $viewport.scrollTop()
@@ -1560,12 +1530,76 @@ up.util = (($) ->
1560
1530
  else
1561
1531
  {}
1562
1532
 
1533
+ CASE_CONVERSION_GROUP = /[^\-\_]+?(?=[A-Z\-\_]|$)/g
1534
+
1535
+ convertCase = (string, separator, fn) ->
1536
+ parts = string.match(CASE_CONVERSION_GROUP)
1537
+ parts = map parts, fn
1538
+ parts.join(separator)
1539
+
1540
+ ###**
1541
+ Returns a copy of the given string that is transformed to `kebab-case`.
1542
+
1543
+ @function up.util.kebabCase
1544
+ @param {string} string
1545
+ @return {string}
1546
+ @internal
1547
+ ###
1548
+ kebabCase = (string) ->
1549
+ convertCase string, '-', (part) -> part.toLowerCase()
1550
+
1551
+ ###**
1552
+ Returns a copy of the given string that is transformed to `camelCase`.
1553
+
1554
+ @function up.util.camelCase
1555
+ @param {string} string
1556
+ @return {string}
1557
+ @internal
1558
+ ###
1559
+ camelCase = (string) ->
1560
+ convertCase string, '', (part, i) ->
1561
+ if i == 0
1562
+ part.toLowerCase()
1563
+ else
1564
+ part.charAt(0).toUpperCase() + part.substr(1).toLowerCase()
1565
+
1566
+ ###**
1567
+ Returns a copy of the given object with all keys renamed
1568
+ in `kebab-case`.
1569
+
1570
+ Does not change the given object.
1571
+
1572
+ @function up.util.kebabCaseKeys
1573
+ @param {object} obj
1574
+ @return {object}
1575
+ @internal
1576
+ ###
1577
+ kebabCaseKeys = (obj) ->
1578
+ copyWithRenamedKeys(obj, kebabCase)
1579
+
1580
+ ###**
1581
+ Returns a copy of the given object with all keys renamed
1582
+ in `camelCase`.
1583
+
1584
+ Does not change the given object.
1585
+
1586
+ @function up.util.camelCaseKeys
1587
+ @param {object} obj
1588
+ @return {object}
1589
+ @internal
1590
+ ###
1591
+ camelCaseKeys = (obj) ->
1592
+ copyWithRenamedKeys(obj, camelCase)
1593
+
1594
+ copyWithRenamedKeys = (obj, keyTransformer) ->
1595
+ result = {}
1596
+ for k, v of obj
1597
+ k = keyTransformer(k)
1598
+ result[k] = v
1599
+ result
1600
+
1563
1601
  opacity = (element) ->
1564
- rawOpacity = $(element).css('opacity')
1565
- if isGiven(rawOpacity)
1566
- parseFloat(rawOpacity)
1567
- else
1568
- undefined
1602
+ readComputedStyleNumber(element, 'opacity')
1569
1603
 
1570
1604
  whenReady = memoize ->
1571
1605
  if $.isReady
@@ -1583,7 +1617,7 @@ up.util = (($) ->
1583
1617
  @internal
1584
1618
  ###
1585
1619
  isDetached = (element) ->
1586
- element = unJQuery(element)
1620
+ element = getElement(element)
1587
1621
  # This is by far the fastest way to do this
1588
1622
  not $.contains(document.documentElement, element)
1589
1623
 
@@ -1726,6 +1760,135 @@ up.util = (($) ->
1726
1760
  $insertion.replaceWith($new)
1727
1761
  $old
1728
1762
 
1763
+ ###**
1764
+ Hides the given element faster than `jQuery.fn.hide()`.
1765
+
1766
+ @function up.util.hide
1767
+ @param {jQuery|Element} element
1768
+ ###
1769
+ hide = (element) ->
1770
+ writeInlineStyle(element, display: 'none')
1771
+
1772
+ ###**
1773
+ Gets the computed style(s) for the given element.
1774
+
1775
+ @function up.util.readComputedStyle
1776
+ @param {jQuery|Element} element
1777
+ @param {String|Array} propOrProps
1778
+ One or more CSS property names in camelCase.
1779
+ @return {string|object}
1780
+ @internal
1781
+ ###
1782
+ readComputedStyle = (element, props) ->
1783
+ element = getElement(element)
1784
+ style = window.getComputedStyle(element)
1785
+ extractFromStyleObject(style, props)
1786
+
1787
+ ###**
1788
+ Gets a computed style value for the given element.
1789
+ If a value is set, the value is parsed to a number before returning.
1790
+
1791
+ @function up.util.readComputedStyleNumber
1792
+ @param {jQuery|Element} element
1793
+ @param {String} prop
1794
+ A CSS property name in camelCase.
1795
+ @return {string|object}
1796
+ @internal
1797
+ ###
1798
+ readComputedStyleNumber = (element, prop) ->
1799
+ rawValue = readComputedStyle(element, prop)
1800
+ if isGiven(rawValue)
1801
+ parseFloat(rawValue)
1802
+ else
1803
+ undefined
1804
+
1805
+ ###**
1806
+ Gets the given inline style(s) from the given element's `[style]` attribute.
1807
+
1808
+ @function up.util.readInlineStyle
1809
+ @param {jQuery|Element} element
1810
+ @param {String|Array} propOrProps
1811
+ One or more CSS property names in camelCase.
1812
+ @return {string|object}
1813
+ @internal
1814
+ ###
1815
+ readInlineStyle = (element, props) ->
1816
+ element = getElement(element)
1817
+ style = element.style
1818
+ extractFromStyleObject(style, props)
1819
+
1820
+ extractFromStyleObject = (style, keyOrKeys) ->
1821
+ if isString(keyOrKeys)
1822
+ style[keyOrKeys]
1823
+ else # array
1824
+ only(style, keyOrKeys...)
1825
+
1826
+ ###**
1827
+ Merges the given inline style(s) into the given element's `[style]` attribute.
1828
+
1829
+ @function up.util.readInlineStyle
1830
+ @param {jQuery|Element} element
1831
+ @param {Object} props
1832
+ One or more CSS properties with camelCase keys.
1833
+ @return {string|object}
1834
+ @internal
1835
+ ###
1836
+ writeInlineStyle = (element, props) ->
1837
+ element = getElement(element)
1838
+ style = element.style
1839
+ for key, value of props
1840
+ value = normalizeStyleValueForWrite(key, value)
1841
+ style[key] = value
1842
+
1843
+ normalizeStyleValueForWrite = (key, value) ->
1844
+ if isMissing(value)
1845
+ value = ''
1846
+ else if CSS_LENGTH_PROPS.has(key)
1847
+ value = cssLength(value)
1848
+ value
1849
+
1850
+ CSS_LENGTH_PROPS = arrayToSet [
1851
+ 'top', 'right', 'bottom', 'left',
1852
+ 'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
1853
+ 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
1854
+ 'width', 'height',
1855
+ 'maxWidth', 'maxHeight',
1856
+ 'minWidth', 'minHeight',
1857
+ ]
1858
+
1859
+ ###*
1860
+ Converts the given value to a CSS length value, adding a `px` unit if required.
1861
+
1862
+ @function up.util.cssLength
1863
+ @internal
1864
+ ###
1865
+ cssLength = (obj) ->
1866
+ if isNumber(obj) || (isString(obj) && /^\d+$/.test(obj))
1867
+ obj.toString() + "px"
1868
+ else
1869
+ obj
1870
+
1871
+ ###*
1872
+ Returns whether the given element has a CSS transition set.
1873
+
1874
+ @function up.util.hasCssTransition
1875
+ @return {boolean}
1876
+ @internal
1877
+ ###
1878
+ hasCssTransition = (elementOrStyleHash) ->
1879
+ if isOptions(elementOrStyleHash)
1880
+ style = elementOrStyleHash
1881
+ else
1882
+ element = getElement(element)
1883
+ style = getComputedStyle(element)
1884
+
1885
+ prop = style.transitionProperty
1886
+ duration = style.transitionDuration
1887
+ # The default transition for elements is actually "all 0s ease 0s"
1888
+ # instead of "none", although that has the same effect as "none".
1889
+ noTransition = (prop == 'none' || (prop == 'all' && duration == 0))
1890
+ not noTransition
1891
+
1729
1892
  ###**
1730
1893
  Flattens the given `array` a single level deep.
1731
1894
 
@@ -1822,6 +1985,16 @@ up.util = (($) ->
1822
1985
  isBodyDescendant = (element) ->
1823
1986
  $(element).parents('body').length > 0
1824
1987
 
1988
+ isEqual = (a, b) ->
1989
+ if typeof(a) != typeof(b)
1990
+ false
1991
+ else if isArray(a)
1992
+ a.length == b.length && all(a, (elem, index) -> isEqual(elem, b[index]))
1993
+ else if isObject(a)
1994
+ fail('isEqual cannot compare objects yet')
1995
+ else
1996
+ a == b
1997
+
1825
1998
  requestDataAsArray: requestDataAsArray
1826
1999
  requestDataAsQuery: requestDataAsQuery
1827
2000
  appendRequestData: appendRequestData
@@ -1880,14 +2053,17 @@ up.util = (($) ->
1880
2053
  isUnmodifiedKeyEvent: isUnmodifiedKeyEvent
1881
2054
  isUnmodifiedMouseEvent: isUnmodifiedMouseEvent
1882
2055
  nullJQuery: nullJQuery
1883
- unJQuery: unJQuery
2056
+ element: getElement
1884
2057
  setTimer: setTimer
1885
2058
  nextFrame: nextFrame
1886
2059
  measure: measure
1887
- temporaryCss: temporaryCss
1888
- cssAnimate: cssAnimate
1889
- forceCompositing: forceCompositing
2060
+ addClass: addClass
2061
+ removeClass: removeClass
2062
+ hasClass: hasClass
2063
+ addTemporaryClass: addTemporaryClass
2064
+ writeTemporaryStyle: writeTemporaryStyle
1890
2065
  forceRepaint: forceRepaint
2066
+ concludeCssTransition: concludeCssTransition
1891
2067
  escapePressed: escapePressed
1892
2068
  copyAttributes: copyAttributes
1893
2069
  selectInSubtree: selectInSubtree
@@ -1908,7 +2084,10 @@ up.util = (($) ->
1908
2084
  config: config
1909
2085
  openConfig: openConfig
1910
2086
  unwrapElement: unwrapElement
1911
- multiSelector: multiSelector
2087
+ camelCase: camelCase
2088
+ camelCaseKeys: camelCaseKeys
2089
+ kebabCase: kebabCase
2090
+ kebabCaseKeys: kebabCaseKeys
1912
2091
  error: fail
1913
2092
  pluckData: pluckData
1914
2093
  pluckKey: pluckKey
@@ -1916,6 +2095,7 @@ up.util = (($) ->
1916
2095
  extractOptions: extractOptions
1917
2096
  isDetached: isDetached
1918
2097
  noop: noop
2098
+ asyncNoop: asyncNoop
1919
2099
  opacity: opacity
1920
2100
  whenReady: whenReady
1921
2101
  identity: identity
@@ -1937,7 +2117,14 @@ up.util = (($) ->
1937
2117
  isBodyDescendant: isBodyDescendant
1938
2118
  isCrossDomain: isCrossDomain
1939
2119
  microtask: microtask
1940
-
2120
+ isEqual: isEqual
2121
+ hide: hide
2122
+ cssLength: cssLength
2123
+ readComputedStyle: readComputedStyle
2124
+ readComputedStyleNumber: readComputedStyleNumber
2125
+ readInlineStyle: readInlineStyle
2126
+ writeInlineStyle: writeInlineStyle
2127
+ hasCssTransition: hasCssTransition
1941
2128
 
1942
2129
  )(jQuery)
1943
2130