@@ -109,13 +109,63 @@ export function handle(
109109 ignore : string [ ] ,
110110 event : MouseEvent
111111) {
112- if ( ! element ) return ;
112+ if ( ! shouldHandle ( location . href , element , ignore , event ) ) {
113+ return ;
114+ }
115+
116+ let url = new URL ( element . href ) ;
117+
118+ let fullHref = `${ url . pathname } ${ url . search } ${ url . hash } ` ;
119+
120+ let rootURL = router . rootURL ;
121+
122+ let withoutRootURL = fullHref . slice ( rootURL . length ) ;
123+
124+ // re-add the "root" sigil
125+ // we removed it when we chopped off the rootURL,
126+ // because the rootURL often has this attached to it as well
127+ if ( ! withoutRootURL . startsWith ( '/' ) ) {
128+ withoutRootURL = `/${ withoutRootURL } ` ;
129+ }
130+
131+ try {
132+ let routeInfo = router . recognize ( fullHref ) ;
133+
134+ if ( routeInfo ) {
135+ event . preventDefault ( ) ;
136+ event . stopImmediatePropagation ( ) ;
137+ event . stopPropagation ( ) ;
138+
139+ router . transitionTo ( withoutRootURL ) ;
140+
141+ return false ;
142+ }
143+ } catch ( e ) {
144+ if ( e instanceof Error && e . name === 'UnrecognizedURLError' ) {
145+ return ;
146+ }
147+
148+ throw e ;
149+ }
150+ }
151+
152+ /**
153+ * Returns `true` if the link should be handled by the Ember router
154+ * Returns `false` if the link should be handled by the browser
155+ */
156+ export function shouldHandle (
157+ href : string ,
158+ element : HTMLAnchorElement ,
159+ ignore : string [ ] ,
160+ event : MouseEvent
161+ ) {
162+ if ( ! element ) return false ;
113163 /**
114164 * If we don't have an href, the <a> is invalid.
115165 * If you're debugging your code and end up finding yourself
116166 * early-returning here, please add an href ;)
117167 */
118- if ( ! element . href ) return ;
168+ if ( ! element . href ) return false ;
119169
120170 /**
121171 * This is partially an escape hatch, but any time target is set,
@@ -129,91 +179,75 @@ export function handle(
129179 * "proper links" is to do what is expected, always -- for in-app SPA links
130180 * as well as external, cross-domain links
131181 */
132- if ( element . target ) return ;
182+ if ( element . target ) return false ;
133183
134184 /**
135185 * If the click is not a "left click" we don't want to intercept the event.
136186 * This allows folks to
137187 * - middle click (usually open the link in a new tab)
138188 * - right click (usually opens the context menu)
139189 */
140- if ( event . button !== 0 ) return ;
190+ if ( event . button !== 0 ) return false ;
141191
142192 /**
143193 * for MacOS users, this default behavior opens the link in a new tab
144194 */
145- if ( event . metaKey ) return ;
195+ if ( event . metaKey ) return false ;
146196
147197 /**
148198 * for for everyone else, this default behavior opens the link in a new tab
149199 */
150- if ( event . ctrlKey ) return ;
200+ if ( event . ctrlKey ) return false ;
151201
152202 /**
153203 * The default behavior here downloads the link content
154204 */
155- if ( event . altKey ) return ;
205+ if ( event . altKey ) return false ;
156206
157207 /**
158208 * The default behavior here opens the link in a new window
159209 */
160- if ( event . shiftKey ) return ;
210+ if ( event . shiftKey ) return false ;
161211
162212 /**
163213 * If another event listener called event.preventDefault(), we don't want to proceed.
164214 */
165- if ( event . defaultPrevented ) return ;
215+ if ( event . defaultPrevented ) return false ;
166216
167217 /**
168218 * The href includes the protocol/host/etc
169219 * In order to not have the page look like a full page refresh,
170220 * we need to chop that "origin" off, and just use the path
171221 */
172222 let url = new URL ( element . href ) ;
223+ let location = new URL ( href ) ;
173224
174225 /**
175226 * If the domains are different, we want to fall back to normal link behavior
176227 *
177228 */
178- if ( location . origin !== url . origin ) return ;
229+ if ( location . origin !== url . origin ) return false ;
179230
180231 /**
181- * We can optionally declare some paths as ignored,
182- * or "let the browser do its default thing,
183- * because there is other server-based routing to worry about"
232+ * Hash-only links are handled by the browser, except for the case where the
233+ * hash is being removed entirely, e.g. /foo#bar to /foo. In that case the
234+ * browser will do a full page refresh which is not what we want. Instead
235+ * we let the router handle such transitions. The current implementation of
236+ * the Ember router will skip the transition in this case because the path
237+ * is the same.
184238 */
185- if ( ignore . includes ( url . pathname ) ) return ;
186-
187- let fullHref = `${ url . pathname } ${ url . search } ${ url . hash } ` ;
188-
189- let rootURL = router . rootURL ;
239+ let [ prehash , posthash ] = url . href . split ( '#' ) ;
190240
191- let withoutRootURL = fullHref . slice ( rootURL . length ) ;
192-
193- // re-add the "root" sigil
194- // we removed it when we chopped off the rootURL,
195- // because the rootURL often has this attached to it as well
196- if ( ! withoutRootURL . startsWith ( '/' ) ) {
197- withoutRootURL = `/${ withoutRootURL } ` ;
241+ if ( posthash !== undefined && prehash === location . href . split ( '#' ) [ 0 ] ) {
242+ return false ;
198243 }
199244
200- try {
201- let routeInfo = router . recognize ( fullHref ) ;
202-
203- if ( routeInfo ) {
204- event . preventDefault ( ) ;
205- event . stopImmediatePropagation ( ) ;
206- event . stopPropagation ( ) ;
207-
208- router . transitionTo ( withoutRootURL ) ;
209-
210- return false ;
211- }
212- } catch ( e ) {
213- if ( e instanceof Error && e . name === 'UnrecognizedURLError' ) {
214- return ;
215- }
245+ /**
246+ * We can optionally declare some paths as ignored,
247+ * or "let the browser do its default thing,
248+ * because there is other server-based routing to worry about"
249+ */
250+ if ( ignore . includes ( url . pathname ) ) return false ;
216251
217- throw e ;
218- }
252+ return true ;
219253}
0 commit comments