UNPKG

@seznam/szn-select-react

Version:

Szn-select integration for React projects.

292 lines (237 loc) 10.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* global makeSznSelectBundleScript */ const e = _react.default.createElement; const READY_EVENT = 'szn-select:ready'; const UI_CONTAINER_PROPS = { 'data-szn-select--ui': '' }; const DEFAULT_LOADER_OPTIONS = { enable: true, useEmbeddedLoader: false, useAsyncLoading: true, urls: { package: 'https://unpkg.com/@seznam/szn-select@1.x/' } }; const SZN_SELECT_PROPERTIES = ['minBottomSpace', 'dropdownClassName', 'dropdownContainer']; // No need to specify every supported prop, any other prop is validated by the <select> element anyway const PROP_TYPES = { id: _propTypes.default.string, name: _propTypes.default.string, disabled: _propTypes.default.bool, multiple: _propTypes.default.bool, defaultValue: _propTypes.default.string, value: _propTypes.default.string, minBottomSpace: _propTypes.default.number, dropdownClassName: _propTypes.default.string, dropdownContainer: _propTypes.default.instanceOf(typeof Node !== 'undefined' ? Node : Object), onChange: _propTypes.default.func, loaderOptions: _propTypes.default.shape({ enable: _propTypes.default.bool, useEmbeddedLoader: _propTypes.default.bool, useAsyncLoading: _propTypes.default.bool, urls: _propTypes.default.shape({ 'package': _propTypes.default.string, 'loader': _propTypes.default.string, 'es3': _propTypes.default.string, 'es2016': _propTypes.default.string, 'bundle-elements.es3': _propTypes.default.string, 'bundle-elements.es2016': _propTypes.default.string, 'bundle-full.es3': _propTypes.default.string, 'bundle-full.es2016': _propTypes.default.string, 'bundle-full.ce': _propTypes.default.string }) }), children: _propTypes.default.node }; let sznSelectLoadingBegun = false; class SznSelect extends _react.default.Component { static get propTypes() { return PROP_TYPES; } constructor(props) { super(props); this._previousRootNode = null; this._nativeSelect = null; this._uiContainer = e('span', UI_CONTAINER_PROPS); // we don't want the UI container updated this._onRootNodeUpdate = this.onRootNodeUpdate.bind(this); this._onNativeSelectNodeUpdate = this.onNativeSelectNodeUpdate.bind(this); this._onSelectReady = this.onSelectReady.bind(this); this._initCallbackDelayedExecutionId = null; this.state = { sznSelectProps: { 'data-szn-elements--init-on-demand': '', 'ref': this._onRootNodeUpdate } }; } render() { // The "rest object properties" syntax is not supported by babel-present-env at the moment const selectProps = Object.assign({ ref: this._onNativeSelectNodeUpdate }, this.props); if (selectProps.loaderOptions) { delete selectProps.loaderOptions; } for (const sznSelectProperty of SZN_SELECT_PROPERTIES) { delete selectProps[sznSelectProperty]; } return e('szn-select', this.state.sznSelectProps, e('select', selectProps, selectProps.children), this._uiContainer); } componentDidMount() { if (sznSelectLoadingBegun) { return; } sznSelectLoadingBegun = true; if (self.SznElements && self.SznElements.init && self.SznElements['szn-tethered'] && self.SznElements['szn-select']) { return; } const loaderOptions = this.props.loaderOptions || DEFAULT_LOADER_OPTIONS; if (loaderOptions.enable === false) { return; } if (loaderOptions.useEmbeddedLoader) { const urlsConfiguration = loaderOptions.urls || DEFAULT_LOADER_OPTIONS.urls; const bundleScript = loadSznSelect(urlsConfiguration, loaderOptions.useAsyncLoading !== false); document.head.appendChild(bundleScript); return; } const loaderScript = document.createElement('script'); loaderScript.async = loaderOptions.useAsyncLoading !== false; const urls = loaderOptions.urls || {}; const providedPackageUrl = urls.package || DEFAULT_LOADER_OPTIONS.urls.package; const packageUrl = /\/$/.test(providedPackageUrl) ? providedPackageUrl : `${providedPackageUrl}/`; loaderScript.setAttribute('data-szn-select--loader-urls--package', packageUrl); for (const urlOption of Object.keys(urls)) { loaderScript.setAttribute(`data-szn-select--loader-urls--${urlOption.replace('.', '-')}`, urls[urlOption]); } loaderScript.src = urls.loader || `${packageUrl}loader.min.js`; document.head.appendChild(loaderScript); } componentDidUpdate(prevProps) { if (prevProps.value !== this.props.value && this._nativeSelect) { // Let the szn-select know that the value, managed by React, has changed, so the UI can get updated. this._nativeSelect.dispatchEvent(new CustomEvent('change')); } for (const sznSelectProperty of SZN_SELECT_PROPERTIES) { if (prevProps[sznSelectProperty] !== this.props[sznSelectProperty] && this._previousRootNode._broker) { this._previousRootNode[sznSelectProperty] = this.props[sznSelectProperty]; } } } onRootNodeUpdate(rootNode) { if (rootNode === this._previousRootNode) { return; } if (this._previousRootNode) { this._previousRootNode.removeEventListener(READY_EVENT, this._onSelectReady); if (this._previousRootNode._broker) { this._previousRootNode._broker.onUnmount(); // no check needed, szn-select relies on the onUnmount callback } if (this._initCallbackDelayedExecutionId) { cancelAnimationFrame(this._initCallbackDelayedExecutionId); this._initCallbackDelayedExecutionId = null; } } this._previousRootNode = rootNode; if (rootNode) { this._awaitAndFinalizeInitialization(rootNode); rootNode.addEventListener(READY_EVENT, this._onSelectReady); if (rootNode.isReady && rootNode.requestedAttributes) { this._handleAttributesUpdate(rootNode.requestedAttributes); } } } onNativeSelectNodeUpdate(nativeSelect) { this._nativeSelect = nativeSelect; } onSelectReady(event) { this._handleAttributesUpdate(event.detail.attributes); } _handleAttributesUpdate(requestedAttributeUpdate) { const sznSelectProps = this._processAttributes(requestedAttributeUpdate); sznSelectProps.ref = this._onRootNodeUpdate; this.setState({ sznSelectProps }); } _processAttributes(attributesUpdate) { const currentAttributes = {}; for (const attribute of Object.keys(attributesUpdate)) { if (attributesUpdate[attribute] !== null) { // attribute deleting is handled by React currentAttributes[attribute] = attributesUpdate[attribute]; } } return currentAttributes; } _awaitAndFinalizeInitialization(sznSelectNode) { const readyCheckCallback = () => { if (sznSelectNode._broker) { sznSelectNode._broker.onMount(); // no check needed, szn-select relies on the onMount callback for (const sznSelectProperty of SZN_SELECT_PROPERTIES) { if (sznSelectProperty in this.props) { sznSelectNode[sznSelectProperty] = this.props[sznSelectProperty]; } } } else { this._initCallbackDelayedExecutionId = requestAnimationFrame(readyCheckCallback); } }; // Run immediately in case we're already initialized, which is always the case with a pre-loaded custom elements // runtime. readyCheckCallback(); } } exports.default = SznSelect; function loadSznSelect(urlConfiguration, useAsyncLoading) { 'use strict'; var makeSznSelectBundleScript = function (global) { // eslint-disable-line no-unused-vars var BUNDLES = { ELEMENT_ES2016: 'es2016', ELEMENT_ES3: 'es3', ELEMENTS_ES2016: 'bundle-elements.es2016', ELEMENTS_ES3: 'bundle-elements.es3', ALL_CE: 'bundle-full.ce', ALL_ES2016: 'bundle-full.es2016', ALL_ES3: 'bundle-full.es3' }; var DEFAULT_PACKAGE_URL = 'https://cdn.jsdelivr.net/npm/@seznam/szn-select@1.x/'; function makeBundleLoadScript(urlsConfiguration, useAsyncLoading) { if (typeof self === 'undefined') { throw new Error('The loader can only be used at the client-side'); } if (global.SznElements && global.SznElements['szn-tethered'] && global.SznElements['szn-select']) { return null; // already loaded } var bundleUrl = getSznSelectBundleUrl(urlsConfiguration); var script = document.createElement('script'); script.async = useAsyncLoading; script.defer = useAsyncLoading; script.src = bundleUrl; return script; } function getSznSelectBundleUrl(urlsConfiguration) { // Firefox 52-based browsers (e.g. Palemoon) have faulty support for for-scoped const variable declarations var firefoxVersionMatch = navigator.userAgent.match(/ Firefox\/(\d+)/); var firefoxVersion = firefoxVersionMatch && parseInt(firefoxVersionMatch[1], 10); var supportsForConst = !firefoxVersion || firefoxVersion > 52; var es2016Supported = supportsForConst && global.Proxy && Array.prototype.includes; var runtimeLoaded = global.SznElements && global.SznElements.init && global.SznElements.injectStyles; var dependenciesLoaded = runtimeLoaded && global.SznElements['szn-tethered']; var bundleToLoad = dependenciesLoaded ? es2016Supported ? BUNDLES.ELEMENT_ES2016 : BUNDLES.ELEMENT_ES3 : runtimeLoaded ? es2016Supported ? BUNDLES.ELEMENTS_ES2016 : BUNDLES.ELEMENTS_ES3 : global.customElements ? BUNDLES.ALL_CE : es2016Supported ? BUNDLES.ALL_ES2016 : BUNDLES.ALL_ES3; return urlsConfiguration[bundleToLoad] || function () { var baseUrl = urlsConfiguration["package"] || DEFAULT_PACKAGE_URL; return /\/$/.test(baseUrl) ? baseUrl : "".concat(baseUrl, "/"); }() + "szn-select.".concat(bundleToLoad, ".min.js"); } return makeBundleLoadScript; }(self); return makeSznSelectBundleScript(urlConfiguration, useAsyncLoading); }