unpoly-rails 0.56.7 → 0.57.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 (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