UNPKG

@clr/angular

Version:

Angular components for Clarity

1,295 lines (1,286 loc) 136 kB
import * as i6 from '@angular/common'; import { isPlatformBrowser, CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { Injectable, EventEmitter, Output, Input, Component, Directive, ContentChild, ViewChildren, PLATFORM_ID, ViewChild, ContentChildren, Inject, NgModule } from '@angular/core'; import * as i3 from '@clr/angular/utils'; import { uniqueIdFactory } from '@clr/angular/utils'; import { filter } from 'rxjs/operators'; import { Subject, startWith, debounceTime, tap } from 'rxjs'; import * as i5 from '@clr/angular/icon'; import { ClarityIcons, errorStandardIcon, successStandardIcon, ClrIcon } from '@clr/angular/icon'; import * as i8 from '@clr/angular/modal'; import { ClrModalModule } from '@clr/angular/modal'; import { ClrAlertModule } from '@clr/angular/emphasis/alert'; /* * Copyright (c) 2016-2026 Broadcom. All Rights Reserved. * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. * This software is released under MIT license. * The full license information can be found in LICENSE in the root directory of this project. */ var ClrWizardFooterAlign; (function (ClrWizardFooterAlign) { ClrWizardFooterAlign["START"] = "start"; ClrWizardFooterAlign["END"] = "end"; })(ClrWizardFooterAlign || (ClrWizardFooterAlign = {})); const CLR_WIZARD_FOOTER_ALIGN_VALUES = new Set(Object.values(ClrWizardFooterAlign)); function footerAlignAttribute(value) { if (CLR_WIZARD_FOOTER_ALIGN_VALUES.has(value)) { return value; } throw new Error(`Invalid ClrWizardFooterAlign: "${value}". Expected one of: ${[...CLR_WIZARD_FOOTER_ALIGN_VALUES].join(', ')}`); } /* * Copyright (c) 2016-2026 Broadcom. All Rights Reserved. * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. * This software is released under MIT license. * The full license information can be found in LICENSE in the root directory of this project. */ var ClrWizardStepnavLayout; (function (ClrWizardStepnavLayout) { ClrWizardStepnavLayout["VERTICAL"] = "vertical"; ClrWizardStepnavLayout["HORIZONTAL"] = "horizontal"; })(ClrWizardStepnavLayout || (ClrWizardStepnavLayout = {})); const CLR_WIZARD_STEPNAV_LAYOUT_VALUES = new Set(Object.values(ClrWizardStepnavLayout)); function stepnavLayoutAttribute(value) { if (CLR_WIZARD_STEPNAV_LAYOUT_VALUES.has(value)) { return value; } throw new Error(`Invalid ClrWizardStepnavLayout: "${value}". Expected one of: ${[...CLR_WIZARD_STEPNAV_LAYOUT_VALUES].join(', ')}`); } /* * Copyright (c) 2016-2026 Broadcom. All Rights Reserved. * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. * This software is released under MIT license. * The full license information can be found in LICENSE in the root directory of this project. */ class ButtonHubService { constructor() { this.buttonsReady = false; this._previousBtnClicked = new Subject(); this._nextBtnClicked = new Subject(); this._dangerBtnClicked = new Subject(); this._cancelBtnClicked = new Subject(); this._finishBtnClicked = new Subject(); this._customBtnClicked = new Subject(); } get previousBtnClicked() { return this._previousBtnClicked.asObservable(); } get nextBtnClicked() { return this._nextBtnClicked.asObservable(); } get dangerBtnClicked() { return this._dangerBtnClicked.asObservable(); } get cancelBtnClicked() { return this._cancelBtnClicked.asObservable(); } get finishBtnClicked() { return this._finishBtnClicked.asObservable(); } get customBtnClicked() { return this._customBtnClicked.asObservable(); } buttonClicked(buttonType) { if ('previous' === buttonType) { this._previousBtnClicked.next(); } else if ('next' === buttonType) { this._nextBtnClicked.next(); } else if ('finish' === buttonType) { this._finishBtnClicked.next(); } else if ('danger' === buttonType) { this._dangerBtnClicked.next(); } else if ('cancel' === buttonType) { this._cancelBtnClicked.next(); } else { this._customBtnClicked.next(buttonType); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ButtonHubService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ButtonHubService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ButtonHubService, decorators: [{ type: Injectable }] }); /* * Copyright (c) 2016-2026 Broadcom. All Rights Reserved. * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. * This software is released under MIT license. * The full license information can be found in LICENSE in the root directory of this project. */ /** * PageCollectionService manages the collection of pages assigned to the wizard and offers * a number of functions useful across the wizards providers and subcomponents -- all related * to essentially lookups on the collection of pages. * * The easiest way to access PageCollectionService is via the wizard. The * following example would allow you to access your instance of the wizard from your host * component and thereby access the page collection via YourHostComponent.wizard.pageCollection. * * @example * <clr-wizard #wizard ...> * * @example * export class YourHostComponent { * @ViewChild("wizard") wizard: Wizard; * ... * } * * The heart of the page collection is the query list of pages, which it is assigned as a * reference to the Wizard.pages QueryList when the wizard is created. * */ class PageCollectionService { constructor() { /** * * @memberof PageCollectionService */ this._pagesReset = new Subject(); } /** * Converts the PageCollectionService.pages QueryList to an array and returns it. * * Useful for many instances when you would prefer a QueryList to act like an array. * * @memberof PageCollectionService */ get pagesAsArray() { return this.pages ? this.pages.toArray() : []; } /** * Returns the length of the pages query list. * * @memberof PageCollectionService */ get pagesCount() { return this.pages ? this.pages.length : 0; } /** * Returns the next-to-last page in the query list of pages. Operates as a getter * so that it isn't working with stale data. * * @memberof PageCollectionService */ get penultimatePage() { const pageCount = this.pagesCount; if (pageCount < 2) { return null; } return this.pagesAsArray[pageCount - 2]; } /** * Returns the last page in the query list of pages. Operates as a getter * so that it isn't working with stale data. * * @memberof PageCollectionService */ get lastPage() { const pageCount = this.pagesCount; if (pageCount < 1) { return null; } return this.pagesAsArray[pageCount - 1]; } /** * Returns the first page in the query list of pages. Operates as a getter * so that it isn't working with stale data. * * @memberof PageCollectionService */ get firstPage() { if (!this.pagesCount) { return null; } return this.pagesAsArray[0]; } /** * An observable that the navigation service listens to in order to know when * the page collection completed states have been reset to false so that way it * can also reset the navigation to make the first page in the page collection * current/active. * * @memberof PageCollectionService */ get pagesReset() { return this._pagesReset.asObservable(); } /** * Used mostly internally, but accepts a string ID and returns a ClrWizardPage * object that matches the ID passed. Note that IDs here should include the prefix * "clr-wizard-page-". * * Returns the next-to-last page in the query list of pages. Operates as a getter * so that it isn't working with stale data. * * @memberof PageCollectionService */ getPageById(id) { const foundPages = this.pages.filter((page) => id === page.id); return this.checkResults(foundPages, id); } /** * Accepts s number as a parameter and treats that number as the index of the page * you're looking for in the collection of pages. Returns a wizard page object. * * @memberof PageCollectionService */ getPageByIndex(index) { const pageCount = this.pagesCount; const pagesLastIndex = pageCount > 1 ? pageCount - 1 : 0; if (index < 0) { throw new Error('Cannot retrieve page with index of ' + index); } if (index > pagesLastIndex) { throw new Error('Page index is greater than length of pages array.'); } return this.pagesAsArray[index]; } /** * Takes a wizard page object as a parameter and returns its index in the * collection of pages. * * @memberof PageCollectionService */ getPageIndex(page) { const index = this.pagesAsArray.indexOf(page); if (index < 0) { throw new Error('Requested page cannot be found in collection of pages.'); } return index; } /** * Accepts two numeric indexes and returns an array of wizard page objects that include * all wizard pages in the page collection from the first index to the second. * * @memberof PageCollectionService */ pageRange(start, end) { let pages = []; if (start < 0 || end < 0) { return []; } if (start === null || typeof start === 'undefined' || isNaN(start)) { return []; } if (end === null || typeof end === 'undefined' || isNaN(end)) { return []; } if (end > this.pagesCount) { end = this.pagesCount; } pages = this.pagesAsArray; if (end - start === 0) { // just return the one page they want return [this.getPageByIndex(start)]; } // slice end does not include item referenced by end index, which is weird for users // incrementing end index here to correct that so users and other methods // don't have to think about it end = end + 1; // slice does not return the last one in the range but it does include the first one // does not modify original array return pages.slice(start, end); } /** * Accepts two wizard page objects and returns those page objects with all other page * objects between them in the page collection. It doesn't care which page is ahead of the * other in the parameters. It will be smart enough to figure that out on its own. * * @memberof PageCollectionService */ getPageRangeFromPages(page, otherPage) { const pageIndex = this.getPageIndex(page); const otherPageIndex = this.getPageIndex(otherPage); let startIndex; let endIndex; if (pageIndex <= otherPageIndex) { startIndex = pageIndex; endIndex = otherPageIndex; } else { startIndex = otherPageIndex; endIndex = pageIndex; } return this.pageRange(startIndex, endIndex); } /** * Takes a wizard page object as a parameter and returns the wizard page object of * the page immediately before it in the page collection. Returns null if there is * no page before the page it is passed. * * @memberof PageCollectionService */ getPreviousPage(page) { const myPageIndex = this.getPageIndex(page); const previousPageIndex = myPageIndex - 1; if (previousPageIndex < 0) { return null; } return this.getPageByIndex(previousPageIndex); } /** * Accepts a wizard page object as a parameter and returns a Boolean that says if * the page you sent it is complete. * * @memberof PageCollectionService */ previousPageIsCompleted(page) { if (!page) { return false; } const previousPage = this.getPreviousPage(page); if (null === previousPage) { // page is the first page. no previous page. return true; } return previousPage.completed; } /** * Takes a wizard page object as a parameter and returns the wizard page object of * the page immediately after it in the page collection. Returns null if there is * no page after the page it is passed. * * @memberof PageCollectionService */ getNextPage(page) { const myPageIndex = this.getPageIndex(page); const nextPageIndex = myPageIndex + 1; if (nextPageIndex >= this.pagesAsArray.length) { return null; } return this.getPageByIndex(nextPageIndex); } /** * Takes a wizard page object as a parameter and generates a step item id from the * page ID. Returns the generated step item ID as a string. * * @memberof PageCollectionService */ getStepItemIdForPage(page) { const pageId = page.id; const pageIdParts = pageId.split('-').reverse(); pageIdParts[1] = 'step'; return pageIdParts.reverse().join('-'); } /** * Generally only used internally to mark that a specific page has been "committed". * This involves marking the page complete and firing the ClrWizardPage.onCommit * (clrWizardPageOnCommit) output. Takes the wizard page object that you intend to * mark completed as a parameter. * * @memberof PageCollectionService */ commitPage(page) { const pageHasOverrides = page.stopNext || page.preventDefault; page.completed = true; if (!pageHasOverrides) { // prevent loop of event emission; alternate flows work off // of event emitters this is how they break that cycle. page.onCommit.emit(page.id); } } /** * Sets all completed states of the pages in the page collection to false and * notifies the navigation service to likewise reset the navigation. * * @memberof PageCollectionService */ reset() { this.pagesAsArray.forEach((page) => { page.completed = false; }); this._pagesReset.next(true); } /** * Rolls through all the pages in the page collection to make sure there are no * incomplete pages sandwiched between completed pages in the workflow. Identifies * the first incomplete page index and sets all pages behind it to a completed * state of false. * * @memberof PageCollectionService */ updateCompletedStates() { const firstIncompleteIndex = this.findFirstIncompletePageIndex(); if (firstIncompleteIndex === this.pagesAsArray.length - 1) { // all complete no need to do anything return; } this.pagesAsArray.forEach((page, index) => { if (index > firstIncompleteIndex) { page.completed = false; } }); } /** * Retrieves the index of the first incomplete page in the page collection. * * @memberof PageCollectionService */ findFirstIncompletePageIndex() { let returnIndex = null; this.pagesAsArray.forEach((page, index) => { if (null === returnIndex && false === page.completed) { returnIndex = index; } }); // fallthrough, all completed, return last page if (null === returnIndex) { returnIndex = this.pagesCount - 1; } return returnIndex; } findFirstIncompletePage() { const myIncompleteIndex = this.findFirstIncompletePageIndex(); return this.pagesAsArray[myIncompleteIndex]; } /** * Consolidates guard logic that prevents a couple of unfortunate edge cases with * look ups on the collection of pages. * * @memberof PageCollectionService */ checkResults(results, requestedPageId) { const foundPagesCount = results.length || 0; if (foundPagesCount > 1) { throw new Error('More than one page has the requested id ' + requestedPageId + '.'); } else if (foundPagesCount < 1) { throw new Error('No page can be found with the id ' + requestedPageId + '.'); } else { return results[0]; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PageCollectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PageCollectionService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PageCollectionService, decorators: [{ type: Injectable }] }); /* * Copyright (c) 2016-2026 Broadcom. All Rights Reserved. * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. * This software is released under MIT license. * The full license information can be found in LICENSE in the root directory of this project. */ /** * Performs navigation functions for a wizard and manages the current page. Presented as a * separate service to encapsulate the behavior of navigating and completing the wizard so * that it can be shared across the wizard and its sub-components. * * The easiest way to access the navigation service is there a reference on your wizard. The * Following example would allow you to access your instance of the wizard from your host * component and thereby access the navigation service via YourHostComponent.wizard.navService. * * @example * <clr-wizard #wizard ...> * * @example * export class YourHostComponent { * @ViewChild("wizard") wizard: Wizard; * ... * } * */ class WizardNavigationService { /** * Creates an instance of WizardNavigationService. Also sets up subscriptions * that listen to the button service to determine when a button has been clicked * in the wizard. Is also responsible for taking action when the page collection * requests that navigation be reset to its pristine state. * * @memberof WizardNavigationService */ constructor(pageCollection, buttonService) { this.pageCollection = pageCollection; this.buttonService = buttonService; /** * A Boolean flag used by the ClrWizardPage to avoid a race condition when pages are * loading and there is no current page defined. * * @memberof WizardNavigationService */ this.navServiceLoaded = false; /** * A boolean flag shared across the Wizard subcomponents that follows the value * of the Wizard.forceForward (clrWizardForceForwardNavigation) input. When true, * navigating backwards in the stepnav menu will reset any skipped pages' completed * state to false. * * This is useful when a wizard executes validation on a page-by-page basis when * the next button is clicked. * * @memberof WizardNavigationService */ this.forceForwardNavigation = false; /** * A boolean flag shared across the Wizard subcomponents that follows the value * of the Wizard.stopCancel (clrWizardPreventDefaultCancel) input. When true, the cancel * routine is subverted and must be reinstated in the host component calling Wizard.close() * at some point. * * @memberof WizardNavigationService */ this.wizardHasAltCancel = false; /** * A boolean flag shared across the Wizard subcomponents that follows the value * of the Wizard.stopNext (clrWizardPreventDefaultNext) input. When true, the next and finish * routines are subverted and must be reinstated in the host component calling Wizard.next(), * Wizard.forceNext(), Wizard.finish(), or Wizard.forceFinish(). * * @memberof WizardNavigationService */ this.wizardHasAltNext = false; /** * A boolean flag shared across the Wizard subcomponents that follows the value * of the Wizard.stopNavigation (clrWizardPreventNavigation) input. When true, all * navigational elements in the wizard are disabled. * * This is intended to freeze the wizard in place. Events are not fired so this is * not a way to implement alternate functionality for navigation. * * @memberof WizardNavigationService */ this.wizardStopNavigation = false; /** * A boolean flag shared with the stepnav items that prevents user clicks on * stepnav items from navigating the wizard. * * @memberof WizardNavigationService */ this.wizardDisableStepnav = false; /** * The layout of the wizard stepnav, either 'vertical' or 'horizontal'. */ this.stepnavLayout = ClrWizardStepnavLayout.VERTICAL; /** * * @memberof WizardNavigationService */ this._currentChanged = new Subject(); /** * @memberof WizardNavigationService */ this._movedToNextPage = new Subject(); /** * @memberof WizardNavigationService */ this._wizardFinished = new Subject(); /** * @memberof WizardNavigationService */ this._movedToPreviousPage = new Subject(); /** * @memberof WizardNavigationService */ this._cancelWizard = new Subject(); this.previousButtonSubscription = buttonService.previousBtnClicked.subscribe(() => { const currentPage = this.currentPage; if (this.currentPageIsFirst || currentPage.previousStepDisabled) { return; } currentPage.previousButtonClicked.emit(currentPage); if (!currentPage.preventDefault) { this.previous(); } }); this.nextButtonSubscription = buttonService.nextBtnClicked.subscribe(() => { this.checkAndCommitCurrentPage('next'); }); this.dangerButtonSubscription = buttonService.dangerBtnClicked.subscribe(() => { this.checkAndCommitCurrentPage('danger'); }); this.finishButtonSubscription = buttonService.finishBtnClicked.subscribe(() => { this.checkAndCommitCurrentPage('finish'); }); this.customButtonSubscription = buttonService.customBtnClicked.subscribe((type) => { if (!this.wizardStopNavigation) { this.currentPage.customButtonClicked.emit(type); } }); this.cancelButtonSubscription = buttonService.cancelBtnClicked.subscribe(() => { if (this.wizardStopNavigation) { return; } if (this.currentPage.preventDefault) { this.currentPage.pageOnCancel.emit(this.currentPage); } else { this.cancel(); } }); this.pagesResetSubscription = pageCollection.pagesReset.subscribe(() => { this.setFirstPageCurrent(); }); } /** * An Observable that is predominantly used amongst the subcomponents and services * of the wizard. It is recommended that users listen to the ClrWizardPage.onLoad * (clrWizardPageOnLoad) output instead of this Observable. * * @memberof WizardNavigationService */ get currentPageChange() { return this._currentChanged.asObservable(); } /** * @memberof WizardNavigationService */ get currentPageTitle() { // when the querylist of pages is empty. this is the first place it fails... if (!this.currentPage) { return null; } return this.currentPage.title; } /** * Returns a Boolean that tells you whether or not the current page is the first * page in the Wizard. * * This is helpful for determining whether a page is navigable. * * @memberof WizardNavigationService */ get currentPageIsFirst() { return this.pageCollection.firstPage === this.currentPage; } /** * Returns a Boolean that tells you whether or not the current page is the * last page in the Wizard. * * This is used to determine which buttons should display in the wizard footer. * * @memberof WizardNavigationService */ get currentPageIsLast() { return this.pageCollection.lastPage === this.currentPage; } /** * Returns the ClrWizardPage object of the current page or null. * * @memberof WizardNavigationService */ get currentPage() { if (!this._currentPage) { return null; } return this._currentPage; } /** * Accepts a ClrWizardPage object, since that object to be the current/active * page in the wizard, and emits the ClrWizardPage.onLoad (clrWizardPageOnLoad) * event for that page. * * Note that all of this work is bypassed if the ClrWizardPage object is already * the current page. * * @memberof WizardNavigationService */ set currentPage(page) { if (this._currentPage !== page && !this.wizardStopNavigation) { this._currentPage = page; page.onLoad.emit(page.id); this._currentChanged.next(page); } } /** * An observable used internally to alert the wizard that forward navigation * has occurred. It is recommended that you use the Wizard.onMoveNext * (clrWizardOnNext) output instead of this one. * * @memberof WizardNavigationService */ get movedToNextPage() { return this._movedToNextPage.asObservable(); } /** * An observable used internally to alert the wizard that the nav service * has approved completion of the wizard. * * It is recommended that you use the Wizard.wizardFinished (clrWizardOnFinish) * output instead of this one. * * @memberof WizardNavigationService */ get wizardFinished() { return this._wizardFinished.asObservable(); } /** * Notifies the wizard when backwards navigation has occurred via the * previous button. * * @memberof WizardNavigationService */ get movedToPreviousPage() { return this._movedToPreviousPage.asObservable(); } /** * Notifies the wizard that a user is trying to cancel it. * * @memberof WizardNavigationService */ get notifyWizardCancel() { return this._cancelWizard.asObservable(); } /** * * @memberof WizardNavigationService */ ngOnDestroy() { this.previousButtonSubscription.unsubscribe(); this.nextButtonSubscription.unsubscribe(); this.dangerButtonSubscription.unsubscribe(); this.finishButtonSubscription.unsubscribe(); this.customButtonSubscription.unsubscribe(); this.cancelButtonSubscription.unsubscribe(); this.pagesResetSubscription.unsubscribe(); } /** * This is a public function that can be used to programmatically advance * the user to the next page. * * When invoked, this method will move the wizard to the next page after * successful validation. Note that this method goes through all checks * and event emissions as if Wizard.next(false) had been called. * * In most cases, it makes more sense to use Wizard.next(false). * * @memberof WizardNavigationService */ next() { if (this.currentPageIsLast) { this.checkAndCommitCurrentPage('finish'); } else { this.checkAndCommitCurrentPage('next'); } } /** * Bypasses checks and most event emissions to force a page to navigate forward. * * Comparable to calling Wizard.next() or Wizard.forceNext(). * * @memberof WizardNavigationService */ forceNext() { const currentPage = this.currentPage; const nextPage = this.pageCollection.getNextPage(currentPage); // catch errant null or undefineds that creep in if (!nextPage) { throw new Error('The wizard has no next page to go to.'); } if (this.wizardStopNavigation) { return; } if (!currentPage.completed) { // this is a state that alt next flows can get themselves in... this.pageCollection.commitPage(currentPage); } this.currentPage = nextPage; } /** * Accepts a button/action type as a parameter. Encapsulates all logic for * event emissions, state of the current page, and wizard and page level overrides. * * Avoid calling this function directly unless you really know what you're doing. * * @memberof WizardNavigationService */ checkAndCommitCurrentPage(buttonType) { const currentPage = this.currentPage; if (!currentPage.readyToComplete || this.wizardStopNavigation) { return; } const iAmTheLastPage = this.currentPageIsLast; const isNext = buttonType === 'next'; const isDanger = buttonType === 'danger'; const isDangerNext = isDanger && !iAmTheLastPage; const isDangerFinish = isDanger && iAmTheLastPage; const isFinish = buttonType === 'finish' || isDangerFinish; if (isFinish && !iAmTheLastPage) { return; } currentPage.primaryButtonClicked.emit(buttonType); if (isFinish) { currentPage.finishButtonClicked.emit(currentPage); } else if (isDanger) { currentPage.dangerButtonClicked.emit(); } else if (isNext) { currentPage.nextButtonClicked.emit(); } if (currentPage.stopNext || currentPage.preventDefault) { currentPage.onCommit.emit(currentPage.id); return; } // order is very important with these emitters! if (isFinish) { // mark page as complete if (!this.wizardHasAltNext) { this.pageCollection.commitPage(currentPage); } this._wizardFinished.next(); } if (this.wizardHasAltNext) { this.pageCollection.commitPage(currentPage); if (isNext || isDangerNext) { this._movedToNextPage.next(true); } // jump out here, no matter what type we're looking at return; } if (isNext || isDangerNext) { this.forceNext(); } if (!this.wizardHasAltNext && !this.wizardStopNavigation) { this._movedToNextPage.next(true); } } /** * This is a public function that can be used to programmatically conclude * the wizard. * * When invoked, this method will initiate the work involved with finalizing * and finishing the wizard workflow. Note that this method goes through all * checks and event emissions as if Wizard.finish(false) had been called. * * In most cases, it makes more sense to use Wizard.finish(false). * * @memberof WizardNavigationService */ finish() { this.checkAndCommitCurrentPage('finish'); } /** * Programmatically moves the wizard to the page before the current page. * * In most instances, it makes more sense to call Wizard.previous() * which does the same thing. * * @memberof WizardNavigationService */ previous() { if (this.currentPageIsFirst || this.wizardStopNavigation) { return; } const previousPage = this.pageCollection.getPreviousPage(this.currentPage); if (!previousPage) { return; } this._movedToPreviousPage.next(true); if (this.forceForwardNavigation) { this.currentPage.completed = false; } this.currentPage = previousPage; } /** * Allows a hook into the cancel workflow of the wizard from the nav service. Note that * this route goes through all checks and event emissions as if a cancel button had * been clicked. * * In most cases, users looking for a hook into the cancel routine are actually looking * for a way to close the wizard from their host component because they have prevented * the default cancel action. * * In this instance, it is recommended that you use Wizard.close() to avoid any event * emission loop resulting from an event handler calling back into routine that will * again evoke the events it handles. * * @memberof WizardNavigationService */ cancel() { this._cancelWizard.next(); } /** * Performs all required checks to determine if a user can navigate to a page. Checking at each * point if a page is navigable -- completed where the page immediately after the last completed * page. * * Takes two parameters. The first one must be either the ClrWizardPage object or the ID of the * ClrWizardPage object that you want to make the current page. * * The second parameter is optional and is a Boolean flag for "lazy completion". What this means * is the Wizard will mark all pages between the current page and the page you want to navigate * to as completed. This is useful for informational wizards that do not require user action, * allowing an easy means for users to jump ahead. * * To avoid checks on navigation, use ClrWizardPage.makeCurrent() instead. * * @memberof WizardNavigationService */ goTo(pageToGoToOrId, lazyComplete = false) { const myPages = this.pageCollection; const pageToGoTo = typeof pageToGoToOrId === 'string' ? myPages.getPageById(pageToGoToOrId) : pageToGoToOrId; const currentPage = this.currentPage; // no point in going to the current page. you're there already! // also hard block on any navigation when stopNavigation is true if (pageToGoTo === currentPage || this.wizardStopNavigation) { return; } const currentPageIndex = myPages.getPageIndex(currentPage); const goToPageIndex = myPages.getPageIndex(pageToGoTo); const goingForward = goToPageIndex > currentPageIndex; const pagesToCheck = myPages.getPageRangeFromPages(this.currentPage, pageToGoTo); const okayToMove = lazyComplete || this.canGoTo(pagesToCheck); if (!okayToMove) { return; } if (goingForward && lazyComplete) { pagesToCheck.forEach((page) => { if (page !== pageToGoTo) { page.completed = true; } }); } else if (!goingForward && this.forceForwardNavigation) { pagesToCheck.forEach((page) => { page.completed = false; }); } this.currentPage = pageToGoTo; } /** * Accepts a range of ClrWizardPage objects as a parameter. Performs the work of checking * those objects to determine if navigation can be accomplished. * * @memberof WizardNavigationService */ canGoTo(pagesToCheck) { let okayToMove = true; const myPages = this.pageCollection; // previous page can be important when moving because if it's completed it // allows us to move to the page even if it's incomplete... let previousPagePasses; if (!pagesToCheck || pagesToCheck.length < 1) { return false; } pagesToCheck.forEach((page) => { if (!okayToMove) { return; } if (page.completed) { // default is true. just jump out instead of complicating it. return; } // so we know our page is not completed... const previousPage = myPages.getPageIndex(page) > 0 ? myPages.getPreviousPage(page) : null; previousPagePasses = previousPage === null || previousPage.completed === true; // we are false if not the current page AND previous page is not completed // (but must have a previous page) if (!page.current && !previousPagePasses) { okayToMove = false; } // falls through to true as default }); return okayToMove; } /** * Looks through the collection of pages to find the first one that is incomplete * and makes that page the current/active page. * * @memberof WizardNavigationService */ setLastEnabledPageCurrent() { const allPages = this.pageCollection.pagesAsArray; let lastCompletedPageIndex = null; allPages.forEach((page, index) => { if (page.completed) { lastCompletedPageIndex = index; } }); if (lastCompletedPageIndex === null) { // always is at least the first item... lastCompletedPageIndex = 0; } else if (lastCompletedPageIndex + 1 < allPages.length) { lastCompletedPageIndex = lastCompletedPageIndex + 1; } this.currentPage = allPages[lastCompletedPageIndex]; } /** * Finds the first page in the collection of pages and makes that page the * current/active page. * * @memberof WizardNavigationService */ setFirstPageCurrent() { this.currentPage = this.pageCollection.pagesAsArray[0]; } /** * Updates the stepnav on the left side of the wizard when pages are dynamically * added or removed from the collection of pages. * * @memberof WizardNavigationService */ updateNavigation() { let toSetCurrent; this.pageCollection.updateCompletedStates(); const currentPageRemoved = this.pageCollection.pagesAsArray.indexOf(this.currentPage) < 0; if (currentPageRemoved) { toSetCurrent = this.pageCollection.findFirstIncompletePage(); this.currentPage = toSetCurrent; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: WizardNavigationService, deps: [{ token: PageCollectionService }, { token: ButtonHubService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: WizardNavigationService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: WizardNavigationService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: PageCollectionService }, { type: ButtonHubService }] }); /* * Copyright (c) 2016-2026 Broadcom. All Rights Reserved. * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. * This software is released under MIT license. * The full license information can be found in LICENSE in the root directory of this project. */ class HeaderActionService { constructor(navService) { this.navService = navService; } get wizardHasHeaderActions() { const wizardHdrActions = this.wizardHeaderActions; if (!wizardHdrActions) { return false; } return wizardHdrActions.toArray().length > 0; } get currentPageHasHeaderActions() { return this.navService.currentPage ? this.navService.currentPage.hasHeaderActions : false; } get showWizardHeaderActions() { return !this.currentPageHasHeaderActions && this.wizardHasHeaderActions; } get displayHeaderActionsWrapper() { return this.currentPageHasHeaderActions || this.wizardHasHeaderActions; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: HeaderActionService, deps: [{ token: WizardNavigationService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: HeaderActionService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: HeaderActionService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: WizardNavigationService }] }); /* * Copyright (c) 2016-2026 Broadcom. All Rights Reserved. * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. * This software is released under MIT license. * The full license information can be found in LICENSE in the root directory of this project. */ const DEFAULT_BUTTON_TYPES = { cancel: 'cancel', previous: 'previous', next: 'next', finish: 'finish', danger: 'danger', }; const CUSTOM_BUTTON_TYPES = { cancel: 'custom-cancel', previous: 'custom-previous', next: 'custom-next', finish: 'custom-finish', danger: 'custom-danger', }; class ClrWizardButton { constructor(navService, buttonService) { this.navService = navService; this.buttonService = buttonService; this.type = ''; this.disabled = false; this.hidden = false; // EventEmitter which is emitted when a button is clicked. this.wasClicked = new EventEmitter(false); } get isCancel() { return this.checkDefaultAndCustomType(this.type, 'cancel'); } get isNext() { return this.checkDefaultAndCustomType(this.type, 'next'); } get isPrevious() { return this.checkDefaultAndCustomType(this.type, 'previous'); } get isFinish() { return this.checkDefaultAndCustomType(this.type, 'finish'); } get isDanger() { return this.checkDefaultAndCustomType(this.type, 'danger'); } get isPrimaryAction() { return this.isNext || this.isDanger || this.isFinish; } get _disabledAttribute() { if (this.isDisabled) { return ''; } return null; } get isDisabled() { // dealing with negatives here. cognitively easier to think of it like this... const disabled = true; const nav = this.navService; const page = this.navService.currentPage; // Ensure we don't change the response until buttons are ready to avoid chocolate if (!this.buttonService.buttonsReady) { return !disabled; } if (this.disabled || nav.wizardStopNavigation || !page) { return true; } if (this.isCancel) { return !disabled; } if (this.isPrevious && (nav.currentPageIsFirst || page.previousStepDisabled)) { return disabled; } if (this.isDanger && !page.readyToComplete) { return disabled; } if (this.isNext && (nav.currentPageIsLast || !page.readyToComplete)) { return disabled; } if (this.isFinish && (!nav.currentPageIsLast || !page.readyToComplete)) { return disabled; } return !disabled; } get isHidden() { // dealing with negatives here. cognitively easier to think of it like this... const hidden = true; const nav = this.navService; // Ensure we don't change the response until buttons are ready to avoid chocolate if (!this.buttonService.buttonsReady) { return !hidden; } if (this.hidden) { return true; } if (this.isCancel) { return !hidden; } if (this.isPrevious && nav.currentPageIsFirst) { return hidden; } if (this.isNext && nav.currentPageIsLast) { return hidden; } if (this.isFinish && !nav.currentPageIsLast) { return hidden; } return !hidden; } click() { if (this.isDisabled) { return; } this.wasClicked.emit(this.type); this.buttonService.buttonClicked(this.type); } checkDefaultAndCustomType(valueToCheck = '', typeToLookUp) { if (DEFAULT_BUTTON_TYPES[typeToLookUp] === valueToCheck) { return true; } if (CUSTOM_BUTTON_TYPES[typeToLookUp] === valueToCheck) { return true; } return false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ClrWizardButton, deps: [{ token: WizardNavigationService }, { token: ButtonHubService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: ClrWizardButton, isStandalone: false, selector: "clr-wizard-button", inputs: { type: "type", disabled: ["clrWizardButtonDisabled", "disabled"], hidden: ["clrWizardButtonHidden", "hidden"] }, outputs: { wasClicked: "clrWizardButtonClicked" }, host: { properties: { "attr.aria-hidden": "isHidden" }, classAttribute: "clr-wizard-btn-wrapper" }, ngImport: i0, template: ` <button type="button" class="btn clr-wizard-btn" [class.btn-link]="isCancel" [class.clr-wizard-btn--tertiary]="isCancel" [class.btn-outline]="isPrevious" [class.clr-wizard-btn--secondary]="isPrevious" [class.btn-primary]="isPrimaryAction" [class.clr-wizard-btn--primary]="isPrimaryAction" [class.btn-success]="isFinish" [class.btn-danger]="isDanger" [class.disabled]="isDisabled" [attr.disabled]="_disabledAttribute" (click)="click()" > <ng-content></ng-content> </button> `, isInline: true }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ClrWizardButton, decorators: [{ type: Component, args: [{ selector: 'clr-wizard-button', template: ` <button type="button" class="btn clr-wizard-btn" [class.btn-link]="isCancel" [class.clr-wizard-btn--tertiary]="isCancel" [class.btn-outline]="isPrevious" [class.clr-wizard-btn--secondary]="isPrevious" [class.btn-primary]="isPrimaryAction" [class.clr-wizard-btn--primary]="isPrimaryAction" [class.btn-success]="isFinish" [class.btn-danger]="isDanger" [class.disabled]="isDisabled" [attr.disabled]="_disabledAttribute" (click)="click()" > <ng-content></ng-content> </button> `, host: { class: 'clr-wizard-btn-wrapper', '[attr.aria-hidden]': 'isHidden' }, standalone: false, }] }], ctorParameters: () => [{ type: WizardNavigationService }, { type: ButtonHubService }], propDecorators: { type: [{ type: Input, args: ['type'] }], disabled: [{ type: Input, args: ['clrWizardButtonDisabled'] }], hidden: [{ type: Input, args: ['clrWizardButtonHidden'] }], wasClicked: [{ type: Output, args: ['clrWizardButtonClicked'] }] } }); /* * Copyright (c) 2016-2026 Broadcom. All Rights Reserved. * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. * This software is released under MIT license. * The full license information can be found in LICENSE in the root directory of this project. */ let wizardHeaderActionIndex = 0; class ClrWizardHeaderAction { constructor() { // title is explanatory text added to the header action this.title = ''; // If our host has an ID attribute, we use this instead of our index. this._id = (wizardHeaderActionIndex++).toString(); this.disabled = false; this.headerActionClicked = new EventEmitter(false); } get id() { return `clr-wizard-header-action-${this._id}`; } click() { if (this.disabled) { return; } // passing the header action id allows users to have one method that // routes to many different actions based on the type of header action // clicked. this is further aided by users being able to specify ids // for their header actions. this.headerActionClicked.emit(this._id); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ClrWizardHeaderAction, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.3", type: ClrWizardHeaderAction, isStandalone: false, selector: "clr-wizard-header-action", inputs: { title: "title", _id: ["id", "_id"], disabled: ["clrWizardHeaderActionDisabled", "disabled"] }, outputs: { headerActionClicked: "actionClicked" }, host: { classAttribute: "clr-wizard-header-action-wrapper" }, ngImport: i0, template: ` <button type="button" class="btn clr-wizard-header-action btn-link"