UNPKG

@atlassian/aui

Version:

Atlassian User Interface library

274 lines (253 loc) 9.48 kB
import './spin'; import './tooltip'; import $ from './jquery'; import { setBooleanAttribute } from './internal/attributes'; import { supportsFocusWithin } from './internal/browser'; 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'; import SpinnerEl, {SIZE} from './spinner'; import { I18n } from './i18n' 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 getSpinner (element) { return element._spinner || (element._spinner = new SpinnerEl()); } 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) { const val = `${change.newValue}${INPUT_SUFFIX}`; getInput(element).setAttribute('id', val); } }; var valueAttributeHandler = { removed: function(element) { removedAttributeHandler.call(this, 'value', element); // Internet Explorer 11 has a bug where it doesn't clear out the previous value // when the attribute is removed. getInput(element).value = 'on'; }, fallback: function (element, change) { fallbackAttributeHandler.call(this, 'value', element, change); } }; var checkedAttributeHandler = { removed: function (element) { getInput(element).checked = false; }, fallback: function (element) { getInput(element).checked = true; } }; 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) { var input = getInput(element); if (!element.disabled && !element.busy && e.target !== input) { input.checked = !input.checked; fireChangeEvent(element); } setBooleanAttribute(element, 'checked', input.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) { getInput(element).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) { getInput(element).addEventListener('click', function (e) { if (element.busy) { e.preventDefault(); } }); } // support focus-within manually when necessary if (!supportsFocusWithin()) { element._input.addEventListener('focus', () => element.classList.add('active')); element._input.addEventListener('blur', () => element.classList.remove('active')); } } const ToggleEl = 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) { getInput(element); // avoid using _input in attribute handlers getSpinner(element).setAttribute('size', SIZE.SMALL.name); $(getInput(element)).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: valueAttributeHandler, 'tooltip-on': { value: I18n.getText('aui.toggle.on'), fallback: function (element, change) { getInput(element).setAttribute('tooltip-on', change.newValue || I18n.getText('aui.toggle.on')); } }, 'tooltip-off': { value: I18n.getText('aui.toggle.off'), fallback: function (element, change) { getInput(element).setAttribute('tooltip-off', change.newValue || I18n.getText('aui.toggle.off')); } }, label: labelHandler }, prototype: { focus: function () { getInput(this).focus(); return this; }, get checked () { return getInput(this).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 (getInput(this).checked !== value) { getInput(this).checked = value; setBooleanAttribute(this, 'checked', value); } }, get disabled () { // AUI-4958 - this may be accessed by a jQuery event handler in response to // a DOMNodeInserted event being fired. In this scenario, the `template` // function has been called by skate, but the `created` callback has not. return getInput(this).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 getInput(this).name; }, set name (value) { this.setAttribute('name', value); return value; }, get value () { return getInput(this).value; }, set value (value) { // Setting the value of an input to null sets it to empty string. let newVal = value === null ? '' : value; this.setAttribute('value', newVal); return newVal; }, get busy () { return getInput(this).getAttribute('aria-busy') === 'true'; }, set busy (value) { const input = getInput(this); const spinner = getSpinner(this); setBooleanAttribute(this, 'busy', value); if (value) { input.setAttribute('aria-busy', 'true'); input.indeterminate = true; if (this.checked) { input.classList.add('indeterminate-checked'); $(this.querySelector('.aui-toggle-tick')).append(spinner); } else { $(this.querySelector('.aui-toggle-cross')).append(spinner); } } else { input.classList.remove('indeterminate-checked'); input.indeterminate = false; input.removeAttribute('aria-busy'); if (spinner.parentNode) { spinner.parentNode.removeChild(this._spinner); } } setDisabledForLabels(this, !!value); return value; } } }); export default ToggleEl;