carbon-custom-elements
Version:
A Carbon Design System variant that's as easy to use as native HTML elements, with no framework tax, no framework silo.
278 lines (237 loc) • 9.47 kB
JavaScript
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/**
* @license
*
* Copyright IBM Corp. 2019, 2020
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { Component, createElement, forwardRef } from 'react';
import on from 'carbon-components/es/globals/js/misc/on';
/**
* @param refs List of React refs to merge.
* @returns Merged React ref.
*/
const mergeRefs = (...refs) => el => {
refs.forEach(ref => {
// https://github.com/facebook/react/issues/13029#issuecomment-410002316
if (typeof ref === 'function') {
ref(el);
} else if (Object(ref) === ref) {
// `React.Ref.current` is read-only for regular use case, but we update it here
ref.current = el;
}
});
};
/**
* @param prop A prop value.
* @param descriptor A React prop descriptor.
* @returns The corresponding attribute value for the given prop value.
*/
const convertProp = (prop, descriptor) => {
if (!descriptor) {
return prop;
}
const {
event,
serialize
} = descriptor;
if (event) {
// Events are not set as props, we use DOM `addEventListener()` instead
return undefined;
}
return !serialize ? prop : serialize(prop);
};
/**
* @param props A set of React props.
* @param descriptor A set of React prop desciptor.
* @returns The set of React props to set to a custom element, corresponding to the given React props.
*/
const convertProps = (props, descriptor) => Object.keys(props).reduce((acc, propName) => {
const {
[propName]: descriptorItem
} = descriptor;
const converted = convertProp(props[propName], descriptorItem);
const {
attribute
} = descriptorItem !== null && descriptorItem !== void 0 ? descriptorItem : {};
return attribute === false ? acc : _objectSpread({}, acc, {
[attribute || propName]: converted
});
}, {});
/**
* Attaches listeners of custom events, to a custom element.
* @param elem The custom element.
* @param descriptor An object, keyed by prop name, of data that may have custom event names.
* @param callback A callback function that runs as the custom events fire.
* @returns A handle that allows to release all event listeners attached.
*/
const attachEventListeners = (elem, descriptor, callback) => {
const handles = new Set();
Object.keys(descriptor).forEach(propName => {
if (descriptor[propName]) {
const {
event: eventDescriptor
} = descriptor[propName];
const name = Object(eventDescriptor) !== eventDescriptor ? eventDescriptor : eventDescriptor.name;
const options = Object(eventDescriptor) !== eventDescriptor ? undefined : eventDescriptor.options;
if (name) {
handles.add(on(elem, name, event => {
callback(propName, event);
}, options));
}
}
});
return {
release() {
handles.forEach(handle => {
handle.release();
handles.delete(handle);
});
return null;
}
};
};
/**
* @param name The tag name of the custom element.
* @param descriptor A descriptor for a set of React props for attributes of a custom element.
* @returns A React component working as a wrapper for the given custom element.
* @example
* import { render } from 'react-dom';
* import createCustomElementType, { booleanSerializer } from '/path/to/createCustomElementType';
*
* const BXDropdown = createCustomElementType('bx-dropdown', {
* disabled: {
* // Sets `disabled` attribute when the React prop value is truthy, unsets otherwise
* serialize: booleanSerializer,
* },
* helperText: {
* // Maps `helperText` React prop to `helper-text` attribute
* attribute: 'helper-text',
* },
* onBeforeSelect: {
* // Sets `onBeforeSelect` React prop value as a listener of `bx-dropdown-beingselected` custom event
* event: 'bx-dropdown-beingselected',
* },
* });
*
* render(
* (
* <BXDropdown
* disabled={true}
* helperText="some-helper-text"
* onBeforeSelect={event => { console.log('bx-dropdown-beingselected is fired!', event); }}>
* <bx-dropdown-item value="all">Option 1</bx-dropdown-item>
* <bx-dropdown-item value="cloudFoundry">Option 2</bx-dropdown-item>
* <bx-dropdown-item value="staging">Option 3</bx-dropdown-item>
* </BXDropdown>
* )
* document.body
* );
*/
const createReactCustomElementType = (name, descriptor) => {
/**
* Array of React prop names that should be mapped to DOM properties instead of attributes.
*/
const nonAttributeProps = Object.keys(descriptor).filter(propName => {
const {
[propName]: descriptorItem
} = descriptor;
const {
attribute
} = descriptorItem !== null && descriptorItem !== void 0 ? descriptorItem : {};
return attribute === false;
});
/**
* A React component working as a wrapper for the custom element.
*/
class CustomElementType extends Component {
constructor(...args) {
super(...args);
_defineProperty(this, "_elem", null);
_defineProperty(this, "_eventListenersHandle", null);
_defineProperty(this, "_handleEvent", (propName, event) => {
const {
[propName]: listener
} = this.props;
if (listener) {
listener.call(event.currentTarget, event);
}
});
_defineProperty(this, "_handleElemRef", elem => {
this._elem = elem;
if (this._eventListenersHandle) {
this._eventListenersHandle.release();
this._eventListenersHandle = null;
}
if (elem) {
this._eventListenersHandle = attachEventListeners(elem, descriptor, this._handleEvent);
}
});
}
/**
* Reflects change in React props to DOM properties.
* @param prevProps The previous props.
*/
updateProps(prevProps = {}) {
const {
props,
_elem: elem
} = this;
nonAttributeProps.forEach(propName => {
const {
[propName]: prevValue
} = prevProps;
const {
[propName]: value
} = props;
if (prevValue !== value) {
elem[propName] = value;
}
});
}
componentDidMount() {
this.updateProps();
}
componentDidUpdate(prevProps) {
this.updateProps(prevProps);
}
render() {
const _this$props = this.props,
{
children,
innerRef
} = _this$props,
props = _objectWithoutProperties(_this$props, ["children", "innerRef"]);
const mergedRef = mergeRefs(innerRef, this._handleElemRef);
return createElement(name, _objectSpread({
ref: mergedRef
}, convertProps(props, descriptor)), children);
}
}
return forwardRef((props, ref) => createElement(CustomElementType, _objectSpread({}, props, {
innerRef: ref
})));
};
/**
* @param value A React prop value.
* @returns Serialized version of React prop value, as a boolean attribute in a custom element.
*/
export const booleanSerializer = value => !value ? undefined : '';
/**
* @param value A React prop value.
* @returns Serialized version of React prop value, as a number attribute in a custom element.
*/
export const numberSerializer = value => String(value);
/**
* @param value A React prop value.
* @returns Serialized version of React prop value, as a object attribute in a custom element.
*/
export const objectSerializer = JSON.stringify;
export default createReactCustomElementType;
//# sourceMappingURL=createReactCustomElementType.js.map