unpoly-rails 0.56.7 → 0.57.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1
  3. data/dist/unpoly.js +1569 -793
  4. data/dist/unpoly.min.js +4 -4
  5. data/lib/assets/javascripts/unpoly.coffee +2 -0
  6. data/lib/assets/javascripts/unpoly/browser.coffee.erb +25 -41
  7. data/lib/assets/javascripts/unpoly/bus.coffee.erb +20 -6
  8. data/lib/assets/javascripts/unpoly/classes/cache.coffee +23 -13
  9. data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +87 -0
  10. data/lib/assets/javascripts/unpoly/classes/focus_tracker.coffee +29 -0
  11. data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +7 -4
  12. data/lib/assets/javascripts/unpoly/classes/record.coffee +1 -1
  13. data/lib/assets/javascripts/unpoly/classes/request.coffee +38 -45
  14. data/lib/assets/javascripts/unpoly/classes/response.coffee +16 -1
  15. data/lib/assets/javascripts/unpoly/classes/store/memory.coffee +26 -0
  16. data/lib/assets/javascripts/unpoly/classes/store/session.coffee +59 -0
  17. data/lib/assets/javascripts/unpoly/cookie.coffee +56 -0
  18. data/lib/assets/javascripts/unpoly/dom.coffee.erb +67 -39
  19. data/lib/assets/javascripts/unpoly/feedback.coffee +2 -2
  20. data/lib/assets/javascripts/unpoly/form.coffee.erb +23 -12
  21. data/lib/assets/javascripts/unpoly/history.coffee +2 -2
  22. data/lib/assets/javascripts/unpoly/layout.coffee.erb +118 -99
  23. data/lib/assets/javascripts/unpoly/link.coffee.erb +12 -5
  24. data/lib/assets/javascripts/unpoly/log.coffee +6 -5
  25. data/lib/assets/javascripts/unpoly/modal.coffee.erb +9 -2
  26. data/lib/assets/javascripts/unpoly/motion.coffee.erb +2 -6
  27. data/lib/assets/javascripts/unpoly/namespace.coffee.erb +2 -2
  28. data/lib/assets/javascripts/unpoly/params.coffee.erb +522 -0
  29. data/lib/assets/javascripts/unpoly/popup.coffee.erb +3 -3
  30. data/lib/assets/javascripts/unpoly/proxy.coffee +42 -34
  31. data/lib/assets/javascripts/unpoly/{syntax.coffee → syntax.coffee.erb} +59 -117
  32. data/lib/assets/javascripts/unpoly/{util.coffee → util.coffee.erb} +206 -171
  33. data/lib/unpoly/rails/version.rb +1 -1
  34. data/package.json +1 -1
  35. data/spec_app/Gemfile.lock +1 -1
  36. data/spec_app/app/assets/javascripts/integration_test.coffee +0 -4
  37. data/spec_app/app/assets/stylesheets/integration_test.sass +7 -1
  38. data/spec_app/app/controllers/pages_controller.rb +4 -0
  39. data/spec_app/app/views/form_test/basics/new.erb +34 -5
  40. data/spec_app/app/views/form_test/submission_result.erb +2 -2
  41. data/spec_app/app/views/form_test/uploads/new.erb +15 -2
  42. data/spec_app/app/views/hash_test/unpoly.erb +30 -0
  43. data/spec_app/app/views/pages/start.erb +2 -1
  44. data/spec_app/spec/javascripts/helpers/parse_form_data.js.coffee +17 -2
  45. data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +5 -0
  46. data/spec_app/spec/javascripts/helpers/to_be_error.coffee +1 -1
  47. data/spec_app/spec/javascripts/helpers/to_match_selector.coffee +5 -0
  48. data/spec_app/spec/javascripts/up/browser_spec.js.coffee +8 -8
  49. data/spec_app/spec/javascripts/up/bus_spec.js.coffee +58 -20
  50. data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +78 -0
  51. data/spec_app/spec/javascripts/up/classes/focus_tracker_spec.coffee +31 -0
  52. data/spec_app/spec/javascripts/up/classes/request_spec.coffee +50 -0
  53. data/spec_app/spec/javascripts/up/classes/store/memory_spec.js.coffee +67 -0
  54. data/spec_app/spec/javascripts/up/classes/store/session_spec.js.coffee +113 -0
  55. data/spec_app/spec/javascripts/up/dom_spec.js.coffee +133 -45
  56. data/spec_app/spec/javascripts/up/form_spec.js.coffee +13 -13
  57. data/spec_app/spec/javascripts/up/layout_spec.js.coffee +110 -26
  58. data/spec_app/spec/javascripts/up/link_spec.js.coffee +1 -1
  59. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +1 -0
  60. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +52 -51
  61. data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +2 -2
  62. data/spec_app/spec/javascripts/up/params_spec.coffee +768 -0
  63. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +75 -36
  64. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +48 -15
  65. data/spec_app/spec/javascripts/up/util_spec.js.coffee +148 -131
  66. metadata +17 -5
  67. data/spec_app/spec/javascripts/up/classes/.keep +0 -0
