"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.uiView = void 0; /** @publicapi @module directives */ /** */ var core_1 = require("@uirouter/core"); var angular_1 = require("../angular"); var services_1 = require("../services"); var views_1 = require("../statebuilders/views"); // eslint-disable-next-line prefer-const exports.uiView = [ '$view', '$animate', '$uiViewScroll', '$interpolate', '$q', function $ViewDirective($view, $animate, $uiViewScroll, $interpolate, $q) { function getRenderer() { return { enter: function (element, target, cb) { if (angular_1.ng.version.minor > 2) { $animate.enter(element, null, target).then(cb); } else { $animate.enter(element, null, target, cb); } }, leave: function (element, cb) { if (angular_1.ng.version.minor > 2) { $animate.leave(element).then(cb); } else { $animate.leave(element, cb); } }, }; } function configsEqual(config1, config2) { return config1 === config2; } var rootData = { $cfg: { viewDecl: { $context: $view._pluginapi._rootViewContext() } }, $uiView: {}, }; var directive = { count: 0, restrict: 'ECA', terminal: true, priority: 400, transclude: 'element', compile: function (tElement, tAttrs, $transclude) { return function (scope, $element, attrs) { var onloadExp = attrs['onload'] || '', autoScrollExp = attrs['autoscroll'], renderer = getRenderer(), inherited = $element.inheritedData('$uiView') || rootData, name = $interpolate(attrs['uiView'] || attrs['name'] || '')(scope) || '$default'; var previousEl, currentEl, currentScope, viewConfig; var activeUIView = { $type: 'ng1', id: directive.count++, name: name, fqn: inherited.$uiView.fqn ? inherited.$uiView.fqn + '.' + name : name, config: null, configUpdated: configUpdatedCallback, get creationContext() { // The context in which this ui-view "tag" was created var fromParentTagConfig = core_1.parse('$cfg.viewDecl.$context')(inherited); // Allow // See https://github.com/angular-ui/ui-router/issues/3355 var fromParentTag = core_1.parse('$uiView.creationContext')(inherited); return fromParentTagConfig || fromParentTag; }, }; core_1.trace.traceUIViewEvent('Linking', activeUIView); function configUpdatedCallback(config) { if (config && !(config instanceof views_1.Ng1ViewConfig)) return; if (configsEqual(viewConfig, config)) return; core_1.trace.traceUIViewConfigUpdated(activeUIView, config && config.viewDecl && config.viewDecl.$context); viewConfig = config; updateView(config); } $element.data('$uiView', { $uiView: activeUIView }); updateView(); var unregister = $view.registerUIView(activeUIView); scope.$on('$destroy', function () { core_1.trace.traceUIViewEvent('Destroying/Unregistering', activeUIView); unregister(); }); function cleanupLastView() { if (previousEl) { core_1.trace.traceUIViewEvent('Removing (previous) el', previousEl.data('$uiView')); previousEl.remove(); previousEl = null; } if (currentScope) { core_1.trace.traceUIViewEvent('Destroying scope', activeUIView); currentScope.$destroy(); currentScope = null; } if (currentEl) { var _viewData_1 = currentEl.data('$uiViewAnim'); core_1.trace.traceUIViewEvent('Animate out', _viewData_1); renderer.leave(currentEl, function () { _viewData_1.$$animLeave.resolve(); previousEl = null; }); previousEl = currentEl; currentEl = null; } } function updateView(config) { var newScope = scope.$new(); var animEnter = $q.defer(), animLeave = $q.defer(); var $uiViewData = { $cfg: config, $uiView: activeUIView, }; var $uiViewAnim = { $animEnter: animEnter.promise, $animLeave: animLeave.promise, $$animLeave: animLeave, }; /** * @ngdoc event * @name ui.router.state.directive:ui-view#$viewContentLoading * @eventOf ui.router.state.directive:ui-view * @eventType emits on ui-view directive scope * @description * * Fired once the view **begins loading**, *before* the DOM is rendered. * * @param {Object} event Event object. * @param {string} viewName Name of the view. */ newScope.$emit('$viewContentLoading', name); var cloned = $transclude(newScope, function (clone) { clone.data('$uiViewAnim', $uiViewAnim); clone.data('$uiView', $uiViewData); renderer.enter(clone, $element, function onUIViewEnter() { animEnter.resolve(); if (currentScope) currentScope.$emit('$viewContentAnimationEnded'); if ((core_1.isDefined(autoScrollExp) && !autoScrollExp) || scope.$eval(autoScrollExp)) { $uiViewScroll(clone); } }); cleanupLastView(); }); currentEl = cloned; currentScope = newScope; /** * @ngdoc event * @name ui.router.state.directive:ui-view#$viewContentLoaded * @eventOf ui.router.state.directive:ui-view * @eventType emits on ui-view directive scope * @description * * Fired once the view is **loaded**, *after* the DOM is rendered. * * @param {Object} event Event object. */ currentScope.$emit('$viewContentLoaded', config || viewConfig); currentScope.$eval(onloadExp); } }; }, }; return directive; }, ]; $ViewDirectiveFill.$inject = ['$compile', '$controller', '$transitions', '$view', '$q']; /** @hidden */ function $ViewDirectiveFill($compile, $controller, $transitions, $view, $q) { var getControllerAs = core_1.parse('viewDecl.controllerAs'); var getResolveAs = core_1.parse('viewDecl.resolveAs'); return { restrict: 'ECA', priority: -400, compile: function (tElement) { var initial = tElement.html(); tElement.empty(); return function (scope, $element) { var data = $element.data('$uiView'); if (!data) { $element.html(initial); $compile($element.contents())(scope); return; } var cfg = data.$cfg || { viewDecl: {}, getTemplate: core_1.noop }; var resolveCtx = cfg.path && new core_1.ResolveContext(cfg.path); $element.html(cfg.getTemplate($element, resolveCtx) || initial); core_1.trace.traceUIViewFill(data.$uiView, $element.html()); var link = $compile($element.contents()); var controller = cfg.controller; var controllerAs = getControllerAs(cfg); var resolveAs = getResolveAs(cfg); var locals = resolveCtx && services_1.getLocals(resolveCtx); scope[resolveAs] = locals; if (controller) { var controllerInstance = ($controller(controller, core_1.extend({}, locals, { $scope: scope, $element: $element }))); if (controllerAs) { scope[controllerAs] = controllerInstance; scope[controllerAs][resolveAs] = locals; } // TODO: Use $view service as a central point for registering component-level hooks // Then, when a component is created, tell the $view service, so it can invoke hooks // $view.componentLoaded(controllerInstance, { $scope: scope, $element: $element }); // scope.$on('$destroy', () => $view.componentUnloaded(controllerInstance, { $scope: scope, $element: $element })); $element.data('$ngControllerController', controllerInstance); $element.children().data('$ngControllerController', controllerInstance); registerControllerCallbacks($q, $transitions, controllerInstance, scope, cfg); } // Wait for the component to appear in the DOM if (core_1.isString(cfg.component)) { var kebobName = core_1.kebobString(cfg.component); var tagRegexp_1 = new RegExp("^(x-|data-)?" + kebobName + "$", 'i'); var getComponentController = function () { var directiveEl = [].slice .call($element[0].children) .filter(function (el) { return el && el.tagName && tagRegexp_1.exec(el.tagName); }); return directiveEl && angular_1.ng.element(directiveEl).data("$" + cfg.component + "Controller"); }; var deregisterWatch_1 = scope.$watch(getComponentController, function (ctrlInstance) { if (!ctrlInstance) return; registerControllerCallbacks($q, $transitions, ctrlInstance, scope, cfg); deregisterWatch_1(); }); } link(scope); }; }, }; } /** @hidden */ var hasComponentImpl = typeof angular_1.ng.module('ui.router')['component'] === 'function'; /** @hidden incrementing id */ var _uiCanExitId = 0; /** @hidden TODO: move these callbacks to $view and/or `/hooks/components.ts` or something */ function registerControllerCallbacks($q, $transitions, controllerInstance, $scope, cfg) { // Call $onInit() ASAP if (core_1.isFunction(controllerInstance.$onInit) && !((cfg.viewDecl.component || cfg.viewDecl.componentProvider) && hasComponentImpl)) { controllerInstance.$onInit(); } var viewState = core_1.tail(cfg.path).state.self; var hookOptions = { bind: controllerInstance }; // Add component-level hook for onUiParamsChanged if (core_1.isFunction(controllerInstance.uiOnParamsChanged)) { var resolveContext = new core_1.ResolveContext(cfg.path); var viewCreationTrans_1 = resolveContext.getResolvable('$transition$').data; // Fire callback on any successful transition var paramsUpdated = function ($transition$) { // Exit early if the $transition$ is the same as the view was created within. // Exit early if the $transition$ will exit the state the view is for. if ($transition$ === viewCreationTrans_1 || $transition$.exiting().indexOf(viewState) !== -1) return; var toParams = $transition$.params('to'); var fromParams = $transition$.params('from'); var getNodeSchema = function (node) { return node.paramSchema; }; var toSchema = $transition$.treeChanges('to').map(getNodeSchema).reduce(core_1.unnestR, []); var fromSchema = $transition$.treeChanges('from').map(getNodeSchema).reduce(core_1.unnestR, []); // Find the to params that have different values than the from params var changedToParams = toSchema.filter(function (param) { var idx = fromSchema.indexOf(param); return idx === -1 || !fromSchema[idx].type.equals(toParams[param.id], fromParams[param.id]); }); // Only trigger callback if a to param has changed or is new if (changedToParams.length) { var changedKeys_1 = changedToParams.map(function (x) { return x.id; }); // Filter the params to only changed/new to params. `$transition$.params()` may be used to get all params. var newValues = core_1.filter(toParams, function (val, key) { return changedKeys_1.indexOf(key) !== -1; }); controllerInstance.uiOnParamsChanged(newValues, $transition$); } }; $scope.$on('$destroy', $transitions.onSuccess({}, paramsUpdated, hookOptions)); } // Add component-level hook for uiCanExit if (core_1.isFunction(controllerInstance.uiCanExit)) { var id_1 = _uiCanExitId++; var cacheProp_1 = '_uiCanExitIds'; // Returns true if a redirect transition already answered truthy var prevTruthyAnswer_1 = function (trans) { return !!trans && ((trans[cacheProp_1] && trans[cacheProp_1][id_1] === true) || prevTruthyAnswer_1(trans.redirectedFrom())); }; // If a user answered yes, but the transition was later redirected, don't also ask for the new redirect transition var wrappedHook = function (trans) { var promise; var ids = (trans[cacheProp_1] = trans[cacheProp_1] || {}); if (!prevTruthyAnswer_1(trans)) { promise = $q.when(controllerInstance.uiCanExit(trans)); promise.then(function (val) { return (ids[id_1] = val !== false); }); } return promise; }; var criteria = { exiting: viewState.name }; $scope.$on('$destroy', $transitions.onBefore(criteria, wrappedHook, hookOptions)); } } angular_1.ng.module('ui.router.state').directive('uiView', exports.uiView); angular_1.ng.module('ui.router.state').directive('uiView', $ViewDirectiveFill); //# sourceMappingURL=viewDirective.js.map