UNPKG

@atlassian/aui

Version:

Atlassian User Interface library

268 lines (234 loc) 8.59 kB
import $ from './jquery'; import { defaults, isNumber, isFinite, isNaN } from 'underscore'; import skate from './internal/skate'; import { recomputeStyle } from './internal/animation'; import * as deprecate from './internal/deprecation'; import globalize from './internal/globalize'; const afterTransitionEvent = 'aui-progress-indicator-after-update'; const beforeTransitionEvent = 'aui-progress-indicator-before-update'; const transitionEnd = 'transitionend webkitTransitionEnd'; function updateProgress($progressBar, $progressBarContainer, progressValue) { recomputeStyle($progressBar); $progressBar.css('width', progressValue * 100 + '%'); $progressBarContainer.attr('data-value', progressValue); } function updateProgressElement(element, value) { if (typeof element === 'string') { let el = document.getElementById(element); if (el) { element = el; } } var $progressBarContainer = $(element).first(); var $progressBar = $progressBarContainer.children('.aui-progress-indicator-value'); var valueAttribute = $progressBarContainer.attr('data-value'); var isIndeterminate = !valueAttribute; var currentProgress = parseFloat(valueAttribute) || 0; var isProgressNotChanged = valueAttribute && currentProgress === value; // AUI-4771 - check for mis-configured legacy progress bar HTML. if (isProgressNotChanged) { const currentDemonstratedValue = parseFloat($progressBar.get(0).style.width) || 0; isProgressNotChanged = currentProgress === currentDemonstratedValue * 100; } if (isProgressNotChanged) { return; } //if the progress bar is indeterminate switch it. if (isIndeterminate) { $progressBar.css('width', 0); } transitionProgress($progressBar, $progressBarContainer, { currentProgress, value }); return $progressBarContainer; } function transitionProgress(progressBar, progressBarContainer, { currentProgress, value }) { const $progressBar = $(progressBar); const $progressBarContainer = $(progressBarContainer); if (typeof value === 'number' && value <= 1 && value >= 0) { $progressBarContainer.trigger(beforeTransitionEvent, [currentProgress, value]); //trigger the event after transition end if supported, otherwise just trigger it $progressBar.one(transitionEnd, function () { $progressBarContainer.trigger(afterTransitionEvent, [currentProgress, value]); }); updateProgress($progressBar, $progressBarContainer, value); } } function setIndeterminateOnProgressElement(element) { var $progressBarContainer = $(element).first(); var $progressBar = $progressBarContainer.children('.aui-progress-indicator-value'); $progressBarContainer.removeAttr('data-value'); recomputeStyle($progressBarContainer); $progressBar.css('width', ''); } const DEFAULTS = { indeterminate: false, max: 1, val: 0, }; function validNumeric(val) { return isNumber(val) && isFinite(val) && !isNaN(val); } function parseNumeric(val, defaultVal = 1) { const num = parseFloat(val); return validNumeric(num) ? num : Number(defaultVal); } function parseDecimal(num, precision = 1) { return Number(parseFloat(num).toFixed(precision)); } function safeValue(val, max) { return Math.max(0, Math.min(val, max)); } function safeMax(max) { return max > 0 ? max : DEFAULTS.max; } /** * @param data the state * @returns {{max: number, val: number, valAsFraction: number, valAsPercent: number}} */ function calc(data) { const { val, max } = data; const parsedMax = safeMax(max); const parsedVal = safeValue(val, parsedMax); const valAsFraction = parseDecimal(parsedVal / parsedMax, 6); const valAsPercent = parseDecimal(valAsFraction * 100, 2); return { max: parsedMax, val: parsedVal, valAsFraction, valAsPercent }; } function refresh(el) { const { val, valAsFraction, max } = calc(el._data); const bar = el.querySelector('.aui-progress-indicator'); const oldVal = bar.getAttribute('data-value'); if (el.indeterminate) { bar.removeAttribute('aria-valuenow'); setIndeterminateOnProgressElement(bar); } else { bar.setAttribute('aria-valuenow', val); bar.setAttribute('aria-valuemax', max); transitionProgress(bar.querySelector('.aui-progress-indicator-value'), bar, { currentProgress: oldVal, value: valAsFraction, }); } } function setValue(el, data) { el._data.val = parseNumeric(data.newValue, data.oldValue || DEFAULTS.val); refresh(el); } function setMax(el, data) { el._data.max = parseNumeric(data.newValue, data.oldValue || DEFAULTS.max); refresh(el); } const ProgressBarEl = skate('aui-progressbar', { template(el) { // Ensure max is set before value upon element creation and before rendering. // Why is this happening in 'template' and not 'created'? Because it gets called before 'created'! el._data.max = parseNumeric(el.getAttribute('max'), DEFAULTS.max); el._data.val = parseNumeric(el.getAttribute('value'), DEFAULTS.val); el._data.indeterminate = el.hasAttribute('indeterminate'); const { val, max, valAsFraction, valAsPercent } = calc(el._data); const legacyValue = el._data.indeterminate ? '' : `data-value="${valAsFraction}"`; el.innerHTML = `<div class="aui-progress-indicator" ${legacyValue} role="progressbar" aria-valuemin="0" aria-valuenow="${val}" aria-valuemax="${max}" tabindex="0" > <span class="aui-progress-indicator-value" style="width: ${valAsPercent}%"></span> </div>`; }, attached(el) { refresh(el); }, attributes: { indeterminate: { created: function (el) { el.indeterminate = true; }, removed: function (el) { el.indeterminate = false; }, }, value: { value: DEFAULTS.val, fallback: function (el, data) { if (el._updating) { return false; } setValue(el, data); }, }, max: { value: DEFAULTS.max, fallback: function (el, data) { if (el._updating) { return false; } setMax(el, data); }, }, }, prototype: { get _data() { return this.__data || (this._data = defaults({}, DEFAULTS)); }, set _data(d) { return (this.__data = d); }, get indeterminate() { return this._data.indeterminate; }, set indeterminate(val) { this._data.indeterminate = !!val; refresh(this); }, get value() { const { val } = calc(this._data); return val; }, set value(num) { if (!validNumeric(num)) { return false; } const data = { newValue: parseDecimal(num, 6) }; this._updating = true; // Reflect whatever value is set in the attributes. this.setAttribute('value', data.newValue); this._updating = false; setValue(this, data); }, get max() { const { max } = calc(this._data); return max; }, set max(num) { if (!validNumeric(num)) { return false; } const data = { newValue: parseDecimal(num, 6) }; this._updating = true; // Reflect whatever value is set in the attributes. this.setAttribute('max', data.newValue); this._updating = false; setMax(this, data); }, }, }); const progressBars = { update: deprecate.fn(updateProgressElement, 'AJS.progressBars.update', { sinceVersion: '7.7.0', removeInVersion: '10.0.0', extraInfo: 'Use the <aui-progressbar> web component instead', }), setIndeterminate: deprecate.fn( setIndeterminateOnProgressElement, 'AJS.progressBars.setIndeterminate', { sinceVersion: '7.7.0', removeInVersion: '10.0.0', extraInfo: 'Use the <aui-progressbar> web component instead', } ), }; globalize('progressBars', progressBars); export default progressBars; export { ProgressBarEl };