@@ -92,7 +92,7 @@ up.feedback = (($) ->
92
92
  for attr in ['href', 'up-href', 'up-alias']
93
93
  if value = u.presentAttr($section, attr)
94
94
  # Allow to include multiple space-separated URLs in [up-alias]
95
- for url in value.split(/\s+/)
95
+ for url in u.splitValues(value)
96
96
  unless url == '#'
97
97
  url = normalizeUrl(url)
98
98
  urls.push(url)
@@ -371,4 +371,4 @@ up.feedback = (($) ->
371
371
 
372
372
  )(jQuery)
373
373
 
374
- up.renamedModule 'navigation', 'feedback'
374
+ up.deprecateRenamedModule 'navigation', 'feedback'
@@ -28,13 +28,16 @@ up.form = (($) ->
28
28
  By default this looks for a `<fieldset>`, `<label>` or `<form>`
29
29
  around the validating input field, or any element with an
30
30
  `up-fieldset` attribute.
31
- @param {string} [config.fields=[':input']]
31
+ @param {string} [config.fields]
32
32
  An array of CSS selectors that represent form fields, such as `input` or `select`.
33
+ @param {string} [config.submitButtons]
34
+ An array of CSS selectors that represent submit buttons, such as `input[type=submit]`.
33
35
  @stable
34
36
  ###
35
37
  config = u.config
36
38
  validateTargets: ['[up-fieldset]:has(&)', 'fieldset:has(&)', 'label:has(&)', 'form:has(&)']
37
- fields: [':input']
39
+ fields: ['select', 'input:not([type=submit]):not([type=image])', 'button[type]:not([type=submit])', 'textarea'],
40
+ submitButtons: ['input[type=submit]', 'input[type=image]', 'button[type=submit]', 'button:not([type])']
38
41
  observeDelay: 0
39
42
 
40
43
  reset = ->
@@ -47,6 +50,13 @@ up.form = (($) ->
47
50
  fieldSelector = ->
48
51
  config.fields.join(',')
49
52
 
53
+ ###**
54
+ @function up.form.submitButtonSelector
55
+ @internal
56
+ ###
57
+ submitButtonSelector = ->
58
+ config.submitButtons.join(',')
59
+
50
60
  ###**
51
61
  Submits a form via AJAX and updates a page fragment with the response.
52
62
 
@@ -129,9 +139,9 @@ up.form = (($) ->
129
139
  with the request.
130
140
  @param {string} [options.layer='auto']
131
141
  The name of the layer that ought to be updated. Valid values are
132
- `auto`, `page`, `modal` and `popup`.
142
+ `'auto'`, `'page'`, `'modal'` and `'popup'`.
133
143
 
134
- If set to `auto` (default), Unpoly will try to find a match in the form's layer.
144
+ If set to `'auto'` (default), Unpoly will try to find a match in the form's layer.
135
145
  @param {string} [options.failLayer='auto']
136
146
  The name of the layer that ought to be updated if the server sends a non-200 status code.
137
147
  @return {Promise}
@@ -158,7 +168,7 @@ up.form = (($) ->
158
168
  options.origin = u.option(options.origin, $form)
159
169
  options.layer = u.option(options.layer, $form.attr('up-layer'), 'auto')
160
170
  options.failLayer = u.option(options.failLayer, $form.attr('up-fail-layer'), 'auto')
161
- options.data = u.requestDataFromForm($form)
171
+ options.params = up.params.fromForm($form)
162
172
  options = u.merge(options, up.motion.animateOptions(options, $form))
163
173
 
164
174
  if options.validate
@@ -167,7 +177,7 @@ up.form = (($) ->
167
177
  options.failTransition = false
168
178
  options.headers[up.protocol.config.validateHeader] = options.validate
169
179
 
170
- up.bus.whenEmitted('up:form:submit', $element: $form).then ->
180
+ up.bus.whenEmitted('up:form:submit', message: 'Submitting form', $form: $form, $element: $form).then ->
171
181
  up.feedback.start($form)
172
182
 
173
183
  # If we can't update the location URL, fall back to a vanilla form submission.
@@ -184,7 +194,7 @@ up.form = (($) ->
184
194
  This event is [emitted](/up.emit) when a form is [submitted](/up.submit) through Unpoly.
185
195
 
186
196
  @event up:form:submit
187
- @param {jQuery} event.$element
197
+ @param {jQuery} event.$form
188
198
  The `<form>` element that will be submitted.
189
199
  @param event.preventDefault()
190
200
  Event listeners may call this method to prevent the form from being submitted.
@@ -430,11 +440,11 @@ up.form = (($) ->
430
440
  $target = $(target)
431
441
  fieldValues ||= switcherValues(findSwitcherForTarget($target))
432
442
  if hideValues = $target.attr('up-hide-for')
433
- hideValues = hideValues.split(' ')
443
+ hideValues = u.splitValues(hideValues)
434
444
  show = u.intersect(fieldValues, hideValues).length == 0
435
445
  else
436
446
  if showValues = $target.attr('up-show-for')
437
- showValues = showValues.split(' ')
447
+ showValues = u.splitValues(showValues)
438
448
  else
439
449
  # If the target has neither up-show-for or up-hide-for attributes,
440
450
  # assume the user wants the target to be visible whenever anything
@@ -472,7 +482,7 @@ up.form = (($) ->
472
482
 
473
483
  \#\#\# Failed submission
474
484
 
475
- When the server was unable to save the form due to invalid data,
485
+ When the server was unable to save the form due to invalid params,
476
486
  it will usually re-render an updated copy of the form with
477
487
  validation messages.
478
488
 
@@ -555,9 +565,9 @@ up.form = (($) ->
555
565
  or `method` (vanilla HTML) for the same purpose.
556
566
  @param {string} [up-layer='auto']
557
567
  The name of the layer that ought to be updated. Valid values are
558
- `auto`, `page`, `modal` and `popup`.
568
+ `'auto'`, `'page'`, `'modal'` and `'popup'`.
559
569
 
560
- If set to `auto` (default), Unpoly will try to find a match in the form's layer.
570
+ If set to `'auto'` (default), Unpoly will try to find a match in the form's layer.
561
571
  If no match was found in that layer,
562
572
  Unpoly will search in other layers, starting from the topmost layer.
563
573
  @param {string} [up-fail-layer='auto']
@@ -978,6 +988,7 @@ up.form = (($) ->
978
988
  switchTargets: switchTargets
979
989
  autosubmit: autosubmit
980
990
  fieldSelector: fieldSelector
991
+ submitButtonSelector: submitButtonSelector
981
992
 
982
993
  )(jQuery)
983
994
 
@@ -1,7 +1,7 @@
1
1
  ###**
2
2
  History
3
3
  ========
4
-
4
+
5
5
  In an Unpoly app, every page has an URL.
6
6
 
7
7
  [Fragment updates](/up.link) automatically update the URL.
@@ -60,7 +60,7 @@ up.history = (($) ->
60
60
  ###
61
61
  currentUrl = (normalizeOptions) ->
62
62
  normalizeUrl(up.browser.url(), normalizeOptions)
63
-
63
+
64
64
  isCurrentUrl = (url) ->
65
65
  normalizeOptions = { stripTrailingSlash: true }
66
66
  normalizeUrl(url, normalizeOptions) == currentUrl(normalizeOptions)
@@ -115,23 +115,16 @@ up.layout = (($) ->
115
115
  @experimental
116
116
  ###
117
117
  scroll = (viewport, scrollTop, options) ->
118
- $scrollable = scrollableElementForViewport(viewport)
118
+ $viewport = $(viewport)
119
119
  options = u.options(options)
120
120
  options.duration = u.option(options.duration, config.duration)
121
121
  options.easing = u.option(options.easing, config.easing)
122
122
 
123
- finishScrolling($scrollable).then ->
123
+ finishScrolling($viewport).then ->
124
124
  if up.motion.isEnabled() && options.duration > 0
125
- scrollWithAnimateNow($scrollable, scrollTop, options)
125
+ scrollWithAnimateNow($viewport, scrollTop, options)
126
126
  else
127
- scrollAbruptlyNow($scrollable, scrollTop)
128
-
129
- scrollableElementForViewport = (viewport) ->
130
- $viewport = $(viewport)
131
- if $viewport.get(0) == document
132
- $('html, body') # FML
133
- else
134
- $viewport
127
+ scrollAbruptlyNow($viewport, scrollTop)
135
128
 
136
129
  scrollWithAnimateNow = ($scrollable, scrollTop, animateOptions) ->
137
130
  start = ->
@@ -164,7 +157,7 @@ up.layout = (($) ->
164
157
  # Don't emit expensive events if no animation can be running anyway
165
158
  return Promise.resolve() unless up.motion.isEnabled()
166
159
 
167
- $scrollable = scrollableElementForViewport(element)
160
+ $scrollable = viewportOf(element)
168
161
  scrollingTracker.finish($scrollable)
169
162
 
170
163
  ###**
@@ -180,22 +173,25 @@ up.layout = (($) ->
180
173
  @return {Object}
181
174
  @internal
182
175
  ###
183
- measureObstruction = ->
184
- measurePosition = (obstructor, cssAttr) ->
185
- $obstructor = $(obstructor)
186
- anchorPosition = u.readComputedStyleNumber($obstructor, cssAttr)
187
- unless u.isPresent(anchorPosition)
188
- up.fail("Fixed element %o must have a CSS attribute %s", $obstructor.get(0), cssAttr)
189
- anchorPosition + $obstructor.height()
190
-
191
- fixedTopBottoms = for obstructor in $(config.fixedTop.join(', '))
192
- measurePosition(obstructor, 'top')
193
-
194
- fixedBottomTops = for obstructor in $(config.fixedBottom.join(', '))
195
- measurePosition(obstructor, 'bottom')
196
-
197
- top: Math.max(0, fixedTopBottoms...)
198
- bottom: Math.max(0, fixedBottomTops...)
176
+ measureObstruction = (viewportHeight) ->
177
+ composeHeight = (obstructor, distanceFromEdgeProps) ->
178
+ distanceFromEdge = u.sum(distanceFromEdgeProps, (prop) -> u.readComputedStyleNumber(obstructor, prop)) || 0
179
+ distanceFromEdge + obstructor.offsetHeight
180
+
181
+ measureTopObstructor = (obstructor) ->
182
+ composeHeight(obstructor, ['top', 'margin-top'])
183
+
184
+ measureBottomObstructor = (obstructor) ->
185
+ composeHeight(obstructor, ['bottom', 'margin-bottom'])
186
+
187
+ $topObstructors = $(config.fixedTop.join(', '))
188
+ $bottomObstructors = $(config.fixedBottom.join(', '))
189
+
190
+ topObstructions = u.map($topObstructors, measureTopObstructor)
191
+ bottomObstructions = u.map($bottomObstructors, measureBottomObstructor)
192
+
193
+ top: Math.max(0, topObstructions...)
194
+ bottom: Math.max(0, bottomObstructions...)
199
195
 
200
196
  ###**
201
197
  Scroll's the given element's viewport so the first rows of the
@@ -250,7 +246,7 @@ up.layout = (($) ->
250
246
 
251
247
  snap = u.option(options.snap, config.snap)
252
248
 
253
- viewportIsDocument = $viewport.is(document)
249
+ viewportIsDocument = $viewport.is(up.browser.documentViewportSelector())
254
250
  viewportHeight = if viewportIsDocument then u.clientSize().height else $viewport.outerHeight()
255
251
  originalScrollPos = $viewport.scrollTop()
256
252
  newScrollPos = originalScrollPos
@@ -259,7 +255,7 @@ up.layout = (($) ->
259
255
  obstruction = undefined
260
256
 
261
257
  if viewportIsDocument
262
- obstruction = measureObstruction()
258
+ obstruction = measureObstruction(viewportHeight)
263
259
  # Within the body, $.position will always return the distance
264
260
  # from the document top and *not* the distance of the viewport
265
261
  # top. This is what the calculations below expect, so don't shift.
@@ -274,11 +270,11 @@ up.layout = (($) ->
274
270
  offsetShift = originalScrollPos
275
271
 
276
272
  predictFirstVisibleRow = -> newScrollPos + obstruction.top
277
- predictLastVisibleRow = -> newScrollPos + viewportHeight - obstruction.bottom - 1
273
+ predictLastVisibleRow = -> newScrollPos + viewportHeight - obstruction.bottom
278
274
 
279
275
  elementDims = u.measure($element, relative: $viewport, includeMargin: true)
280
276
  firstElementRow = elementDims.top + offsetShift
281
- lastElementRow = firstElementRow + Math.min(elementDims.height, config.substance) - 1
277
+ lastElementRow = firstElementRow + Math.min(elementDims.height, config.substance)
282
278
 
283
279
  if lastElementRow > predictLastVisibleRow()
284
280
  # Try to show the full height of the element
@@ -297,32 +293,91 @@ up.layout = (($) ->
297
293
  Promise.resolve()
298
294
 
299
295
  ###**
300
- [Reveals](/up.reveal) an element matching the `#hash` in the current URL.
296
+ @function up.layout.scrollAfterInsertFragment
297
+ @param {boolean|object} [options.restoreScroll]
298
+ @param {boolean|string|jQuery|Element} [options.reveal]
299
+ @param {boolean|string} [options.reveal]
300
+ @return {Promise}
301
+ A promise that is fulfilled when the scrolling has finished.
302
+ @internal
303
+ ###
304
+ scrollAfterInsertFragment = (selectorOrElement, options) ->
305
+ options = u.options(options)
306
+ $element = $(selectorOrElement)
307
+
308
+ hashOpt = options.hash
309
+ revealOpt = options.reveal
310
+ restoreScrollOpt = options.restoreScroll
311
+
312
+ durationOptions = u.only(options, 'duration')
313
+
314
+ if restoreScrollOpt
315
+ # If options.restoreScroll is an object, its keys map viewport selectors
316
+ # to scroll positions. If it is just true, we leave the scrollTops option
317
+ # undefined and let restoreScroll() retrieve previous scrollTops from cache.
318
+ givenTops = u.presence(restoreScrollOpt, u.isObject)
319
+ return restoreScroll(around: $element, scrollTops: givenTops)
320
+
321
+ else if hashOpt && revealOpt == true # hash revealing can be disabled with { reveal: false }
322
+ return revealHash(hashOpt, durationOptions)
323
+
324
+ else if revealOpt
325
+ # We allow to pass another element as { reveal } option
326
+ if u.isElement(revealOpt) || u.isJQuery(revealOpt)
327
+ $element = $(revealOpt)
328
+
329
+ # We allow to pass a selector as { reveal } option
330
+ else if u.isString(revealOpt)
331
+ selector = up.dom.resolveSelector(revealOpt, options.origin)
332
+ $element = up.first(selector)
333
+
334
+ else
335
+ # We reveal the given $element
336
+
337
+ # If selectorOrElement was a CSS selector, don't blow up by calling reveal()
338
+ # with an empty jQuery collection. This might happen if a failed form submission
339
+ # reveals the first validation error message, but the error is shown in an
340
+ # unexpected element.
341
+ if $element.length
342
+ return reveal($element, durationOptions)
343
+
344
+ else
345
+ # If we didn't need to scroll above, just return a resolved promise
346
+ # to fulfill this function's signature.
347
+ return Promise.resolve()
348
+
349
+ ###**
350
+ [Reveals](/up.reveal) an element matching the given `#hash` anchor.
301
351
 
302
352
  Other than the default behavior found in browsers, `up.revealHash` works with
303
353
  [multiple viewports](/up-viewport) and honors [fixed elements](/up-fixed-top) obstructing the user's
304
354
  view of the viewport.
305
355
 
306
- This is called automatically when the page loads initially.
356
+ When the page loads initially, this function is automatically called with the hash from
357
+ the current URL.
358
+
359
+ If no element matches the given `#hash` anchor, a resolved promise is returned.
307
360
 
308
361
  @function up.layout.revealHash
309
362
  @return {Promise}
310
363
  A promise that is fulfilled when scroll position has changed to match the location hash.
311
364
  @experimental
312
365
  ###
313
- revealHash = ->
314
- if (hash = up.browser.hash()) && ($match = firstHashTarget(hash))
315
- reveal($match)
366
+ revealHash = (hash) ->
367
+ if (hash) && ($match = firstHashTarget(hash))
368
+ reveal($match, top: true)
316
369
  else
317
370
  Promise.resolve()
318
371
 
319
372
  viewportSelector = ->
320
- config.viewports.join(',')
373
+ # On Edge the document viewport can be changed from CSS
374
+ [up.browser.documentViewportSelector(), config.viewports...].join(',')
321
375
 
322
376
  ###**
323
377
  Returns the viewport for the given element.
324
378
 
325
- Returns `$(document)` if no better viewpoint could be found.
379
+ Returns the [document's scrolling element](https://developer.mozilla.org/en-US/docs/Web/API/Document/scrollingElement)
380
+ if no closer viewpoint exists.
326
381
 
327
382
  @function up.layout.viewportOf
328
383
  @param {string|Element|jQuery} selectorOrElement
@@ -331,10 +386,7 @@ up.layout = (($) ->
331
386
  ###
332
387
  viewportOf = (selectorOrElement, options = {}) ->
333
388
  $element = $(selectorOrElement)
334
- $viewport = $element.closest(viewportSelector())
335
- if $viewport.length == 0
336
- $viewport = $(document)
337
- $viewport
389
+ $element.closest(viewportSelector())
338
390
 
339
391
  ###**
340
392
  Returns a jQuery collection of all the viewports contained within the
@@ -356,14 +408,10 @@ up.layout = (($) ->
356
408
  @internal
357
409
  ###
358
410
  viewports = ->
359
- $(document).add(viewportSelector())
411
+ $(viewportSelector())
360
412
 
361
413
  scrollTopKey = (viewport) ->
362
- $viewport = $(viewport)
363
- if $viewport.is(document)
364
- 'document'
365
- else
366
- u.selectorForElement($viewport)
414
+ u.selectorForElement(viewport)
367
415
 
368
416
  ###**
369
417
  Returns a hash with scroll positions.
@@ -447,7 +495,7 @@ up.layout = (($) ->
447
495
  else
448
496
  $viewports = viewports()
449
497
 
450
- scrollTopsForUrl = lastScrollTops.get(url) || {}
498
+ scrollTopsForUrl = options.scrollTops || lastScrollTops.get(url) || {}
451
499
 
452
500
  up.log.group 'Restoring scroll positions for URL %s to %o', url, scrollTopsForUrl, ->
453
501
  allScrollPromises = u.map $viewports, (viewport) ->
@@ -457,51 +505,6 @@ up.layout = (($) ->
457
505
 
458
506
  Promise.all(allScrollPromises)
459
507
 
460
- ###**
461
- @function up.layout.revealOrRestoreScroll
462
- @param {boolean} [options.restoreScroll]
463
- @param {boolean|string} [options.reveal]
464
- @return {Promise}
465
- A promise that is fulfilled when the element is revealed or
466
- the scroll position is restored.
467
- @internal
468
- ###
469
- revealOrRestoreScroll = (selectorOrElement, options) ->
470
- options = u.options(options)
471
- $element = $(selectorOrElement)
472
-
473
- if options.restoreScroll
474
- return restoreScroll(around: $element)
475
-
476
- if options.reveal
477
- revealOptions = { duration: options.duration }
478
- if u.isString(options.reveal)
479
- selector = revealSelector(options.reveal, options)
480
- $element = up.first(selector) || $element
481
- revealOptions.top = true
482
-
483
- # If selectorOrElement was a CSS selector, don't blow up by calling reveal()
484
- # with an empty jQuery collection. This might happen if a failed form submission
485
- # reveals the first validation error message, but the error is shown in an
486
- # unexpected element.
487
- if $element.length
488
- return reveal($element, revealOptions)
489
-
490
- # If we didn't need to scroll above, just return a resolved promise
491
- # to fulfill this function's signature.
492
- return Promise.resolve()
493
-
494
- ###**
495
- @internal
496
- ###
497
- revealSelector = (selector, options) ->
498
- # up.dom.resolveSelector() will (1) substitute any "&" shorthands with an selector
499
- # for options.origin and (2) convert selector into a string, if it is an Element.
500
- selector = up.dom.resolveSelector(selector, options.origin)
501
- if selector[0] == '#'
502
- selector += ", a[name='#{selector}']"
503
- selector
504
-
505
508
  ###**
506
509
  @internal
507
510
  ###
@@ -686,10 +689,26 @@ up.layout = (($) ->
686
689
  @internal
687
690
  ###
688
691
  firstHashTarget = (hash) ->
689
- if hash = up.browser.hash(hash)
690
- up.first("[id='#{hash}'], a[name='#{hash}']")
692
+ if hash = pureHash(hash)
693
+ byID = u.attributeSelector('id', hash)
694
+ byName = 'a' + u.attributeSelector('name', hash)
695
+ up.first("#{byID},#{byName}")
696
+
697
+
698
+ ###**
699
+ Returns `'foo'` if the hash is `'#foo'`.
700
+
701
+ Returns undefined if the hash is `'#'`, `''` or `undefined`.
702
+
703
+ @function up.browser.hash
704
+ @internal
705
+ ###
706
+ pureHash = (value) ->
707
+ if value && value[0] == '#'
708
+ value = value.substr(1)
709
+ u.presence(value)
691
710
 
692
- up.on 'up:app:booted', revealHash
711
+ up.on 'up:app:booted', -> revealHash(location.hash)
693
712
 
694
713
  up.on 'up:framework:reset', reset
695
714
 
@@ -705,7 +724,7 @@ up.layout = (($) ->
705
724
  scrollTops: scrollTops
706
725
  saveScroll: saveScroll
707
726
  restoreScroll: restoreScroll
708
- revealOrRestoreScroll: revealOrRestoreScroll
727
+ scrollAfterInsertFragment: scrollAfterInsertFragment
709
728
  anchoredRight: anchoredRight
710
729
  fixedChildren: fixedChildren
711
730
  absolutize: absolutize