UNPKG

@hashicorp/design-system-components

Version:
194 lines (191 loc) 8 kB
import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import { schedule } from '@ember/runloop'; import { assert } from '@ember/debug'; import { modifier } from 'ember-modifier'; import { hash } from '@ember/helper'; import { and } from 'ember-truth-helpers'; import style from 'ember-style-modifier'; import { HdsStepperTitleTagValues } from '../types.js'; import HdsStepperNavStep from './step.js'; import HdsStepperNavPanel from './panel.js'; import { precompileTemplate } from '@ember/template-compilation'; import { setComponentTemplate } from '@ember/component'; import { g, i } from 'decorator-transforms/runtime'; /** * Copyright IBM Corp. 2021, 2025 * SPDX-License-Identifier: MPL-2.0 */ const STEP_ELEMENT_SELECTOR = '.hds-stepper-nav__step-content'; const PANEL_ELEMENT_SELECTOR = '.hds-stepper-nav__panel'; class HdsStepperNav extends Component { static { g(this.prototype, "_stepIds", [tracked], function () { return []; }); } #_stepIds = (i(this, "_stepIds"), void 0); static { g(this.prototype, "_stepNodes", [tracked], function () { return []; }); } #_stepNodes = (i(this, "_stepNodes"), void 0); static { g(this.prototype, "_panelNodes", [tracked], function () { return []; }); } #_panelNodes = (i(this, "_panelNodes"), void 0); static { g(this.prototype, "_panelIds", [tracked], function () { return []; }); } #_panelIds = (i(this, "_panelIds"), void 0); _element; _setUpStepperNav = modifier(element => { if (this.isInteractive) { assert('If @isInteractive is true, the number of Steps must be equal to the number of Panels', this._stepNodes.length === this._panelNodes.length); } this._element = element; return () => {}; }); get currentStep() { const { currentStep } = this.args; if (currentStep) { if (currentStep < 0) { return 0; } else { return currentStep; } } else { return 0; } } get isInteractive() { return this.args.isInteractive != undefined ? this.args.isInteractive : true; } get titleTag() { return this.args.titleTag ?? HdsStepperTitleTagValues.Div; } get inlineStyles() { const inlineStyles = {}; inlineStyles['--hds-stepper-nav-progress-bar-width'] = this.progressBarWidthStyle; return inlineStyles; } get progressBarWidthStyle() { let progressBarWidth = 0; let progressBarOffset = 0; if (this._stepIds.length != 0) { if (this.currentStep >= this._stepIds.length) { progressBarWidth = 100; progressBarOffset = 0; } else { const activeStepWidth = 1 / this._stepIds.length / 2; const width = this.currentStep / this._stepIds.length; progressBarWidth = (width + activeStepWidth) * 100; progressBarOffset = 16; } } return `calc(${progressBarWidth}% - ${progressBarOffset}px)`; } didInsertStep = () => { // eslint-disable-next-line ember/no-runloop schedule('afterRender', () => { this.updateSteps(); }); }; willDestroyStep = element => { // eslint-disable-next-line ember/no-runloop schedule('afterRender', () => { this._stepNodes = this._stepNodes.filter(node => node.id !== element.id); this._stepIds = this._stepIds.filter(stepId => stepId !== element.id); }); }; didInsertPanel = () => { // eslint-disable-next-line ember/no-runloop schedule('afterRender', () => { this.updatePanels(); }); }; willDestroyPanel = element => { // eslint-disable-next-line ember/no-runloop schedule('afterRender', () => { this._panelNodes = this._panelNodes.filter(node => node.id !== element.id); this._panelIds = this._panelIds.filter(panelId => panelId !== element.id); }); }; onKeyUp = (currentStepIndex, event) => { const leftArrow = 'ArrowLeft'; const rightArrow = 'ArrowRight'; if (event.key === rightArrow) { const nextStepIndex = this.findNextInteractiveStepIndex(currentStepIndex, 1); this.focusStep(nextStepIndex, event); } else if (event.key === leftArrow) { const prevStepIndex = this.findNextInteractiveStepIndex(currentStepIndex, this._stepIds.length - 1); this.focusStep(prevStepIndex, event); } }; // Update the step arrays based on how they are ordered in the DOM updateSteps = () => { const steps = this._element.querySelectorAll(STEP_ELEMENT_SELECTOR); let newStepIds = []; let newStepNodes = []; steps.forEach(step => { newStepIds = [...newStepIds, step.id]; newStepNodes = [...newStepNodes, step]; }); this._stepIds = newStepIds; this._stepNodes = newStepNodes; }; // Update the panel arrays based on how they are ordered in the DOM updatePanels = () => { const panels = this._element.querySelectorAll(PANEL_ELEMENT_SELECTOR); let newPanelIds = []; let newPanelNodes = []; panels.forEach(panel => { newPanelIds = [...newPanelIds, panel.id]; newPanelNodes = [...newPanelNodes, panel]; }); this._panelIds = newPanelIds; this._panelNodes = newPanelNodes; }; // Find the next interactive step to focus based on keyboard input findNextInteractiveStepIndex = (currentStepIndex, increment) => { let newStepIndex = (currentStepIndex + increment) % this._stepIds.length; while (newStepIndex > this.currentStep) { newStepIndex = (newStepIndex + increment) % this._stepIds.length; } return newStepIndex; }; // Focus step for keyboard & mouse nav focusStep = (stepIndex, event) => { event.preventDefault(); const step = this._stepNodes[stepIndex]; step?.focus(); }; get classNames() { const classes = ['hds-stepper-nav']; if (this.isInteractive) { classes.push('hds-stepper-nav--interactive'); } return classes.join(' '); } static { setComponentTemplate(precompileTemplate("<div class={{this.classNames}} ...attributes {{style this.inlineStyles}} {{this._setUpStepperNav}}>\n <div class=\"hds-stepper-nav__progress-bar\"></div>\n <ol class=\"hds-stepper-nav__list\" aria-label={{@ariaLabel}} role={{if this.isInteractive \"tablist\"}}>\n {{#if @steps}}\n {{#each @steps as |step|}}\n <HdsStepperNavStep @currentStep={{this.currentStep}} @isNavInteractive={{this.isInteractive}} @titleTag={{this.titleTag}} @didInsertNode={{this.didInsertStep}} @willDestroyNode={{this.willDestroyStep}} @stepIds={{this._stepIds}} @panelIds={{this._panelIds}} @onStepChange={{@onStepChange}} @onKeyUp={{this.onKeyUp}}>\n <:title>{{step.title}}</:title>\n <:description>{{step.description}}</:description>\n </HdsStepperNavStep>\n {{/each}}\n {{else}}\n {{yield (hash Step=(component HdsStepperNavStep currentStep=this.currentStep isNavInteractive=this.isInteractive titleTag=this.titleTag stepIds=this._stepIds panelIds=this._panelIds didInsertNode=this.didInsertStep willDestroyNode=this.willDestroyStep onStepChange=@onStepChange onKeyUp=this.onKeyUp))}}\n {{/if}}\n </ol>\n {{#if (and @steps (has-block \"body\"))}}\n {{#each @steps}}\n <HdsStepperNavPanel @currentStep={{this.currentStep}} @isNavInteractive={{this.isInteractive}} @stepIds={{this._stepIds}} @panelIds={{this._panelIds}} @didInsertNode={{this.didInsertPanel}} @willDestroyNode={{this.willDestroyPanel}}>\n {{yield to=\"body\"}}\n </HdsStepperNavPanel>\n {{/each}}\n {{else}}\n {{yield (hash Panel=(component HdsStepperNavPanel currentStep=this.currentStep isNavInteractive=this.isInteractive stepIds=this._stepIds panelIds=this._panelIds didInsertNode=this.didInsertPanel willDestroyNode=this.willDestroyPanel))}}\n {{/if}}\n</div>", { strictMode: true, scope: () => ({ style, HdsStepperNavStep, hash, and, HdsStepperNavPanel }) }), this); } } export { HdsStepperNav as default }; //# sourceMappingURL=index.js.map