UNPKG

@atlassian/aui

Version:

Atlassian User Interface library

370 lines (323 loc) 13.4 kB
import $ from './jquery'; import * as deprecate from './internal/deprecation'; import * as logger from './internal/log'; import globalize from './internal/globalize'; import escapeHtml from './escape-html'; import keyCode from './key-code'; import skate from './internal/skate'; import { CLOSE_BUTTON, CLOSE_BUTTON_CLASS_SELECTOR } from './close-button'; import { I18n } from './i18n'; const DEFAULT_FADEOUT_DURATION = 500; const DEFAULT_FADEOUT_DELAY = 5000; const FADEOUT_RESTORE_DURATION = 100; function createMessageConstructor(type) { /** * * @param context * @param {Object} obj - message configuration * @param {String} [obj.id] - ID to add to the message. * @param {String} [obj.title] - Plain-text title of the message. If provided, will appear above the message body. * @param {String} [obj.a11yTypeLabel] - Accessibility label that will communicate type of message non-visually * @param {String} obj.body - Content of the message. Can be HTML content. * @param {boolean} [obj.closeable] - If true, the message can be manually closed by the end-user via the UI. * @param {boolean} [obj.removeOnHide] - If true, the message will be removed from the DOM after hide. * @param {boolean} [obj.fadeout] * @param {boolean} [obj.duration] * @param {boolean} [obj.delay] * @returns {*|HTMLElement} */ this[type] = function (context, obj) { if (!obj) { obj = context; context = '#aui-message-bar'; } // Set up our template options obj.closeable = obj.closeable !== null && obj.closeable !== false; // clean the title value obj.title = (obj.title || '').toString().trim(); let $message = renderMessageElement(obj, type); insertMessageIntoContext($message, obj.insert, context); // Attach the optional extra behaviours if (obj.removeOnHide) { console.warn && console.warn( 'Use of AUI Message `removeOnHide` is deprecated due to accessibility reasons and will be removed in AUI 10.0.0.' ); makeRemoveOnHide($message, obj.delay, obj.duration); } if (obj.closeable) { makeCloseable($message); } if (obj.fadeout) { console.warn && console.warn( 'Use of AUI Message `fadeout` is deprecated due to accessibility reasons and will be removed in AUI 10.0.0.' ); makeFadeout($message, obj.delay, obj.duration); } return $message; }; } function makeRemoveOnHide(message, delay, duration) { $(message || '.aui-message.aui-remove-on-hide').each(function () { const $this = $(this); makeFadeout($this, delay, duration); }); } function makeCloseable(message) { $(message || 'div.aui-message.closeable').each(function () { const $this = $(this); const $closeIcons = $this.find(CLOSE_BUTTON_CLASS_SELECTOR); const $icon = $closeIcons.length > 0 ? $closeIcons.first() : $(CLOSE_BUTTON); $this.addClass('closeable'); $this.append($icon); initCloseMessageBoxOnClickAndKeypress($this); }); } function makeFadeout(message, delay, duration) { delay = typeof delay !== 'undefined' ? delay : DEFAULT_FADEOUT_DELAY; duration = typeof duration !== 'undefined' ? duration : DEFAULT_FADEOUT_DURATION; $(message || 'div.aui-message.fadeout').each(function () { const $this = $(this); //Store the component state to avoid collisions between animations let hasFocus = false; let isHover = false; //Small functions to keep the code easier to read and avoid code duplication function fadeOut() { //Algorithm: //1. Stop all running animations (first arg), including any fade animation and delay // Do not jump to the end of the animation (second arg). This prevents the message to abruptly // jump to opacity:0 or opacity:1 //2. Wait <delay> ms before starting the fadeout //3. Start the fadeout with a duration of <duration> ms //4. Close the message at the end of the animation $this .stop(true, false) .delay(delay) .fadeOut(duration, function () { $this.closeMessage(); }); } function resetFadeOut() { //Algorithm: //1. Stop all running animations (first arg), including any fade animation and delay // Do not jump to the end of the animation (second arg). This prevents the message to abruptly // jump to opacity:0 or opacity:1 //2. Fast animation to opacity:1 $this.stop(true, false).fadeTo(FADEOUT_RESTORE_DURATION, 1); } function shouldStartFadeOut() { return !hasFocus && !isHover; } //Attach handlers for user interactions (focus and hover) $this .focusin(function () { hasFocus = true; resetFadeOut(); }) .focusout(function () { hasFocus = false; if (shouldStartFadeOut()) { fadeOut(); } }) .hover( function () { //should be called .hoverin(), but jQuery does not implement that method isHover = true; resetFadeOut(); }, function () { //should be called .hoverout(), but jQuery does not implement that method isHover = false; if (shouldStartFadeOut()) { fadeOut(); } } ); //Initial animation fadeOut(); }); } const messageFunctions = { setup: function () { makeRemoveOnHide(); makeCloseable(); makeFadeout(); }, makeRemoveOnHide: makeRemoveOnHide, makeCloseable: makeCloseable, makeFadeout: makeFadeout, createMessage: createMessageConstructor, }; function initCloseMessageBoxOnClickAndKeypress($message) { $message.unbind('click.aui-message').unbind('keydown.aui-message'); $message .on('click.aui-message', CLOSE_BUTTON_CLASS_SELECTOR, function (e) { $(e.target).closest('.aui-message').closeMessage(); }) .on('keydown.aui-message', CLOSE_BUTTON_CLASS_SELECTOR, function (e) { if (e.which === keyCode.ENTER || e.which === keyCode.SPACE) { $(e.target).closest('.aui-message').closeMessage(); e.preventDefault(); // this is especially important when handling the space bar, as we don't want to page down } }); } function insertMessageIntoContext($message, insertWhere, context) { if (insertWhere === 'prepend') { $message.prependTo(context); } else if (insertWhere === 'before') { $message.insertBefore(context); } else if (insertWhere === 'after') { $message.insertAfter(context); } else { $message.appendTo(context); } } function getMessageA11yTypeText(obj, type) { const { a11yTypeLabel } = obj; if (a11yTypeLabel === '') { return ''; } const isA11yTypeLabelNotEmpty = a11yTypeLabel != null; if (isA11yTypeLabelNotEmpty) { return a11yTypeLabel; } const isA11yTypeLabelNotPassed = a11yTypeLabel == null; if (isA11yTypeLabelNotPassed) { switch (type) { case 'error': return I18n.getText('aui.messagecomponent.error.label'); case 'warning': return I18n.getText('aui.messagecomponent.warning.label'); case 'info': return I18n.getText('aui.messagecomponent.info.label'); case 'confirmation': return I18n.getText('aui.messagecomponent.confirmation.label'); case 'change': return I18n.getText('aui.messagecomponent.change.label'); case 'generic': case 'success': case 'hint': return ''; default: throw Error(`Provide \`a11yTypeLabel\` for AUI message of custom type ${type}`); } } } function renderMessageElement(obj, type) { const { id, closeable, removeOnHide, fadeout, title, body } = obj; const messageTypeText = getMessageA11yTypeText(obj, type); const messageTypeLabel = messageTypeText ? `<strong hidden>${messageTypeText}: </strong>` : ''; // Convert the options in to template values const titleId = id ? `${id}-title` : ''; const titleHtml = title ? `<p class="title" ${titleId ? `id="${titleId}" aria-hidden="true"` : ''}>${messageTypeLabel}<strong>${escapeHtml(title)}</strong></p>` : ''; const html = `<div class="aui-message" role="note" ${titleId ? `aria-labelledby="${titleId}"` : ''}>${titleHtml}</div>`; // Construct the message element const $message = $(html) .append($.parseHTML(body || '')) .addClass(removeOnHide ? 'aui-remove-on-hide' : '') .addClass(closeable ? 'closeable' : '') .addClass(fadeout ? 'fadeout' : '') .addClass(`aui-message-${type}`); // Add ID if supplied if (id) { if (/[#'".\s]/g.test(id)) { // reject IDs that don't comply with style guide (ie. they'll break stuff) logger.warn( 'Messages error: ID rejected, must not include spaces, hashes, dots or quotes.' ); } else { $message.attr('id', id); } } return $message; } $.fn.closeMessage = function () { const $message = $(this); if ( $message.hasClass('aui-message') && ($message.hasClass('closeable') || $message.hasClass('aui-remove-on-hide')) ) { $message.stop(true); //Stop any running animation $message.trigger('messageClose', [this]); //messageClose event Deprecated as of 5.3 $message.remove(); $(document).trigger('aui-message-close', [this]); //must trigger on document since the element has been removed } }; /** * Utility methods to display different message types to the user. * Usage: * <pre> * messages.info("#container", { * title: "Info", * body: "You can choose to have messages without Close functionality.", * closeable: false, * }); * </pre> */ messageFunctions.createMessage('generic'); //Deprecated Oct 2017 messageFunctions.createMessage('error'); messageFunctions.createMessage('warning'); messageFunctions.createMessage('info'); messageFunctions.createMessage('confirmation'); messageFunctions.createMessage('change'); messageFunctions.createMessage('success'); //Deprecated Oct 2017 messageFunctions.createMessage('hint'); //Deprecated Oct 2017 const MessageEl = skate('aui-message', { created: function (element) { const body = element.innerHTML; const type = element.getAttribute('type') || 'info'; element.innerHTML = ''; messageFunctions[type](element, { body: body, id: element.getAttribute('id'), a11yTypeLabel: element.getAttribute('a11yTypeLabel'), removeOnHide: element.getAttribute('removeOnHide'), closeable: element.getAttribute('closeable'), delay: element.getAttribute('delay'), duration: element.getAttribute('duration'), fadeout: element.getAttribute('fadeout'), title: element.getAttribute('title'), }); element.setAttribute('role', 'none'); }, }); $(function () { messageFunctions.setup(); }); deprecate.prop(messageFunctions, 'makeCloseable', { extraInfo: 'Use the "closeable" option in the constructor instead. Docs: https://aui.atlassian.com/latest/docs/messages.html', }); deprecate.prop(messageFunctions, 'createMessage', { extraInfo: 'Use the provided convenience methods instead e.g. messages.info(). Docs: https://aui.atlassian.com/latest/docs/messages.html', }); deprecate.prop(messageFunctions, 'makeRemoveOnHide', { extraInfo: 'Use of the `makeRemoveOnHide` option is deprecated due to accessibility reasons. Docs: https://aui.atlassian.com/latest/docs/messages.html', }); deprecate.prop(messageFunctions, 'makeFadeout', { extraInfo: 'Use of the `fadeout` option is deprecated due to accessibility reasons. Docs: https://aui.atlassian.com/latest/docs/messages.html', }); deprecate.prop(messageFunctions, 'generic', { extraInfo: 'use the messages.info() method instead. Docs: https://aui.atlassian.com/latest/docs/messages.html', }); deprecate.prop(messageFunctions, 'hint', { extraInfo: 'use the messages.info() method instead. Docs: https://aui.atlassian.com/latest/docs/messages.html', }); deprecate.prop(messageFunctions, 'success', { extraInfo: 'use the messages.confirmation() method instead. Docs: https://aui.atlassian.com/latest/docs/messages.html', }); // Exporting // --------- globalize('messages', messageFunctions); export default messageFunctions; export { MessageEl };