UNPKG

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.

1 lines 10.6 kB
{"version":3,"sources":["globals/wrappers/createReactCustomElementType.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAA+C,MAAM,OAAO,CAAC;AAIpE;;GAEG;AACH,UAAU,4BAA4B;IACpC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;CAC1C;AAED;;GAEG;AACH,UAAU,2BAA2B;IACnC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAE3B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,4BAA4B,CAAC;IAE9C;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED;;GAEG;AACH,UAAU,4BAA4B;IACpC,CAAC,QAAQ,EAAE,MAAM,GAAG,2BAA2B,CAAC;CACjD;AAED;;GAEG;AACH,UAAU,sBAAsB;IAC9B;;OAEG;IACH,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC;IAExB;;OAEG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAoGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,QAAA,MAAM,4BAA4B,+KAoFjC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,gCAAqC,CAAC;AAEpE;;;GAGG;AACH,eAAO,MAAM,gBAAgB,wBAAyB,CAAC;AAEvD;;;GAGG;AACH,eAAO,MAAM,gBAAgB;;;CAAiB,CAAC;AAE/C,eAAe,4BAA4B,CAAC","file":"createReactCustomElementType.d.ts","sourcesContent":["/**\n * @license\n *\n * Copyright IBM Corp. 2019, 2020\n *\n * This source code is licensed under the Apache-2.0 license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport React, { Component, createElement, forwardRef } from 'react';\nimport on from 'carbon-components/es/globals/js/misc/on';\nimport Handle from '../internal/handle';\n\n/**\n * A descriptor for a React event prop of a custom element.\n */\ninterface CustomElementEventDescriptor {\n /**\n * The event name.\n */\n name: string;\n\n /**\n * A boolean to detemine usage of capture mode or the event options.\n */\n options?: boolean | EventListenerOptions;\n}\n\n/**\n * A descriptor for a React prop for an attribute of a custom element.\n */\ninterface CustomElementPropDescriptor {\n /**\n * The attribute name for the prop.\n */\n attribute?: string | false;\n\n /**\n * The event name (or descriptor) for the prop.\n */\n event?: string | CustomElementEventDescriptor;\n\n /**\n * A function that takes a property value and returns the corresponding attribute value.\n */\n serialize?: (value: any) => string | void;\n}\n\n/**\n * A descriptor for a set of React props for attributes of a custom element.\n */\ninterface CustomElementPropsDescriptor {\n [propName: string]: CustomElementPropDescriptor;\n}\n\n/**\n * React props for the component `createCustomElementType()` generates.\n */\ninterface CustomElementTypeProps {\n /**\n * Ordinal prop.\n */\n [propName: string]: any;\n\n /**\n * Child nodes.\n */\n children?: React.ReactNode;\n}\n\n/**\n * @param refs List of React refs to merge.\n * @returns Merged React ref.\n */\nconst mergeRefs = <T>(...refs: React.Ref<T>[]) => el => {\n refs.forEach(ref => {\n // https://github.com/facebook/react/issues/13029#issuecomment-410002316\n if (typeof ref === 'function') {\n ref(el);\n } else if (Object(ref) === ref) {\n // `React.Ref.current` is read-only for regular use case, but we update it here\n (ref as { current: T }).current = el;\n }\n });\n};\n\n/**\n * @param prop A prop value.\n * @param descriptor A React prop descriptor.\n * @returns The corresponding attribute value for the given prop value.\n */\nconst convertProp = (prop: any, descriptor: CustomElementPropDescriptor) => {\n if (!descriptor) {\n return prop;\n }\n const { event, serialize } = descriptor;\n if (event) {\n // Events are not set as props, we use DOM `addEventListener()` instead\n return undefined;\n }\n return !serialize ? prop : serialize(prop);\n};\n\n/**\n * @param props A set of React props.\n * @param descriptor A set of React prop desciptor.\n * @returns The set of React props to set to a custom element, corresponding to the given React props.\n */\nconst convertProps = (props: CustomElementTypeProps, descriptor: CustomElementPropsDescriptor) =>\n Object.keys(props).reduce((acc, propName) => {\n const { [propName]: descriptorItem } = descriptor;\n const converted = convertProp(props[propName], descriptorItem);\n const { attribute } = descriptorItem ?? {};\n return attribute === false\n ? acc\n : {\n ...acc,\n [attribute || propName]: converted,\n };\n }, {});\n\n/**\n * Attaches listeners of custom events, to a custom element.\n * @param elem The custom element.\n * @param descriptor An object, keyed by prop name, of data that may have custom event names.\n * @param callback A callback function that runs as the custom events fire.\n * @returns A handle that allows to release all event listeners attached.\n */\nconst attachEventListeners = (\n elem: HTMLElement,\n descriptor: CustomElementPropsDescriptor,\n callback: (name: string, event: Event) => void\n): Handle => {\n const handles = new Set<Handle>();\n Object.keys(descriptor).forEach(propName => {\n if (descriptor[propName]) {\n const { event: eventDescriptor } = descriptor[propName];\n const name =\n Object(eventDescriptor) !== eventDescriptor\n ? (eventDescriptor as string)\n : (eventDescriptor as CustomElementEventDescriptor).name;\n const options =\n Object(eventDescriptor) !== eventDescriptor ? undefined : (eventDescriptor as CustomElementEventDescriptor).options;\n if (name) {\n handles.add(\n on(\n elem,\n name,\n event => {\n callback(propName, event);\n },\n options\n )\n );\n }\n }\n });\n return {\n release() {\n handles.forEach(handle => {\n handle.release();\n handles.delete(handle);\n });\n return null;\n },\n };\n};\n\n/**\n * @param name The tag name of the custom element.\n * @param descriptor A descriptor for a set of React props for attributes of a custom element.\n * @returns A React component working as a wrapper for the given custom element.\n * @example\n * import { render } from 'react-dom';\n * import createCustomElementType, { booleanSerializer } from '/path/to/createCustomElementType';\n *\n * const BXDropdown = createCustomElementType('bx-dropdown', {\n * disabled: {\n * // Sets `disabled` attribute when the React prop value is truthy, unsets otherwise\n * serialize: booleanSerializer,\n * },\n * helperText: {\n * // Maps `helperText` React prop to `helper-text` attribute\n * attribute: 'helper-text',\n * },\n * onBeforeSelect: {\n * // Sets `onBeforeSelect` React prop value as a listener of `bx-dropdown-beingselected` custom event\n * event: 'bx-dropdown-beingselected',\n * },\n * });\n *\n * render(\n * (\n * <BXDropdown\n * disabled={true}\n * helperText=\"some-helper-text\"\n * onBeforeSelect={event => { console.log('bx-dropdown-beingselected is fired!', event); }}>\n * <bx-dropdown-item value=\"all\">Option 1</bx-dropdown-item>\n * <bx-dropdown-item value=\"cloudFoundry\">Option 2</bx-dropdown-item>\n * <bx-dropdown-item value=\"staging\">Option 3</bx-dropdown-item>\n * </BXDropdown>\n * )\n * document.body\n * );\n */\nconst createReactCustomElementType = (name: string, descriptor: CustomElementPropsDescriptor) => {\n /**\n * Array of React prop names that should be mapped to DOM properties instead of attributes.\n */\n const nonAttributeProps = Object.keys(descriptor).filter(propName => {\n const { [propName]: descriptorItem } = descriptor;\n const { attribute } = descriptorItem ?? {};\n return attribute === false;\n });\n\n /**\n * A React component working as a wrapper for the custom element.\n */\n class CustomElementType extends Component<CustomElementTypeProps> {\n /**\n * The element.\n */\n private _elem: HTMLElement | null = null;\n\n /**\n * The handle that allows to release all event listeners attached to this custom element.\n */\n private _eventListenersHandle: Handle | null = null;\n\n /**\n * The callback function that runs as the custom events fire.\n * @param propName The React prop name associated with the event listener.\n * @param event The event.\n */\n private _handleEvent = (propName: string, event: Event) => {\n const { [propName]: listener } = this.props;\n if (listener) {\n listener.call(event.currentTarget, event);\n }\n };\n\n /**\n * Handles getting/losing the React `ref` object of this custom element.\n * @param elem The custom element.\n */\n private _handleElemRef = (elem: HTMLElement) => {\n this._elem = elem;\n if (this._eventListenersHandle) {\n this._eventListenersHandle.release();\n this._eventListenersHandle = null;\n }\n if (elem) {\n this._eventListenersHandle = attachEventListeners(elem, descriptor, this._handleEvent);\n }\n };\n\n /**\n * Reflects change in React props to DOM properties.\n * @param prevProps The previous props.\n */\n updateProps(prevProps: { [key: string]: any } = {}) {\n const { props, _elem: elem } = this;\n nonAttributeProps.forEach(propName => {\n const { [propName]: prevValue } = prevProps;\n const { [propName]: value } = props;\n if (prevValue !== value) {\n elem![propName] = value;\n }\n });\n }\n\n componentDidMount() {\n this.updateProps();\n }\n\n componentDidUpdate(prevProps) {\n this.updateProps(prevProps);\n }\n\n render() {\n const { children, innerRef, ...props } = this.props;\n const mergedRef = mergeRefs<HTMLElement>(innerRef, this._handleElemRef);\n return createElement(name, { ref: mergedRef, ...convertProps(props, descriptor) }, children);\n }\n }\n\n return forwardRef<HTMLElement, CustomElementTypeProps>((props, ref) =>\n createElement(CustomElementType, { ...props, innerRef: ref })\n );\n};\n\n/**\n * @param value A React prop value.\n * @returns Serialized version of React prop value, as a boolean attribute in a custom element.\n */\nexport const booleanSerializer = value => (!value ? undefined : '');\n\n/**\n * @param value A React prop value.\n * @returns Serialized version of React prop value, as a number attribute in a custom element.\n */\nexport const numberSerializer = value => String(value);\n\n/**\n * @param value A React prop value.\n * @returns Serialized version of React prop value, as a object attribute in a custom element.\n */\nexport const objectSerializer = JSON.stringify;\n\nexport default createReactCustomElementType;\n"]}