carbon-components
Version:
The Carbon Design System is IBM’s open-source design system for products and experiences.
132 lines (120 loc) • 4.78 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
export default function (ToMix) {
/**
* Mix-in class to manage events associated with states.
* @class EventedState
*/
class EventedState extends ToMix {
/* eslint-disable jsdoc/check-param-names */
/**
* The internal implementation for {@link EventedState#changeState `.changeState()`}, performing actual change in state.
* @param {string} [state] The new state. Can be an omitted, which means toggling.
* @param {object} [detail]
* The object that should be put to event details that is fired before/after changing state.
* Can have a `group` property, which specifies what state to be changed.
* @param {EventedState~changeStateCallback} callback The callback called once changing state is finished or is canceled.
* @private
*/
_changeState() {
throw new Error(
'_changeState() should be overridden to perform actual change in state.'
);
}
/**
* Changes the state of this component.
* @param {string} [state] The new state. Can be an omitted, which means toggling.
* @param {object} [detail]
* The object that should be put to event details that is fired before/after changing state.
* Can have a `group` property, which specifies what state to be changed.
* @param {EventedState~changeStateCallback} [callback] The callback called once changing state is finished or is canceled.
*/
changeState(...args) {
const state = typeof args[0] === 'string' ? args.shift() : undefined;
const detail =
Object(args[0]) === args[0] && typeof args[0] !== 'function'
? args.shift()
: undefined;
const callback = typeof args[0] === 'function' ? args.shift() : undefined;
if (
typeof this.shouldStateBeChanged === 'function' &&
!this.shouldStateBeChanged(state, detail)
) {
if (callback) {
callback(null, true);
}
return;
}
const data = {
group: detail && detail.group,
state,
};
const eventNameSuffix = [data.group, state]
.filter(Boolean)
.join('-')
.split('-') // Group or state may contain hyphen
.map((item) => item[0].toUpperCase() + item.substr(1))
.join('');
const eventStart = new CustomEvent(
this.options[`eventBefore${eventNameSuffix}`],
{
bubbles: true,
cancelable: true,
detail,
}
);
const fireOnNode = (detail && detail.delegatorNode) || this.element;
const canceled = !fireOnNode.dispatchEvent(eventStart);
if (canceled) {
if (callback) {
const error = new Error(
`Changing state (${JSON.stringify(data)}) has been canceled.`
);
error.canceled = true;
callback(error);
}
} else {
const changeStateArgs = [state, detail].filter(Boolean);
this._changeState(...changeStateArgs, () => {
fireOnNode.dispatchEvent(
new CustomEvent(this.options[`eventAfter${eventNameSuffix}`], {
bubbles: true,
cancelable: true,
detail,
})
);
if (callback) {
callback();
}
});
}
}
/* eslint-enable jsdoc/check-param-names */
/**
* Tests if change in state should happen or not.
* Classes inheriting {@link EventedState `EventedState`} should override this function.
* @function EventedState#shouldStateBeChanged
* @param {string} [state] The new state. Can be an omitted, which means toggling.
* @param {object} [detail]
* The object that should be put to event details that is fired before/after changing state.
* Can have a `group` property, which specifies what state to be changed.
* @returns {boolean}
* `false` if change in state shouldn't happen, e.g. when the given new state is the same as the current one.
*/
}
/**
* The callback called once changing state is finished or is canceled.
* @callback EventedState~changeStateCallback
* @param {Error} error
* An error object with `true` in its `canceled` property if changing state is canceled.
* Cancellation happens if the handler of a custom event, that is fired before changing state happens,
* calls `.preventDefault()` against the event.
* @param {boolean} keptState
* `true` if the call to {@link EventedState#changeState `.changeState()`} didn't cause actual change in state.
*/
return EventedState;
}