@atlassian/aui
Version:
Atlassian User Interface Framework
241 lines (224 loc) • 8.19 kB
JavaScript
'use strict';
import './spin';
import './tooltip';
import { setBooleanAttribute } from './internal/attributes';
import $ from './jquery';
import enforce from './internal/enforcer';
import keyCode from './key-code';
import skateTemplateHtml from 'skatejs-template-html';
import skate from './internal/skate';
import { INPUT_SUFFIX } from './internal/constants';
import CustomEvent from './polyfills/custom-event';
function fireChangeEvent(element) {
if (element._canFireEventsNow) {
element.dispatchEvent(new CustomEvent('change', { bubbles: true }));
}
}
function getInput (element) {
return element._input || (element._input = element.querySelector('input'));
}
function removedAttributeHandler(attributeName, element) {
getInput(element).removeAttribute(attributeName);
}
function fallbackAttributeHandler(attributeName, element, change) {
getInput(element).setAttribute(attributeName, change.newValue);
}
function getAttributeHandler (attributeName) {
return {
removed: removedAttributeHandler.bind(this, attributeName),
fallback: fallbackAttributeHandler.bind(this, attributeName)
};
}
const formAttributeHandler = {
removed: function (element) {
removedAttributeHandler.call(this, 'form', element);
element._formId = null;
},
fallback: function (element, change) {
fallbackAttributeHandler.call(this, 'form', element, change);
element._formId = change.newValue;
}
};
var idAttributeHandler = {
removed: removedAttributeHandler.bind(this, 'id'),
fallback: function (element, change) {
getInput(element).setAttribute('id', `${change.newValue}${INPUT_SUFFIX}`);
}
};
var checkedAttributeHandler = {
removed: function (element) {
getInput(element).checked = false;
fireChangeEvent(element);
},
fallback: function (element) {
getInput(element).checked = true;
fireChangeEvent(element);
}
};
var labelHandler = {
removed: function (element) {
getInput(element).removeAttribute('aria-label');
},
fallback: function (element, change) {
getInput(element).setAttribute('aria-label', change.newValue);
}
};
function clickHandler(element, e) {
if (!element.disabled && !element.busy && e.target !== element._input) {
element._input.checked = !element._input.checked;
}
setBooleanAttribute(element, 'checked', getInput(element).checked);
}
function setDisabledForLabels(element, disabled) {
if (!element.id) {
return;
}
Array.prototype.forEach.call(document.querySelectorAll(`aui-label[for="${element.id}"]`), function (el) {
el.disabled = disabled;
});
}
/**
* Workaround to prevent pressing SPACE on busy state.
* Preventing click event still makes the toggle flip and revert back.
* So on CSS side, the input has "pointer-events: none" on busy state.
*/
function bindEventsToInput(element) {
element._input.addEventListener('keydown', function (e) {
if (element.busy && e.keyCode === keyCode.SPACE) {
e.preventDefault();
}
});
// prevent toggle can be trigger through SPACE key on Firefox
if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
element._input.addEventListener('click', function (e) {
if (element.busy) {
e.preventDefault();
}
});
}
}
skate('aui-toggle', {
// "assistive" class avoids direct interaction with the <input> element
// (which prevents our click handler from being called),
// while allow the element to still participate in the form.
template: skateTemplateHtml(
'<input type="checkbox" class="aui-toggle-input assistive">',
'<span class="aui-toggle-view">',
'<span class="aui-toggle-tick aui-icon aui-icon-small aui-iconfont-success"></span>',
'<span class="aui-toggle-cross aui-icon aui-icon-small aui-iconfont-close-dialog"></span>',
'</span>'
),
created: function (element) {
element._input = getInput(element); // avoid using _input in attribute handlers
element._tick = element.querySelector('.aui-toggle-tick');
element._cross = element.querySelector('.aui-toggle-cross');
$(element._input).tooltip({
title: function () {
return this.checked ? this.getAttribute('tooltip-on') : this.getAttribute('tooltip-off');
},
gravity: 's',
hoverable: false
});
bindEventsToInput(element);
if (element.hasAttribute('checked')) {
getInput(element).setAttribute('checked', '');
}
element._canFireEventsNow = true;
},
attached: function (element) {
enforce(element).attributeExists('label');
},
events: {
click: clickHandler
},
attributes: {
id: idAttributeHandler,
checked: checkedAttributeHandler,
disabled: getAttributeHandler('disabled'),
form: formAttributeHandler,
name: getAttributeHandler('name'),
value: getAttributeHandler('value'),
'tooltip-on': {
value: AJS.I18n.getText('aui.toggle.on'),
fallback: function (element, change) {
getInput(element).setAttribute('tooltip-on', change.newValue || AJS.I18n.getText('aui.toggle.on'));
}
},
'tooltip-off': {
value: AJS.I18n.getText('aui.toggle.off'),
fallback: function (element, change) {
getInput(element).setAttribute('tooltip-off', change.newValue || AJS.I18n.getText('aui.toggle.off'));
}
},
label: labelHandler
},
prototype: {
focus: function () {
this._input.focus();
return this;
},
get checked () {
return this._input.checked;
},
set checked (value) {
// Need to explicitly set the property on the checkbox because the
// checkbox's property doesn't change with it's attribute after it
// is clicked.
if (this._input.checked !== value) {
this._input.checked = value;
setBooleanAttribute(this, 'checked', value);
}
},
get disabled () {
return this._input.disabled;
},
set disabled (value) {
return setBooleanAttribute(this, 'disabled', value);
},
get form () {
return document.getElementById(this._formId);
},
set form (value) {
formAttributeHandler.fallback.call(this, this, { newValue: value || null });
return this.form;
},
get name () {
return this._input.name;
},
set name (value) {
this.setAttribute('name', value);
return value;
},
get value () {
return this._input.value;
},
set value (value) { // Setting the value of an input to null sets it to empty string.
this.setAttribute('value', value === null ? '' : value);
return value;
},
get busy () {
return this._input.getAttribute('aria-busy') === 'true';
},
set busy (value) {
setBooleanAttribute(this, 'busy', value);
if (value) {
this._input.setAttribute('aria-busy', 'true');
this._input.indeterminate = true;
if (this.checked) {
$(this._input).addClass('indeterminate-checked');
$(this._tick).spin({zIndex: null});
} else {
$(this._cross).spin({zIndex: null, color: 'black'});
}
} else {
$(this._input).removeClass('indeterminate-checked');
this._input.indeterminate = false;
this._input.removeAttribute('aria-busy');
$(this._cross).spinStop();
$(this._tick).spinStop();
}
setDisabledForLabels(this, !!value);
return value;
}
}
});