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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +393 -1
- data/Gemfile.lock +5 -2
- data/README.md +1 -1
- data/README_RAILS.md +1 -1
- data/Rakefile +10 -1
- data/design/es6.js +32 -0
- data/design/ie11.txt +9 -0
- data/design/measure_jquery/element_list.js +41 -0
- data/design/measure_jquery/up.on_vs_addEventListener.js +56 -0
- data/design/todo_jquery.txt +13 -0
- data/dist/unpoly-bootstrap3.js +8 -8
- data/dist/unpoly-bootstrap3.min.js +1 -1
- data/dist/unpoly.css +22 -20
- data/dist/unpoly.js +6990 -5336
- data/dist/unpoly.min.css +1 -1
- data/dist/unpoly.min.js +4 -4
- data/lib/assets/javascripts/unpoly-bootstrap3/viewport-ext.coffee +5 -0
- data/lib/assets/javascripts/unpoly.coffee +8 -6
- data/lib/assets/javascripts/unpoly/browser.coffee.erb +23 -118
- data/lib/assets/javascripts/unpoly/classes/body_shifter.coffee +36 -0
- data/lib/assets/javascripts/unpoly/classes/cache.coffee +4 -4
- data/lib/assets/javascripts/unpoly/classes/compile_pass.coffee +45 -39
- data/lib/assets/javascripts/unpoly/classes/config.coffee +9 -0
- data/lib/assets/javascripts/unpoly/classes/css_transition.coffee +18 -27
- data/lib/assets/javascripts/unpoly/classes/divertible_chain.coffee +39 -0
- data/lib/assets/javascripts/unpoly/classes/event_listener.coffee +116 -0
- data/lib/assets/javascripts/unpoly/classes/extract_cascade.coffee +8 -8
- data/lib/assets/javascripts/unpoly/classes/extract_plan.coffee +19 -19
- data/lib/assets/javascripts/unpoly/classes/field_observer.coffee +54 -31
- data/lib/assets/javascripts/unpoly/classes/{focus_tracker.coffee → focus_follower.coffee} +2 -2
- data/lib/assets/javascripts/unpoly/classes/follow_variant.coffee +25 -25
- data/lib/assets/javascripts/unpoly/classes/html_parser.coffee +4 -11
- data/lib/assets/javascripts/unpoly/classes/motion_controller.coffee +157 -0
- data/lib/assets/javascripts/unpoly/classes/params.coffee.erb +525 -0
- data/lib/assets/javascripts/unpoly/classes/record.coffee +8 -2
- data/lib/assets/javascripts/unpoly/classes/rect.js +21 -0
- data/lib/assets/javascripts/unpoly/classes/request.coffee +41 -35
- data/lib/assets/javascripts/unpoly/classes/response.coffee +7 -3
- data/lib/assets/javascripts/unpoly/classes/reveal_motion.coffee +102 -0
- data/lib/assets/javascripts/unpoly/classes/scroll_motion.coffee +67 -0
- data/lib/assets/javascripts/unpoly/classes/selector.coffee +60 -0
- data/lib/assets/javascripts/unpoly/classes/tether.coffee +105 -0
- data/lib/assets/javascripts/unpoly/classes/url_set.coffee +12 -7
- data/lib/assets/javascripts/unpoly/element.coffee.erb +1126 -0
- data/lib/assets/javascripts/unpoly/event.coffee.erb +437 -0
- data/lib/assets/javascripts/unpoly/feedback.coffee +73 -94
- data/lib/assets/javascripts/unpoly/form.coffee.erb +188 -181
- data/lib/assets/javascripts/unpoly/{dom.coffee.erb → fragment.coffee.erb} +250 -283
- data/lib/assets/javascripts/unpoly/framework.coffee +67 -0
- data/lib/assets/javascripts/unpoly/history.coffee +29 -28
- data/lib/assets/javascripts/unpoly/legacy.coffee +60 -0
- data/lib/assets/javascripts/unpoly/link.coffee.erb +127 -119
- data/lib/assets/javascripts/unpoly/log.coffee +99 -19
- data/lib/assets/javascripts/unpoly/modal.coffee.erb +95 -118
- data/lib/assets/javascripts/unpoly/motion.coffee.erb +158 -138
- data/lib/assets/javascripts/unpoly/namespace.coffee.erb +0 -5
- data/lib/assets/javascripts/unpoly/popup.coffee.erb +119 -102
- data/lib/assets/javascripts/unpoly/protocol.coffee +11 -15
- data/lib/assets/javascripts/unpoly/proxy.coffee +62 -65
- data/lib/assets/javascripts/unpoly/radio.coffee +3 -5
- data/lib/assets/javascripts/unpoly/rails.coffee +8 -9
- data/lib/assets/javascripts/unpoly/syntax.coffee.erb +173 -125
- data/lib/assets/javascripts/unpoly/toast.coffee +25 -24
- data/lib/assets/javascripts/unpoly/tooltip.coffee +89 -79
- data/lib/assets/javascripts/unpoly/util.coffee.erb +579 -1074
- data/lib/assets/javascripts/unpoly/{layout.coffee.erb → viewport.coffee.erb} +334 -264
- data/lib/assets/stylesheets/unpoly/dom.sass +1 -1
- data/lib/assets/stylesheets/unpoly/layout.sass +2 -0
- data/lib/assets/stylesheets/unpoly/popup.sass +0 -1
- data/lib/assets/stylesheets/unpoly/tooltip.sass +17 -12
- data/lib/unpoly/rails/version.rb +1 -1
- data/package.json +1 -2
- data/spec_app/Gemfile +2 -1
- data/spec_app/Gemfile.lock +38 -27
- data/spec_app/app/assets/javascripts/integration_test.coffee +1 -0
- data/spec_app/app/assets/javascripts/jasmine_specs.coffee +1 -2
- data/spec_app/app/assets/stylesheets/integration_test.sass +14 -1
- data/spec_app/app/controllers/scroll_test_controller.rb +5 -0
- data/spec_app/app/views/css_test/modal.erb +6 -6
- data/spec_app/app/views/css_test/popup.erb +44 -18
- data/spec_app/app/views/css_test/tooltip.erb +23 -4
- data/spec_app/app/views/error_test/trigger.erb +1 -1
- data/spec_app/app/views/form_test/basics/new.erb +1 -3
- data/spec_app/app/views/pages/start.erb +9 -2
- data/spec_app/app/views/reveal_test/long1.erb +1 -1
- data/spec_app/app/views/reveal_test/long2.erb +1 -1
- data/spec_app/app/views/reveal_test/within_document_viewport.erb +24 -0
- data/spec_app/app/views/reveal_test/within_overflowing_div_viewport.erb +28 -0
- data/spec_app/app/views/scroll_test/long1.erb +30 -0
- data/spec_app/config/routes.rb +1 -0
- data/spec_app/spec/javascripts/helpers/agent_detector.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/async_sequence.js.coffee +1 -0
- data/spec_app/spec/javascripts/helpers/browser_switches.js.coffee +17 -5
- data/spec_app/spec/javascripts/helpers/enable_logging.js.coffee +1 -1
- data/spec_app/spec/javascripts/helpers/fixture.js.coffee +25 -0
- data/spec_app/spec/javascripts/helpers/jquery_no_conflict.js +1 -0
- data/spec_app/spec/javascripts/helpers/last_request.js.coffee +1 -0
- data/spec_app/spec/javascripts/helpers/mock_ajax.js.coffee +1 -1
- data/spec_app/spec/javascripts/helpers/parse_form_data.js.coffee +2 -2
- data/spec_app/spec/javascripts/helpers/protect_jasmine_runner.coffee +4 -1
- data/spec_app/spec/javascripts/helpers/remove_body_margin.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/reset_history.js.coffee +2 -1
- data/spec_app/spec/javascripts/helpers/reset_knife.js.coffee +2 -2
- data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +18 -11
- data/spec_app/spec/javascripts/helpers/restore_body_scroll.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/show_lib_versions.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/spec_util.coffee +47 -0
- data/spec_app/spec/javascripts/helpers/to_be_around.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_array.coffee +5 -0
- data/spec_app/spec/javascripts/helpers/to_be_attached.coffee +6 -2
- data/spec_app/spec/javascripts/helpers/to_be_blank.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_detached.coffee +6 -2
- data/spec_app/spec/javascripts/helpers/to_be_element.js.coffee +8 -0
- data/spec_app/spec/javascripts/helpers/to_be_error.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_given.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_hidden.js.coffee +8 -0
- data/spec_app/spec/javascripts/helpers/to_be_missing.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_present.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_scrolled_to.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_be_visible.js.coffee +9 -0
- data/spec_app/spec/javascripts/helpers/to_contain.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_end_with.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_equal_jquery.js.coffee +1 -2
- data/spec_app/spec/javascripts/helpers/to_equal_node_list.coffee +7 -0
- data/spec_app/spec/javascripts/helpers/to_equal_via_is_equal.js.coffee +7 -0
- data/spec_app/spec/javascripts/helpers/to_have_class.js.coffee +10 -0
- data/spec_app/spec/javascripts/helpers/to_have_descendant.js.coffee +10 -0
- data/spec_app/spec/javascripts/helpers/to_have_length.js.coffee +8 -0
- data/spec_app/spec/javascripts/helpers/to_have_opacity.coffee +7 -3
- data/spec_app/spec/javascripts/helpers/to_have_own_property.js.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_have_request_method.js.coffee +1 -0
- data/spec_app/spec/javascripts/helpers/to_have_text.js.coffee +9 -0
- data/spec_app/spec/javascripts/helpers/to_have_unhandled_rejections.coffee +0 -21
- data/spec_app/spec/javascripts/helpers/to_match_list.coffee +14 -0
- data/spec_app/spec/javascripts/helpers/to_match_selector.coffee +3 -0
- data/spec_app/spec/javascripts/helpers/to_match_text.js.coffee +4 -1
- data/spec_app/spec/javascripts/helpers/to_match_url.coffee +1 -0
- data/spec_app/spec/javascripts/helpers/trigger.js.coffee +91 -7
- data/spec_app/spec/javascripts/helpers/wait_until_dom_ready.js.coffee +3 -0
- data/spec_app/spec/javascripts/up/browser_spec.js.coffee +23 -90
- data/spec_app/spec/javascripts/up/classes/cache_spec.js.coffee +3 -0
- data/spec_app/spec/javascripts/up/classes/config_spec.coffee +24 -0
- data/spec_app/spec/javascripts/up/classes/divertible_chain_spec.coffee +45 -0
- data/spec_app/spec/javascripts/up/classes/focus_tracker_spec.coffee +5 -2
- data/spec_app/spec/javascripts/up/classes/params_spec.coffee +557 -0
- data/spec_app/spec/javascripts/up/classes/request_spec.coffee +7 -4
- data/spec_app/spec/javascripts/up/classes/scroll_motion_spec.js.coffee +51 -0
- data/spec_app/spec/javascripts/up/classes/store/memory_spec.js.coffee +3 -0
- data/spec_app/spec/javascripts/up/classes/store/session_spec.js.coffee +3 -2
- data/spec_app/spec/javascripts/up/element_spec.coffee +897 -0
- data/spec_app/spec/javascripts/up/event_spec.js.coffee +496 -0
- data/spec_app/spec/javascripts/up/feedback_spec.js.coffee +69 -48
- data/spec_app/spec/javascripts/up/form_spec.js.coffee +252 -194
- data/spec_app/spec/javascripts/up/{dom_spec.js.coffee → fragment_spec.js.coffee} +381 -388
- data/spec_app/spec/javascripts/up/history_spec.js.coffee +21 -19
- data/spec_app/spec/javascripts/up/jquery_spec.js.coffee +4 -0
- data/spec_app/spec/javascripts/up/legacy_spec.js.coffee +27 -0
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +163 -160
- data/spec_app/spec/javascripts/up/log_spec.js.coffee +85 -12
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +141 -123
- data/spec_app/spec/javascripts/up/motion_spec.js.coffee +117 -113
- data/spec_app/spec/javascripts/up/popup_spec.js.coffee +60 -77
- data/spec_app/spec/javascripts/up/protocol_spec.js.coffee +1 -0
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +85 -78
- data/spec_app/spec/javascripts/up/radio_spec.js.coffee +29 -22
- data/spec_app/spec/javascripts/up/rails_spec.js.coffee +14 -13
- data/spec_app/spec/javascripts/up/spec_spec.js.coffee +9 -0
- data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +96 -66
- data/spec_app/spec/javascripts/up/toast_spec.js.coffee +37 -0
- data/spec_app/spec/javascripts/up/tooltip_spec.js.coffee +31 -47
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +725 -562
- data/spec_app/spec/javascripts/up/{layout_spec.js.coffee → viewport_spec.js.coffee} +175 -149
- metadata +57 -19
- data/lib/assets/javascripts/unpoly-bootstrap3/layout-ext.coffee +0 -5
- data/lib/assets/javascripts/unpoly/bus.coffee.erb +0 -518
- data/lib/assets/javascripts/unpoly/classes/extract_step.coffee +0 -4
- data/lib/assets/javascripts/unpoly/classes/motion_tracker.coffee +0 -125
- data/lib/assets/javascripts/unpoly/params.coffee.erb +0 -522
- data/spec_app/spec/javascripts/helpers/append_fixture.js.coffee +0 -8
- data/spec_app/spec/javascripts/up/bus_spec.js.coffee +0 -210
- data/spec_app/spec/javascripts/up/namespace_spec.js.coffee +0 -9
- data/spec_app/spec/javascripts/up/params_spec.coffee +0 -768
- data/spec_app/vendor/asset-libs/jasmine-fixture-1.3.4/jasmine-fixture.js +0 -433
- data/spec_app/vendor/asset-libs/jasmine-jquery-2.1.1/.bower.json +0 -26
- data/spec_app/vendor/asset-libs/jasmine-jquery-2.1.1/jasmine-jquery.js +0 -838
@@ -0,0 +1,437 @@
|
|
1
|
+
###**
|
2
|
+
Events
|
3
|
+
======
|
4
|
+
|
5
|
+
Most Unpoly interactions emit DOM events that are prefixed with `up:`.
|
6
|
+
|
7
|
+
document.addEventListener('up:modal:opened', (event) => {
|
8
|
+
console.log('A new modal has just opened!')
|
9
|
+
})
|
10
|
+
|
11
|
+
Events often have both present and past forms. For example,
|
12
|
+
`up:modal:open` is emitted before a modal starts to open.
|
13
|
+
`up:modal:opened` is emitted when the modal has finished its
|
14
|
+
opening animation.
|
15
|
+
|
16
|
+
\#\#\# Preventing events
|
17
|
+
|
18
|
+
You can prevent most present form events by calling `preventDefault()`:
|
19
|
+
|
20
|
+
document.addEventListener('up:modal:open', (event) => {
|
21
|
+
if (event.url == '/evil') {
|
22
|
+
// Prevent the modal from opening
|
23
|
+
event.preventDefault()
|
24
|
+
}
|
25
|
+
})
|
26
|
+
|
27
|
+
|
28
|
+
\#\#\# A better way to bind event listeners
|
29
|
+
|
30
|
+
Instead of using [`Element#addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener),
|
31
|
+
you may find it convenient to use [`up.on()`](/up.on) instead:
|
32
|
+
|
33
|
+
up.on('click', 'button', function(event, button, data) {
|
34
|
+
// button is the clicked element
|
35
|
+
// data is the parsed [`up-data`](/up-data) attribute
|
36
|
+
})
|
37
|
+
|
38
|
+
There are some advantages to using `up.on()`:
|
39
|
+
|
40
|
+
- You may pass a selector for [event delegation](https://davidwalsh.name/event-delegate).
|
41
|
+
- The event target is automatically passed as a second argument.
|
42
|
+
- You may register a listener to multiple events by passing a space-separated list of event name (e.g. `"click mousedown"`)
|
43
|
+
- You may register a listener to multiple elements in a single `up.on()` call, by passing a [list](/up.util.isList) of elements.
|
44
|
+
- You use an [`[up-data]`](/up-data) attribute to [attach structured data](/up.on#attaching-structured-data)
|
45
|
+
to observed elements. If an `[up-data]` attribute is set, its value will automatically be
|
46
|
+
parsed as JSON and passed as a third argument.
|
47
|
+
- Event listeners on [unsupported browsers](/up.browser.isSupported) are silently discarded,
|
48
|
+
leaving you with an application without JavaScript. This is typically preferable to
|
49
|
+
a soup of randomly broken JavaScript in ancient browsers.
|
50
|
+
|
51
|
+
@module up.event
|
52
|
+
###
|
53
|
+
up.event = do ->
|
54
|
+
|
55
|
+
u = up.util
|
56
|
+
e = up.element
|
57
|
+
|
58
|
+
reset = ->
|
59
|
+
# Resets the list of registered event listeners to the
|
60
|
+
# moment when the framework was booted.
|
61
|
+
for element in [window, document, document.documentElement, document.body]
|
62
|
+
up.EventListener.unbindNonDefault(element)
|
63
|
+
|
64
|
+
###**
|
65
|
+
Listens to a [DOM event](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Events)
|
66
|
+
on `document` or a given element.
|
67
|
+
|
68
|
+
`up.on()` has some quality of life improvements over
|
69
|
+
[`Element#addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener):
|
70
|
+
|
71
|
+
- You may pass a selector for [event delegation](https://davidwalsh.name/event-delegate).
|
72
|
+
- The event target is automatically passed as a second argument.
|
73
|
+
- You may register a listener to multiple events by passing a space-separated list of event name (e.g. `"click mousedown"`)
|
74
|
+
- You may register a listener to multiple elements in a single `up.on()` call, by passing a [list](/up.util.isList) of elements.
|
75
|
+
- You use an [`[up-data]`](/up-data) attribute to [attach structured data](/up.on#attaching-structured-data)
|
76
|
+
to observed elements. If an `[up-data]` attribute is set, its value will automatically be
|
77
|
+
parsed as JSON and passed as a third argument.
|
78
|
+
- Event listeners on [unsupported browsers](/up.browser.isSupported) are silently discarded,
|
79
|
+
leaving you with an application without JavaScript. This is typically preferable to
|
80
|
+
a soup of randomly broken JavaScript in ancient browsers.
|
81
|
+
|
82
|
+
\#\#\# Examples
|
83
|
+
|
84
|
+
The code below will call the listener when a `<a>` is clicked
|
85
|
+
anywhere in the `document`:
|
86
|
+
|
87
|
+
up.on('click', 'a', function(event, element) {
|
88
|
+
console.log("Click on a link %o", element)
|
89
|
+
})
|
90
|
+
|
91
|
+
You may also bind the listener to a given element instead of `document`:
|
92
|
+
|
93
|
+
var form = document.querySelector('form')
|
94
|
+
up.on(form, 'click', function(event, form) {
|
95
|
+
console.log("Click within %o", form)
|
96
|
+
})
|
97
|
+
|
98
|
+
You may also pass both an element and a selector
|
99
|
+
for [event delegation](https://davidwalsh.name/event-delegate):
|
100
|
+
|
101
|
+
var form = document.querySelector('form')
|
102
|
+
document.addEventListener(form, 'click', 'a', function(event, link) {
|
103
|
+
console.log("Click on a link %o within %o", link, form)
|
104
|
+
})
|
105
|
+
|
106
|
+
\#\#\# Attaching structured data
|
107
|
+
|
108
|
+
In case you want to attach structured data to the event you're observing,
|
109
|
+
you can serialize the data to JSON and put it into an `[up-data]` attribute:
|
110
|
+
|
111
|
+
<span class='person' up-data='{ "age": 18, "name": "Bob" }'>Bob</span>
|
112
|
+
<span class='person' up-data='{ "age": 22, "name": "Jim" }'>Jim</span>
|
113
|
+
|
114
|
+
The JSON will parsed and handed to your event handler as a third argument:
|
115
|
+
|
116
|
+
up.on('click', '.person', function(event, element, data) {
|
117
|
+
console.log("This is %o who is %o years old", data.name, data.age)
|
118
|
+
})
|
119
|
+
|
120
|
+
\#\#\# Unbinding an event listener
|
121
|
+
|
122
|
+
`up.on()` returns a function that unbinds the event listeners when called:
|
123
|
+
|
124
|
+
// Define the listener
|
125
|
+
var listener = function(event) { ... }
|
126
|
+
|
127
|
+
// Binding the listener returns an unbind function
|
128
|
+
var unbind = up.on('click', listener)
|
129
|
+
|
130
|
+
// Unbind the listener
|
131
|
+
unbind()
|
132
|
+
|
133
|
+
There is also a function [`up.off()`](/up.off) which you can use for the same purpose:
|
134
|
+
|
135
|
+
// Define the listener
|
136
|
+
var listener = function(event) { ... }
|
137
|
+
|
138
|
+
// Bind the listener
|
139
|
+
up.on('click', listener)
|
140
|
+
|
141
|
+
// Unbind the listener
|
142
|
+
up.off('click', listener)
|
143
|
+
|
144
|
+
@function up.on
|
145
|
+
@param {Element|jQuery} [element=document]
|
146
|
+
The element on which to register the event listener.
|
147
|
+
|
148
|
+
If no element is given, the listener is registered on the `document`.
|
149
|
+
@param {string} events
|
150
|
+
A space-separated list of event names to bind to.
|
151
|
+
@param {string} [selector]
|
152
|
+
The selector of an element on which the event must be triggered.
|
153
|
+
Omit the selector to listen to all events with that name, regardless
|
154
|
+
of the event target.
|
155
|
+
@param {Function(event, [element], [data])} listener
|
156
|
+
The listener function that should be called.
|
157
|
+
|
158
|
+
The function takes the affected element as the first argument).
|
159
|
+
If the element has an [`up-data`](/up-data) attribute, its value is parsed as JSON
|
160
|
+
and passed as a second argument.
|
161
|
+
@return {Function()}
|
162
|
+
A function that unbinds the event listeners when called.
|
163
|
+
@stable
|
164
|
+
###
|
165
|
+
bind = (args...) ->
|
166
|
+
bindNow(args)
|
167
|
+
|
168
|
+
###**
|
169
|
+
Listens to an event on `document` or a given element.
|
170
|
+
The event handler is called with the event target as a
|
171
|
+
[jQuery collection](https://learn.jquery.com/using-jquery-core/jquery-object/).
|
172
|
+
|
173
|
+
If you're not using jQuery, use `up.on()` instead, which calls
|
174
|
+
event handlers with a native element.
|
175
|
+
|
176
|
+
\#\#\# Example
|
177
|
+
|
178
|
+
```
|
179
|
+
up.$on('click', 'a', function(event, $link) {
|
180
|
+
console.log("Click on a link with destination %s", $element.attr('href'))
|
181
|
+
})
|
182
|
+
```
|
183
|
+
|
184
|
+
@function up.$on
|
185
|
+
@param {Element|jQuery} [element=document]
|
186
|
+
The element on which to register the event listener.
|
187
|
+
|
188
|
+
If no element is given, the listener is registered on the `document`.
|
189
|
+
@param {string} events
|
190
|
+
A space-separated list of event names to bind to.
|
191
|
+
@param {string} [selector]
|
192
|
+
The selector of an element on which the event must be triggered.
|
193
|
+
Omit the selector to listen to all events with that name, regardless
|
194
|
+
of the event target.
|
195
|
+
@param {Function(event, [element], [data])} listener
|
196
|
+
The listener function that should be called.
|
197
|
+
|
198
|
+
The function takes the affected element as the first argument).
|
199
|
+
If the element has an [`up-data`](/up-data) attribute, its value is parsed as JSON
|
200
|
+
and passed as a second argument.
|
201
|
+
@return {Function()}
|
202
|
+
A function that unbinds the event listeners when called.
|
203
|
+
@stable
|
204
|
+
###
|
205
|
+
$bind = (args...) ->
|
206
|
+
bindNow(args, jQuery: true)
|
207
|
+
|
208
|
+
bindNow = (args, options) ->
|
209
|
+
# Silently discard any event handlers that are registered on unsupported
|
210
|
+
# browsers and return a no-op destructor
|
211
|
+
return (->) unless up.browser.isSupported()
|
212
|
+
|
213
|
+
up.EventListener.bind(args, options)
|
214
|
+
|
215
|
+
###**
|
216
|
+
Unbinds an event listener previously bound with [`up.on()`](/up.on).
|
217
|
+
|
218
|
+
\#\#\# Example
|
219
|
+
|
220
|
+
Let's say you are listing to clicks on `.button` elements:
|
221
|
+
|
222
|
+
var listener = function() { ... }
|
223
|
+
up.on('click', '.button', listener)
|
224
|
+
|
225
|
+
You can stop listening to these events like this:
|
226
|
+
|
227
|
+
up.off('click', '.button', listener)
|
228
|
+
|
229
|
+
Note that you need to pass `up.off()` a reference to the same listener function
|
230
|
+
that was passed to `up.on()` earlier.
|
231
|
+
|
232
|
+
@function up.off
|
233
|
+
@stable
|
234
|
+
###
|
235
|
+
unbind = (args...) ->
|
236
|
+
up.EventListener.unbind(args)
|
237
|
+
|
238
|
+
###**
|
239
|
+
Emits a event with the given name and properties.
|
240
|
+
|
241
|
+
The event will be triggered as an event on `document` or on the given element.
|
242
|
+
|
243
|
+
Other code can subscribe to events with that name using
|
244
|
+
[`Element#addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)
|
245
|
+
or [`up.on()`](/up.on).
|
246
|
+
|
247
|
+
\#\#\# Example
|
248
|
+
|
249
|
+
up.on('my:event', function(event) {
|
250
|
+
console.log(event.foo)
|
251
|
+
})
|
252
|
+
|
253
|
+
up.emit('my:event', { foo: 'bar' })
|
254
|
+
// Prints "bar" to the console
|
255
|
+
|
256
|
+
@function up.emit
|
257
|
+
@param {Element|jQuery} [target=document]
|
258
|
+
The element on which the event is triggered.
|
259
|
+
|
260
|
+
If omitted, the event will be emitted on the `document`.
|
261
|
+
@param {string} eventName
|
262
|
+
The name of the event.
|
263
|
+
@param {Object} [eventProps={}]
|
264
|
+
A list of properties to become part of the event object
|
265
|
+
that will be passed to listeners. Note that the event object
|
266
|
+
will by default include properties like `preventDefault()`
|
267
|
+
or `stopPropagation()`.
|
268
|
+
@param {string|Array} [eventProps.log=false]
|
269
|
+
A message to print to the console when the event is emitted.
|
270
|
+
|
271
|
+
Pass `true` to print a default message
|
272
|
+
@param {Element|jQuery} [eventProps.target=document]
|
273
|
+
The element on which the event is triggered.
|
274
|
+
@stable
|
275
|
+
###
|
276
|
+
emit = (args...) ->
|
277
|
+
if args[0].addEventListener
|
278
|
+
target = args.shift()
|
279
|
+
else if u.isJQuery(args[0])
|
280
|
+
target = e.get(args.shift())
|
281
|
+
|
282
|
+
eventName = args[0]
|
283
|
+
eventProps = args[1] || {}
|
284
|
+
|
285
|
+
if targetFromProps = u.pluckKey(eventProps, 'target')
|
286
|
+
target = targetFromProps
|
287
|
+
target ?= document
|
288
|
+
|
289
|
+
logEmission(eventName, eventProps)
|
290
|
+
|
291
|
+
event = buildEvent(eventName, eventProps)
|
292
|
+
target.dispatchEvent(event)
|
293
|
+
|
294
|
+
return event
|
295
|
+
|
296
|
+
buildEvent = (name, props) ->
|
297
|
+
event = document.createEvent('Event')
|
298
|
+
event.initEvent(name, true, true) # name, bubbles, cancelable
|
299
|
+
u.assign(event, props)
|
300
|
+
|
301
|
+
# IE11 does not set { defaultPrevented: true } after #preventDefault()
|
302
|
+
# was called on a custom event.
|
303
|
+
# See discussion here: https://stackoverflow.com/questions/23349191
|
304
|
+
if up.browser.isIE11()
|
305
|
+
event.preventDefault = ->
|
306
|
+
Object.defineProperty(event, 'defaultPrevented', get: -> true)
|
307
|
+
|
308
|
+
event
|
309
|
+
|
310
|
+
logEmission = (eventName, eventProps) ->
|
311
|
+
return unless up.log.isEnabled()
|
312
|
+
|
313
|
+
message = u.pluckKey(eventProps, 'log')
|
314
|
+
|
315
|
+
if u.isArray(message)
|
316
|
+
[message, messageArgs...] = message
|
317
|
+
else
|
318
|
+
messageArgs = []
|
319
|
+
|
320
|
+
if u.isString(message)
|
321
|
+
if u.isPresent(eventProps)
|
322
|
+
up.puts "#{message} (%s (%o))", messageArgs..., eventName, eventProps
|
323
|
+
else
|
324
|
+
up.puts "#{message} (%s)", messageArgs..., eventName
|
325
|
+
else if message == true
|
326
|
+
if u.isPresent(eventProps)
|
327
|
+
up.puts 'Event %s (%o)', eventName, eventProps
|
328
|
+
else
|
329
|
+
up.puts 'Event %s', eventName
|
330
|
+
|
331
|
+
###**
|
332
|
+
[Emits an event](/up.emit) and returns whether no listener
|
333
|
+
has prevented the default action.
|
334
|
+
|
335
|
+
@function up.event.nobodyPrevents
|
336
|
+
@param {string} eventName
|
337
|
+
@param {Object} eventProps
|
338
|
+
@param {string|Array} [eventProps.log]
|
339
|
+
@return {boolean}
|
340
|
+
whether no listener has prevented the default action
|
341
|
+
@experimental
|
342
|
+
###
|
343
|
+
nobodyPrevents = (args...) ->
|
344
|
+
event = emit(args...)
|
345
|
+
not event.defaultPrevented
|
346
|
+
|
347
|
+
###**
|
348
|
+
[Emits](/up.emit) the given event and returns a promise
|
349
|
+
that will be fulfilled if no listener has prevented the default action.
|
350
|
+
|
351
|
+
If any listener prevented the default listener
|
352
|
+
the returned promise will never be resolved.
|
353
|
+
|
354
|
+
@function up.event.whenEmitted
|
355
|
+
@param {string} eventName
|
356
|
+
@param {Object} eventProps
|
357
|
+
@param {string|Array} [eventProps.message]
|
358
|
+
@return {Promise}
|
359
|
+
@internal
|
360
|
+
###
|
361
|
+
whenEmitted = (args...) ->
|
362
|
+
new Promise (resolve, reject) ->
|
363
|
+
if nobodyPrevents(args...)
|
364
|
+
resolve()
|
365
|
+
else
|
366
|
+
reject(new Error("Event #{args[0]} was prevented"))
|
367
|
+
|
368
|
+
###**
|
369
|
+
Registers an event listener to be called when the user
|
370
|
+
presses the `Escape` key.
|
371
|
+
|
372
|
+
@function up.event.onEscape
|
373
|
+
@param {Function(event)} listener
|
374
|
+
The listener function to register.
|
375
|
+
@return {Function()}
|
376
|
+
A function that unbinds the event listeners when called.
|
377
|
+
@experimental
|
378
|
+
###
|
379
|
+
onEscape = (listener) ->
|
380
|
+
bind('keydown', 'body', (event) ->
|
381
|
+
if u.escapePressed(event)
|
382
|
+
listener(event)
|
383
|
+
)
|
384
|
+
|
385
|
+
###**
|
386
|
+
Stops the given event from propagating and prevents the default action.
|
387
|
+
|
388
|
+
@function up.event.halt
|
389
|
+
@internal
|
390
|
+
###
|
391
|
+
halt = (event) ->
|
392
|
+
event.stopImmediatePropagation()
|
393
|
+
event.stopPropagation()
|
394
|
+
event.preventDefault()
|
395
|
+
|
396
|
+
###**
|
397
|
+
@function up.event.consumeAction
|
398
|
+
@internal
|
399
|
+
###
|
400
|
+
consumeAction = (event) ->
|
401
|
+
# Halt the event chain to stop duplicate processing of this user interaction.
|
402
|
+
halt(event)
|
403
|
+
unless event.type == 'up:action:consumed'
|
404
|
+
# Although we have consumed this action and halted the event chain,
|
405
|
+
# other components might still need to react. E.g. a popup needs to close when
|
406
|
+
# an outside link consumes the user click. So we emit another event for that.
|
407
|
+
emit(event.target, 'up:action:consumed', log: false)
|
408
|
+
|
409
|
+
onReady = (callback) ->
|
410
|
+
# Values are "loading", "interactive" and "completed".
|
411
|
+
# https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState
|
412
|
+
if document.readyState != 'loading'
|
413
|
+
callback()
|
414
|
+
else
|
415
|
+
document.addEventListener('DOMContentLoaded', callback)
|
416
|
+
|
417
|
+
bind 'up:framework:reset', reset
|
418
|
+
|
419
|
+
<% if ENV['JS_KNIFE'] %>knife: eval(Knife.point)<% end %>
|
420
|
+
on: bind # can't name symbols `on` in Coffeescript
|
421
|
+
$on: $bind
|
422
|
+
off: unbind # can't name symbols `off` in Coffeescript
|
423
|
+
emit: emit
|
424
|
+
nobodyPrevents: nobodyPrevents
|
425
|
+
whenEmitted: whenEmitted
|
426
|
+
onEscape: onEscape
|
427
|
+
halt: halt
|
428
|
+
consumeAction: consumeAction
|
429
|
+
onReady: onReady
|
430
|
+
|
431
|
+
up.on = up.event.on
|
432
|
+
up.$on = up.event.$on
|
433
|
+
up.off = up.event.off
|
434
|
+
up.$off = up.event.off # it's the same as up.off()
|
435
|
+
up.emit = up.event.emit
|
436
|
+
|
437
|
+
up.legacy.renamedModule 'bus', 'event'
|
@@ -2,43 +2,49 @@
|
|
2
2
|
Navigation feedback
|
3
3
|
===================
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
By marking navigation elements as [`[up-nav]`](/up-nav), contained links that point to the current location
|
8
|
-
automatically get the [`.up-current`](/up-nav-a.up-current) class.
|
9
|
-
|
10
|
-
You should style [`.up-active`](/a.up-active) and [`.up-current`](/up-nav a.up-current) with CSS to
|
5
|
+
The `up.feedback` module adds useful CSS classes to links while they are loading,
|
6
|
+
or when they point to the current URL. By styling these classes you may
|
11
7
|
provide instant feedback to user interactions. This improves the perceived speed of your interface.
|
12
8
|
|
9
|
+
|
13
10
|
\#\#\# Example
|
14
11
|
|
15
12
|
Let's say we have an navigation bar with two links, pointing to `/foo` and `/bar` respectively:
|
16
13
|
|
17
|
-
<
|
18
|
-
|
14
|
+
<div up-nav>
|
15
|
+
<a href="/foo" up-follow>Foo</a>
|
16
|
+
<a href="/bar" up-follow>Bar</a>
|
17
|
+
</div>
|
19
18
|
|
20
|
-
If the current URL is `/foo`, the first link is automatically marked with an [
|
19
|
+
If the current URL is `/foo`, the first link is automatically marked with an [`.up-current`](/a.up-current) class:
|
21
20
|
|
22
|
-
<
|
23
|
-
|
21
|
+
<div up-nav>
|
22
|
+
<a href="/foo" up-follow class="up-current">Foo</a>
|
23
|
+
<a href="/bar" up-follow>Bar</a>
|
24
|
+
</div>
|
24
25
|
|
25
26
|
When the user clicks on the `/bar` link, the link will receive the [`up-active`](/a.up-active) class while it is waiting
|
26
27
|
for the server to respond:
|
27
28
|
|
28
|
-
<
|
29
|
-
|
29
|
+
<div up-nav>
|
30
|
+
<a href="/foo" up-follow class="up-current">Foo</a>
|
31
|
+
<a href="/bar" up-follow class="up-active">Bar</a>
|
32
|
+
</div>
|
30
33
|
|
31
34
|
Once the response is received the URL will change to `/bar` and the `up-active` class is removed:
|
32
35
|
|
33
|
-
<
|
34
|
-
|
36
|
+
<div up-nav>
|
37
|
+
<a href="/foo" up-follow>Foo</a>
|
38
|
+
<a href="/bar" up-follow class="up-current">Bar</a>
|
39
|
+
</div>
|
35
40
|
|
36
41
|
|
37
|
-
@
|
42
|
+
@module up.feedback
|
38
43
|
###
|
39
|
-
up.feedback =
|
44
|
+
up.feedback = do ->
|
40
45
|
|
41
46
|
u = up.util
|
47
|
+
e = up.element
|
42
48
|
|
43
49
|
###**
|
44
50
|
Sets default options for this module.
|
@@ -50,7 +56,7 @@ up.feedback = (($) ->
|
|
50
56
|
An array of CSS selectors that match [navigation components](/up-nav).
|
51
57
|
@stable
|
52
58
|
###
|
53
|
-
config =
|
59
|
+
config = new up.Config
|
54
60
|
currentClasses: ['up-current']
|
55
61
|
navs: ['[up-nav]']
|
56
62
|
|
@@ -72,25 +78,23 @@ up.feedback = (($) ->
|
|
72
78
|
if u.isPresent(url)
|
73
79
|
u.normalizeUrl(url, stripTrailingSlash: true)
|
74
80
|
|
75
|
-
|
76
|
-
|
77
|
-
sectionUrls = ($section) ->
|
81
|
+
sectionUrls = (section) ->
|
78
82
|
# Check if we have computed the URLs before.
|
79
83
|
# Computation is sort of expensive (multiplied by number of links),
|
80
84
|
# so we cache the results in a data attribute.
|
81
|
-
unless urls =
|
82
|
-
urls = buildSectionUrls(
|
83
|
-
|
85
|
+
unless urls = section.upNormalizedUrls
|
86
|
+
urls = buildSectionUrls(section)
|
87
|
+
section.upNormalizedUrls = urls
|
84
88
|
urls
|
85
89
|
|
86
|
-
buildSectionUrls = (
|
90
|
+
buildSectionUrls = (section) ->
|
87
91
|
urls = []
|
88
92
|
|
89
93
|
# A link with an unsafe method will never be higlighted with .up-current,
|
90
94
|
# so we cache an empty array.
|
91
|
-
if up.link.isSafe(
|
95
|
+
if up.link.isSafe(section)
|
92
96
|
for attr in ['href', 'up-href', 'up-alias']
|
93
|
-
if value =
|
97
|
+
if value = section.getAttribute(attr)
|
94
98
|
# Allow to include multiple space-separated URLs in [up-alias]
|
95
99
|
for url in u.splitValues(value)
|
96
100
|
unless url == '#'
|
@@ -111,37 +115,33 @@ up.feedback = (($) ->
|
|
111
115
|
updateAllNavigationSectionsIfLocationChanged = ->
|
112
116
|
previousUrlSet = currentUrlSet
|
113
117
|
currentUrlSet = buildCurrentUrlSet()
|
114
|
-
unless
|
115
|
-
updateAllNavigationSections(
|
118
|
+
unless u.isEqual(currentUrlSet, previousUrlSet)
|
119
|
+
updateAllNavigationSections(document.body)
|
116
120
|
|
117
|
-
updateAllNavigationSections = (
|
118
|
-
|
119
|
-
|
120
|
-
updateCurrentClassForLinks(
|
121
|
+
updateAllNavigationSections = (root) ->
|
122
|
+
navs = e.subtree(root, navSelector())
|
123
|
+
sections = u.flatMap navs, (nav) -> e.subtree(nav, SELECTOR_LINK)
|
124
|
+
updateCurrentClassForLinks(sections)
|
121
125
|
|
122
|
-
updateNavigationSectionsInNewFragment = (
|
123
|
-
if
|
126
|
+
updateNavigationSectionsInNewFragment = (fragment) ->
|
127
|
+
if e.closest(fragment, navSelector())
|
124
128
|
# If the new fragment is an [up-nav], or if the new fragment is a child of an [up-nav],
|
125
129
|
# all links in the new fragment are considered sections that we need to update.
|
126
130
|
# Note that:
|
127
131
|
# - The [up-nav] element might not be part of this update.
|
128
132
|
# It might already be in the DOM, and only a child was updated.
|
129
|
-
# - The
|
130
|
-
# - We do not need to update sibling links of
|
131
|
-
|
132
|
-
updateCurrentClassForLinks(
|
133
|
+
# - The fragment might be a link itself
|
134
|
+
# - We do not need to update sibling links of fragment that have been processed before.
|
135
|
+
sections = e.subtree(fragment, SELECTOR_LINK)
|
136
|
+
updateCurrentClassForLinks(sections)
|
133
137
|
else
|
134
|
-
updateAllNavigationSections(
|
138
|
+
updateAllNavigationSections(fragment)
|
135
139
|
|
136
|
-
updateCurrentClassForLinks = (
|
140
|
+
updateCurrentClassForLinks = (links) ->
|
137
141
|
currentUrlSet ||= buildCurrentUrlSet()
|
138
|
-
u.each
|
139
|
-
|
140
|
-
urls = sectionUrls($link)
|
142
|
+
u.each links, (link) ->
|
143
|
+
urls = sectionUrls(link)
|
141
144
|
|
142
|
-
# We use Element#classList to manipulate classes instead of jQuery's
|
143
|
-
# addClass and removeClass. Since we are in an inner loop, we want to
|
144
|
-
# be as fast as we can.
|
145
145
|
classList = link.classList
|
146
146
|
if currentUrlSet.matchesAny(urls)
|
147
147
|
for klass in config.currentClasses
|
@@ -157,12 +157,11 @@ up.feedback = (($) ->
|
|
157
157
|
@param {string|Element|jQuery} elementOrSelector
|
158
158
|
@internal
|
159
159
|
###
|
160
|
-
findActivatableArea = (
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
$area
|
160
|
+
findActivatableArea = (element) ->
|
161
|
+
element = e.get(element)
|
162
|
+
# Try to enlarge links that are expanded with [up-expand] on a surrounding container.
|
163
|
+
# Note that the expression below is not the same as e.closest(area, SELECTOR_LINK)!
|
164
|
+
e.ancestor(element, SELECTOR_LINK) || element
|
166
165
|
|
167
166
|
###**
|
168
167
|
Marks the given element as currently loading, by assigning the CSS class [`up-active`](/a.up-active).
|
@@ -175,39 +174,22 @@ up.feedback = (($) ->
|
|
175
174
|
|
176
175
|
\#\#\# Example
|
177
176
|
|
178
|
-
var
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
177
|
+
var button = document.querySelector('button')
|
178
|
+
|
179
|
+
button.addEventListener('click', () => {
|
180
|
+
up.feedback.start(button)
|
181
|
+
up.request(...).then(() => {
|
182
|
+
up.feedback.stop(button)
|
183
|
+
})
|
184
|
+
})
|
185
185
|
|
186
186
|
@method up.feedback.start
|
187
|
-
@param {Element|jQuery|string}
|
187
|
+
@param {Element|jQuery|string} element
|
188
188
|
The element to mark as active
|
189
|
-
@param {Object} [options.preload]
|
190
|
-
If set to `false`, the element will not be marked as loading.
|
191
|
-
@param {Function} [action]
|
192
|
-
An optional function to run while the element is marked as loading.
|
193
|
-
The function must return a promise.
|
194
|
-
Once the promise resolves, the element will be [marked as no longer loading](/up.feedback.stop).
|
195
189
|
@internal
|
196
190
|
###
|
197
|
-
start = (
|
198
|
-
|
199
|
-
action = args.pop()
|
200
|
-
options = u.options(args[0])
|
201
|
-
$element = findActivatableArea(elementOrSelector)
|
202
|
-
unless options.preload
|
203
|
-
$element.addClass(CLASS_ACTIVE)
|
204
|
-
if action
|
205
|
-
promise = action()
|
206
|
-
if u.isPromise(promise)
|
207
|
-
u.always promise, -> stop($element)
|
208
|
-
else
|
209
|
-
up.warn('Expected block to return a promise, but got %o', promise)
|
210
|
-
promise
|
191
|
+
start = (element) ->
|
192
|
+
findActivatableArea(element).classList.add(CLASS_ACTIVE)
|
211
193
|
|
212
194
|
###**
|
213
195
|
Links that are currently [loading through Unpoly](/form-up-target)
|
@@ -275,13 +257,12 @@ up.feedback = (($) ->
|
|
275
257
|
Use this function if you make custom network calls from your own JavaScript code.
|
276
258
|
|
277
259
|
@function up.feedback.stop
|
278
|
-
@param {jQuery}
|
260
|
+
@param {Element|jQuery|string} element
|
279
261
|
The link or form that has finished loading.
|
280
262
|
@internal
|
281
263
|
###
|
282
|
-
stop = (
|
283
|
-
|
284
|
-
$element.removeClass(CLASS_ACTIVE)
|
264
|
+
stop = (element) ->
|
265
|
+
findActivatableArea(element).classList.remove(CLASS_ACTIVE)
|
285
266
|
|
286
267
|
###**
|
287
268
|
Marks this element as a navigation component, such as a menu or navigation bar.
|
@@ -333,10 +314,10 @@ up.feedback = (($) ->
|
|
333
314
|
- the link's `up-href` attribute
|
334
315
|
- a space-separated list of URLs in the link's `up-alias` attribute
|
335
316
|
|
336
|
-
\#\#\# Matching URL by
|
317
|
+
\#\#\# Matching URL by pattern
|
337
318
|
|
338
|
-
You can mark a link as `.up-current` whenever the current URL matches a prefix.
|
339
|
-
To do so,
|
319
|
+
You can mark a link as `.up-current` whenever the current URL matches a prefix or suffix.
|
320
|
+
To do so, include an asterisk (`*`) in the `up-alias` attribute.
|
340
321
|
|
341
322
|
For instance, the following `[up-nav]` link is highlighted for both `/reports` and `/reports/123`:
|
342
323
|
|
@@ -356,11 +337,11 @@ up.feedback = (($) ->
|
|
356
337
|
###
|
357
338
|
|
358
339
|
# Even when the modal or popup does not change history, we consider the URLs of the content it displays.
|
359
|
-
up.on 'up:history:pushed up:history:replaced up:history:restored up:modal:opened up:modal:closed up:popup:opened up:popup:closed', (event) ->
|
340
|
+
up.on 'up:history:pushed up:history:replaced up:history:restored up:modal:opened up:modal:closed up:popup:opened up:popup:closed', (event) -> # take 1 arg to prevent data parsing
|
360
341
|
updateAllNavigationSectionsIfLocationChanged()
|
361
342
|
|
362
|
-
up.on 'up:fragment:inserted', (event,
|
363
|
-
updateNavigationSectionsInNewFragment(
|
343
|
+
up.on 'up:fragment:inserted', (event, newFragment) ->
|
344
|
+
updateNavigationSectionsInNewFragment(newFragment)
|
364
345
|
|
365
346
|
# The framework is reset between tests
|
366
347
|
up.on 'up:framework:reset', reset
|
@@ -369,6 +350,4 @@ up.feedback = (($) ->
|
|
369
350
|
start: start
|
370
351
|
stop: stop
|
371
352
|
|
372
|
-
|
373
|
-
|
374
|
-
up.deprecateRenamedModule 'navigation', 'feedback'
|
353
|
+
up.legacy.renamedModule 'navigation', 'feedback'
|