import { getOwner , setOwner } from '@ember/application' ; import { assert , deprecate } from '@ember/debug' ; import { action } from '@ember/object' ; import Transition from '@ember/routing/-private/transition' ; import RouterService from '@ember/routing/router-service' ; import { DEBUG } from '@glimmer/env' ; import { tracked } from '@glimmer/tracking' ; import LinkManagerService from './services/link-manager' ; const MAIN_BUTTON = 0 ; export type QueryParams = Record < string , unknown > ; export function isQueryParams ( maybeQueryParam ) { return ( maybeQueryParam ?. isQueryParams && typeof maybeQueryParam . values === 'object' ) ; } export type RouteModel = object | string | number ; export type RouteArgs = Parameters < RouterService [ 'urlFor' ] > ; export interface LinkParams { /**
     * The target route name.
     */ route : string ; /**
     * Optional array of models / dynamic segments.
     */ models ? : RouteModel [ ] ; /**
     * Optional query params object.
     */ query ? : QueryParams ; /**
     * An optional callback that will be fired when the Link is transitioned to.
     *
     * The callback is only fired if the Link is explicitly invoked, not if the
     * app transitions to the Link through other means.
     */ onTransitionTo ? : ( ) => void ; /**
     * An optional callback that will be fired when the current route is replaced
     * with the Link.
     *
     * The callback is only fired if the Link is explicitly invoked, not if the
     * app transitions to the Link through other means.
     */ onReplaceWith ? : ( ) => void ; } function freezeParams ( params ) { if ( DEBUG ) { if ( params . models ) Object . freeze ( params . models ) ; if ( params . query ) Object . freeze ( params . query ) ; return Object . freeze ( params ) ; } return params ; } function isUnmodifiedLeftClick ( event ) { return event . button === MAIN_BUTTON && ! event . ctrlKey && ! event . metaKey ; } function isMouseEvent ( event ) { return typeof event === 'object' && event !== null && 'button' in event ; } export default class Link { @ tracked protected _params : LinkParams ; protected _linkManager : LinkManagerService ; constructor ( linkManager , params ) { setOwner ( this , getOwner ( linkManager ) ) ; this . _linkManager = linkManager ; this . _params = freezeParams ( params ) ; } private get _routeArgs ( ) : RouteArgs { const { routeName , models , queryParams } = this ; if ( queryParams ) { return [ routeName , ... models , { queryParams : { ... queryParams } } ] as RouteArgs ; } return [ routeName , ... models ] as RouteArgs ; } /**
     * Whether this route is currently active, including potentially supplied
     * models and query params.
     */ get isActive ( ) : boolean { if ( ! this . _linkManager . isRouterInitialized ) return false ; this . _linkManager . currentTransitionStack ; return this . _linkManager . router . isActive ( ... this . _routeArgs ) ; } /**
     * Whether this route is currently active, including potentially supplied
     * models, but ignoring query params.
     */ get isActiveWithoutQueryParams ( ) : boolean { if ( ! this . _linkManager . isRouterInitialized ) return false ; this . _linkManager . currentTransitionStack ; return this . _linkManager . router . isActive ( this . routeName , ... this . models ) ; } /**
     * Whether this route is currently active, but ignoring models and query
     * params.
     */ get isActiveWithoutModels ( ) : boolean { if ( ! this . _linkManager . isRouterInitialized ) return false ; this . _linkManager . currentTransitionStack ; return this . _linkManager . router . isActive ( this . routeName ) ; } /**
     * Whether this route is currently being transitioned into / entered.
     */ get isEntering ( ) : boolean { return this . _isTransitioning ( 'to' ) ; } /**
     * Whether this route is currently being transitioned out of / exited.
     */ get isExiting ( ) : boolean { return this . _isTransitioning ( 'from' ) ; } /**
     * The URL for this link that you can pass to an `<a>` tag as the `href`
     * attribute.
     */ get url ( ) : string { if ( ! this . _linkManager . isRouterInitialized ) return '' ; return this . _linkManager . router . urlFor ( ... this . _routeArgs ) ; } /**
     * Deprecated alias for `url`.
     */ get href ( ) : string { deprecate ( '`href` is deprecated. Use `url` instead.' , false , { id : 'ember-link.link.href' , until : '2.0.0' , for : 'ember-link' , since : { available : '1.1.0' , enabled : '1.1.0' } } ) ; return this . url ; } /**
     * Alias for `url`.
     *
     * Allows for more ergonomic composition as query parameters.
     *
     * ```hbs
     * {{link "foo" query=(hash bar=(link "bar"))}}
     * ```
     */ toString ( ) { return this . url ; } /**
     * The `RouteInfo` object for the target route.
     */ /**
     * The target route name of this link.
     */ get routeName ( ) : string { return this . _params . route ; } /**
     * The fully qualified target route name of this link.
     */ get qualifiedRouteName ( ) : string { const routeInfo = this . _linkManager . router . recognize ( this . url ) ; return routeInfo . name ; } /**
     * The route models passed in this link.
     */ get models ( ) : RouteModel [ ] { return this . _params . models ?? [ ] ; } /**
     * The query params for this link, if specified.
     */ get queryParams ( ) : QueryParams | undefined { return this . _params . query ; } private _isTransitioning ( direction ) { return ( this . _linkManager . currentTransitionStack ?. some ( transition => { return transition [ direction ] ?. name === this . qualifiedRouteName ; } ) ?? false ) ; } /**
     * Transition into the target route.
     */ @ action transitionTo ( ) { assert ( 'You can only call `transitionTo`, when the router is initialized, e.g. when using `setupApplicationTest`.' , this . _linkManager . isRouterInitialized ) ; this . _params . onTransitionTo ?. ( ) ; return this . _linkManager . router . transitionTo ( ... this . _routeArgs ) ; } /**
     * Transition into the target route while replacing the current URL, if
     * possible.
     */ @ action replaceWith ( ) { assert ( 'You can only call `replaceWith`, when the router is initialized, e.g. when using `setupApplicationTest`.' , this . _linkManager . isRouterInitialized ) ; this . _params . onReplaceWith ?. ( ) ; return this . _linkManager . router . replaceWith ( ... this . _routeArgs ) ; } } export interface UILinkParams { /**
     * Whether or not to call `event.preventDefault()`, if the first parameter to
     * the `transitionTo` or `replaceWith` action is an `Event`. This is useful to
     * prevent links from accidentally triggering real browser navigation or
     * buttons from submitting a form.
     *
     * Defaults to `true`.
     */ preventDefault ? : boolean ; } export class UILink extends Link { @ tracked protected _params ! : LinkParams & UILinkParams ; private preventDefault ( event ? ) { if ( ( this . _params . preventDefault ?? true ) && typeof ( event as Event ) ?. preventDefault === 'function' ) { ( event as Event ) . preventDefault ( ) ; } } /**
     * Transition into the target route.
     *
     * Optionally call `preventDefault()`, if an `Event` is passed in.
     */ @ action transitionTo ( event ? ) { if ( isMouseEvent ( event ) && ! isUnmodifiedLeftClick ( event ) ) return ; this . preventDefault ( event ) ; return super . transitionTo ( ) ; } /**
     * Transition into the target route while replacing the current URL, if
     * possible.
     *
     * Optionally call `preventDefault()`, if an `Event` is passed in.
     */ @ action replaceWith ( event ? ) { if ( isMouseEvent ( event ) && ! isUnmodifiedLeftClick ( event ) ) return ; this . preventDefault ( event ) ; return super . replaceWith ( ) ; } }