/* eslint-disable @typescript-eslint/no-empty-interface */ /* eslint-disable prefer-const */ /** * # Angular 1 Directives * * These are the directives included in UI-Router for Angular 1. * These directives are used in templates to create viewports and link/navigate to states. * * @preferred @publicapi @module directives */ /** */ import { ng as angular } from '../angular'; import { extend, forEach, tail, isString, isObject, isArray, parse, noop, unnestR, identity, uniqR, inArray, removeFrom, } from '@uirouter/core'; /** @hidden */ function parseStateRef(ref) { var paramsOnly = ref.match(/^\s*({[^}]*})\s*$/); if (paramsOnly) ref = '(' + paramsOnly[1] + ')'; var parsed = ref.replace(/\n/g, ' ').match(/^\s*([^(]*?)\s*(\((.*)\))?\s*$/); if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'"); return { state: parsed[1] || null, paramExpr: parsed[3] || null }; } /** @hidden */ function stateContext(el) { var $uiView = el.parent().inheritedData('$uiView'); var path = parse('$cfg.path')($uiView); return path ? tail(path).state.name : undefined; } /** @hidden */ function processedDef($state, $element, def) { var uiState = def.uiState || $state.current.name; var uiStateOpts = extend(defaultOpts($element, $state), def.uiStateOpts || {}); var href = $state.href(uiState, def.uiStateParams, uiStateOpts); return { uiState: uiState, uiStateParams: def.uiStateParams, uiStateOpts: uiStateOpts, href: href }; } /** @hidden */ function getTypeInfo(el) { // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. var isSvg = Object.prototype.toString.call(el.prop('href')) === '[object SVGAnimatedString]'; var isForm = el[0].nodeName === 'FORM'; return { attr: isForm ? 'action' : isSvg ? 'xlink:href' : 'href', isAnchor: el.prop('tagName').toUpperCase() === 'A', clickable: !isForm, }; } /** @hidden */ function clickHook(el, $state, $timeout, type, getDef) { return function (e) { var button = e.which || e.button, target = getDef(); if (!(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey || el.attr('target'))) { // HACK: This is to allow ng-clicks to be processed before the transition is initiated: var transition_1 = $timeout(function () { if (!el.attr('disabled')) { $state.go(target.uiState, target.uiStateParams, target.uiStateOpts); } }); e.preventDefault(); // if the state has no URL, ignore one preventDefault from the directive. var ignorePreventDefaultCount_1 = type.isAnchor && !target.href ? 1 : 0; e.preventDefault = function () { if (ignorePreventDefaultCount_1-- <= 0) $timeout.cancel(transition_1); }; } }; } /** @hidden */ function defaultOpts(el, $state) { return { relative: stateContext(el) || $state.$current, inherit: true, source: 'sref', }; } /** @hidden */ function bindEvents(element, scope, hookFn, uiStateOpts) { var events; if (uiStateOpts) { events = uiStateOpts.events; } if (!isArray(events)) { events = ['click']; } var on = element.on ? 'on' : 'bind'; for (var _i = 0, events_1 = events; _i < events_1.length; _i++) { var event_1 = events_1[_i]; element[on](event_1, hookFn); } scope.$on('$destroy', function () { var off = element.off ? 'off' : 'unbind'; for (var _i = 0, events_2 = events; _i < events_2.length; _i++) { var event_2 = events_2[_i]; element[off](event_2, hookFn); } }); } /** * `ui-sref`: A directive for linking to a state * * A directive which links to a state (and optionally, parameters). * When clicked, this directive activates the linked state with the supplied parameter values. * * ### Linked State * The attribute value of the `ui-sref` is the name of the state to link to. * * #### Example: * This will activate the `home` state when the link is clicked. * ```html * Home * ``` * * ### Relative Links * You can also use relative state paths within `ui-sref`, just like a relative path passed to `$state.go()` ([[StateService.go]]). * You just need to be aware that the path is relative to the state that *created* the link. * This allows a state to create a relative `ui-sref` which always targets the same destination. * * #### Example: * Both these links are relative to the parent state, even when a child state is currently active. * ```html * child 1 state * child 2 state * ``` * * This link activates the parent state. * ```html * Return * ``` * * ### hrefs * If the linked state has a URL, the directive will automatically generate and * update the `href` attribute (using the [[StateService.href]] method). * * #### Example: * Assuming the `users` state has a url of `/users/` * ```html * Users * ``` * * ### Parameter Values * In addition to the state name, a `ui-sref` can include parameter values which are applied when activating the state. * Param values can be provided in the `ui-sref` value after the state name, enclosed by parentheses. * The content inside the parentheses is an expression, evaluated to the parameter values. * * #### Example: * This example renders a list of links to users. * The state's `userId` parameter value comes from each user's `user.id` property. * ```html *
  • * {{ user.displayName }} *
  • * ``` * * Note: * The parameter values expression is `$watch`ed for updates. * * ### Transition Options * You can specify [[TransitionOptions]] to pass to [[StateService.go]] by using the `ui-sref-opts` attribute. * Options are restricted to `location`, `inherit`, and `reload`. * * #### Example: * ```html * Home * ``` * * ### Other DOM Events * * You can also customize which DOM events to respond to (instead of `click`) by * providing an `events` array in the `ui-sref-opts` attribute. * * #### Example: * ```html * * ``` * * ### Highlighting the active link * This directive can be used in conjunction with [[uiSrefActive]] to highlight the active link. * * ### Examples * If you have the following template: * * ```html * Home * About * Next page * * * ``` * * Then (assuming the current state is `contacts`) the rendered html including hrefs would be: * * ```html * Home * About * Next page * * * * Home * ``` * * ### Notes * * - You can use `ui-sref` to change **only the parameter values** by omitting the state name and parentheses. * #### Example: * Sets the `lang` parameter to `en` and remains on the same state. * * ```html * English * ``` * * - A middle-click, right-click, or ctrl-click is handled (natively) by the browser to open the href in a new window, for example. * * - Unlike the parameter values expression, the state name is not `$watch`ed (for performance reasons). * If you need to dynamically update the state being linked to, use the fully dynamic [[uiState]] directive. */ var uiSrefDirective; uiSrefDirective = [ '$uiRouter', '$timeout', function $StateRefDirective($uiRouter, $timeout) { var $state = $uiRouter.stateService; return { restrict: 'A', require: ['?^uiSrefActive', '?^uiSrefActiveEq'], link: function (scope, element, attrs, uiSrefActive) { var type = getTypeInfo(element); var active = uiSrefActive[1] || uiSrefActive[0]; var unlinkInfoFn = null; var rawDef = {}; var getDef = function () { return processedDef($state, element, rawDef); }; var ref = parseStateRef(attrs.uiSref); rawDef.uiState = ref.state; rawDef.uiStateOpts = attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {}; function update() { var def = getDef(); if (unlinkInfoFn) unlinkInfoFn(); if (active) unlinkInfoFn = active.$$addStateInfo(def.uiState, def.uiStateParams); if (def.href != null) attrs.$set(type.attr, def.href); } if (ref.paramExpr) { scope.$watch(ref.paramExpr, function (val) { rawDef.uiStateParams = extend({}, val); update(); }, true); rawDef.uiStateParams = extend({}, scope.$eval(ref.paramExpr)); } update(); scope.$on('$destroy', $uiRouter.stateRegistry.onStatesChanged(update)); scope.$on('$destroy', $uiRouter.transitionService.onSuccess({}, update)); if (!type.clickable) return; var hookFn = clickHook(element, $state, $timeout, type, getDef); bindEvents(element, scope, hookFn, rawDef.uiStateOpts); }, }; }, ]; /** * `ui-state`: A fully dynamic directive for linking to a state * * A directive which links to a state (and optionally, parameters). * When clicked, this directive activates the linked state with the supplied parameter values. * * **This directive is very similar to [[uiSref]], but it `$observe`s and `$watch`es/evaluates all its inputs.** * * A directive which links to a state (and optionally, parameters). * When clicked, this directive activates the linked state with the supplied parameter values. * * ### Linked State * The attribute value of `ui-state` is an expression which is `$watch`ed and evaluated as the state to link to. * **This is in contrast with `ui-sref`, which takes a state name as a string literal.** * * #### Example: * Create a list of links. * ```html *
  • * {{ link.displayName }} *
  • * ``` * * ### Relative Links * If the expression evaluates to a relative path, it is processed like [[uiSref]]. * You just need to be aware that the path is relative to the state that *created* the link. * This allows a state to create relative `ui-state` which always targets the same destination. * * ### hrefs * If the linked state has a URL, the directive will automatically generate and * update the `href` attribute (using the [[StateService.href]] method). * * ### Parameter Values * In addition to the state name expression, a `ui-state` can include parameter values which are applied when activating the state. * Param values should be provided using the `ui-state-params` attribute. * The `ui-state-params` attribute value is `$watch`ed and evaluated as an expression. * * #### Example: * This example renders a list of links with param values. * The state's `userId` parameter value comes from each user's `user.id` property. * ```html *
  • * {{ link.displayName }} *
  • * ``` * * ### Transition Options * You can specify [[TransitionOptions]] to pass to [[StateService.go]] by using the `ui-state-opts` attribute. * Options are restricted to `location`, `inherit`, and `reload`. * The value of the `ui-state-opts` is `$watch`ed and evaluated as an expression. * * #### Example: * ```html * Home * ``` * * ### Other DOM Events * * You can also customize which DOM events to respond to (instead of `click`) by * providing an `events` array in the `ui-state-opts` attribute. * * #### Example: * ```html * * ``` * * ### Highlighting the active link * This directive can be used in conjunction with [[uiSrefActive]] to highlight the active link. * * ### Notes * * - You can use `ui-params` to change **only the parameter values** by omitting the state name and supplying only `ui-state-params`. * However, it might be simpler to use [[uiSref]] parameter-only links. * * #### Example: * Sets the `lang` parameter to `en` and remains on the same state. * * ```html * English * ``` * * - A middle-click, right-click, or ctrl-click is handled (natively) by the browser to open the href in a new window, for example. * ``` */ var uiStateDirective; uiStateDirective = [ '$uiRouter', '$timeout', function $StateRefDynamicDirective($uiRouter, $timeout) { var $state = $uiRouter.stateService; return { restrict: 'A', require: ['?^uiSrefActive', '?^uiSrefActiveEq'], link: function (scope, element, attrs, uiSrefActive) { var type = getTypeInfo(element); var active = uiSrefActive[1] || uiSrefActive[0]; var unlinkInfoFn = null; var hookFn; var rawDef = {}; var getDef = function () { return processedDef($state, element, rawDef); }; var inputAttrs = ['uiState', 'uiStateParams', 'uiStateOpts']; var watchDeregFns = inputAttrs.reduce(function (acc, attr) { return ((acc[attr] = noop), acc); }, {}); function update() { var def = getDef(); if (unlinkInfoFn) unlinkInfoFn(); if (active) unlinkInfoFn = active.$$addStateInfo(def.uiState, def.uiStateParams); if (def.href != null) attrs.$set(type.attr, def.href); } inputAttrs.forEach(function (field) { rawDef[field] = attrs[field] ? scope.$eval(attrs[field]) : null; attrs.$observe(field, function (expr) { watchDeregFns[field](); watchDeregFns[field] = scope.$watch(expr, function (newval) { rawDef[field] = newval; update(); }, true); }); }); update(); scope.$on('$destroy', $uiRouter.stateRegistry.onStatesChanged(update)); scope.$on('$destroy', $uiRouter.transitionService.onSuccess({}, update)); if (!type.clickable) return; hookFn = clickHook(element, $state, $timeout, type, getDef); bindEvents(element, scope, hookFn, rawDef.uiStateOpts); }, }; }, ]; /** * `ui-sref-active` and `ui-sref-active-eq`: A directive that adds a CSS class when a `ui-sref` is active * * A directive working alongside [[uiSref]] and [[uiState]] to add classes to an element when the * related directive's state is active (and remove them when it is inactive). * * The primary use-case is to highlight the active link in navigation menus, * distinguishing it from the inactive menu items. * * ### Linking to a `ui-sref` or `ui-state` * `ui-sref-active` can live on the same element as `ui-sref`/`ui-state`, or it can be on a parent element. * If a `ui-sref-active` is a parent to more than one `ui-sref`/`ui-state`, it will apply the CSS class when **any of the links are active**. * * ### Matching * * The `ui-sref-active` directive applies the CSS class when the `ui-sref`/`ui-state`'s target state **or any child state is active**. * This is a "fuzzy match" which uses [[StateService.includes]]. * * The `ui-sref-active-eq` directive applies the CSS class when the `ui-sref`/`ui-state`'s target state is directly active (not when child states are active). * This is an "exact match" which uses [[StateService.is]]. * * ### Parameter values * If the `ui-sref`/`ui-state` includes parameter values, the current parameter values must match the link's values for the link to be highlighted. * This allows a list of links to the same state with different parameters to be rendered, and the correct one highlighted. * * #### Example: * ```html *
  • * {{ user.lastName }} *
  • * ``` * * ### Examples * * Given the following template: * #### Example: * ```html * * ``` * * When the app state is `app.user` (or any child state), * and contains the state parameter "user" with value "bilbobaggins", * the resulting HTML will appear as (note the 'active' class): * * ```html * * ``` * * ### Glob mode * * It is possible to pass `ui-sref-active` an expression that evaluates to an object. * The objects keys represent active class names and values represent the respective state names/globs. * `ui-sref-active` will match if the current active state **includes** any of * the specified state names/globs, even the abstract ones. * * #### Example: * Given the following template, with "admin" being an abstract state: * ```html *
    * Roles *
    * ``` * * Arrays are also supported as values in the `ngClass`-like interface. * This allows multiple states to add `active` class. * * #### Example: * Given the following template, with "admin.roles" being the current state, the class will be added too: * ```html *
    * Roles *
    * ``` * * When the current state is "admin.roles" the "active" class will be applied to both the `
    ` and `` elements. * It is important to note that the state names/globs passed to `ui-sref-active` override any state provided by a linked `ui-sref`. * * ### Notes: * * - The class name is interpolated **once** during the directives link time (any further changes to the * interpolated value are ignored). * * - Multiple classes may be specified in a space-separated format: `ui-sref-active='class1 class2 class3'` */ var uiSrefActiveDirective; uiSrefActiveDirective = [ '$state', '$stateParams', '$interpolate', '$uiRouter', function $StateRefActiveDirective($state, $stateParams, $interpolate, $uiRouter) { return { restrict: 'A', controller: [ '$scope', '$element', '$attrs', function ($scope, $element, $attrs) { var states = []; var activeEqClass; var uiSrefActive; // There probably isn't much point in $observing this // uiSrefActive and uiSrefActiveEq share the same directive object with some // slight difference in logic routing activeEqClass = $interpolate($attrs.uiSrefActiveEq || '', false)($scope); try { uiSrefActive = $scope.$eval($attrs.uiSrefActive); } catch (e) { // Do nothing. uiSrefActive is not a valid expression. // Fall back to using $interpolate below } uiSrefActive = uiSrefActive || $interpolate($attrs.uiSrefActive || '', false)($scope); setStatesFromDefinitionObject(uiSrefActive); // Allow uiSref to communicate with uiSrefActive[Equals] this.$$addStateInfo = function (newState, newParams) { // we already got an explicit state provided by ui-sref-active, so we // shadow the one that comes from ui-sref if (isObject(uiSrefActive) && states.length > 0) { return; } var deregister = addState(newState, newParams, uiSrefActive); update(); return deregister; }; function updateAfterTransition(trans) { trans.promise.then(update, noop); } $scope.$on('$destroy', setupEventListeners()); if ($uiRouter.globals.transition) { updateAfterTransition($uiRouter.globals.transition); } function setupEventListeners() { var deregisterStatesChangedListener = $uiRouter.stateRegistry.onStatesChanged(handleStatesChanged); var deregisterOnStartListener = $uiRouter.transitionService.onStart({}, updateAfterTransition); var deregisterStateChangeSuccessListener = $scope.$on('$stateChangeSuccess', update); return function cleanUp() { deregisterStatesChangedListener(); deregisterOnStartListener(); deregisterStateChangeSuccessListener(); }; } function handleStatesChanged() { setStatesFromDefinitionObject(uiSrefActive); } function setStatesFromDefinitionObject(statesDefinition) { if (isObject(statesDefinition)) { states = []; forEach(statesDefinition, function (stateOrName, activeClass) { // Helper function to abstract adding state. var addStateForClass = function (stateOrName, activeClass) { var ref = parseStateRef(stateOrName); addState(ref.state, $scope.$eval(ref.paramExpr), activeClass); }; if (isString(stateOrName)) { // If state is string, just add it. addStateForClass(stateOrName, activeClass); } else if (isArray(stateOrName)) { // If state is an array, iterate over it and add each array item individually. forEach(stateOrName, function (stateOrName) { addStateForClass(stateOrName, activeClass); }); } }); } } function addState(stateName, stateParams, activeClass) { var state = $state.get(stateName, stateContext($element)); var stateInfo = { state: state || { name: stateName }, params: stateParams, activeClass: activeClass, }; states.push(stateInfo); return function removeState() { removeFrom(states)(stateInfo); }; } // Update route state function update() { var splitClasses = function (str) { return str.split(/\s/).filter(identity); }; var getClasses = function (stateList) { return stateList .map(function (x) { return x.activeClass; }) .map(splitClasses) .reduce(unnestR, []); }; var allClasses = getClasses(states).concat(splitClasses(activeEqClass)).reduce(uniqR, []); var fuzzyClasses = getClasses(states.filter(function (x) { return $state.includes(x.state.name, x.params); })); var exactlyMatchesAny = !!states.filter(function (x) { return $state.is(x.state.name, x.params); }).length; var exactClasses = exactlyMatchesAny ? splitClasses(activeEqClass) : []; var addClasses = fuzzyClasses.concat(exactClasses).reduce(uniqR, []); var removeClasses = allClasses.filter(function (cls) { return !inArray(addClasses, cls); }); $scope.$evalAsync(function () { addClasses.forEach(function (className) { return $element.addClass(className); }); removeClasses.forEach(function (className) { return $element.removeClass(className); }); }); } update(); }, ], }; }, ]; angular .module('ui.router.state') .directive('uiSref', uiSrefDirective) .directive('uiSrefActive', uiSrefActiveDirective) .directive('uiSrefActiveEq', uiSrefActiveDirective) .directive('uiState', uiStateDirective); //# sourceMappingURL=stateDirectives.js.map