@atlassian/aui
Version:
Atlassian User Interface library
268 lines (234 loc) • 8.59 kB
JavaScript
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 };