@seznam/szn-select-react
Version:
Szn-select integration for React projects.
292 lines (237 loc) • 10.3 kB
JavaScript
"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);
}