devextreme-react
Version:
DevExtreme React UI and Visualization Components
353 lines (351 loc) • 15 kB
JavaScript
/*!
* devextreme-react
* Version: 24.2.6
* Build date: Mon Mar 17 2025
*
* Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file in the root of the project for details.
*
* https://github.com/DevExpress/devextreme-react
*/
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DX_REMOVE_EVENT = exports.ComponentBase = void 0;
const React = __importStar(require("react"));
const events = __importStar(require("devextreme/events"));
const react_1 = require("react");
const frame_1 = require("devextreme/animation/frame");
const common_1 = require("devextreme/core/utils/common");
const config_1 = __importDefault(require("devextreme/core/config"));
const react_dom_1 = require("react-dom");
const use_option_scanning_1 = require("./use-option-scanning");
const options_manager_1 = require("./options-manager");
const widget_config_1 = require("./widget-config");
const template_manager_1 = require("./template-manager");
const element_1 = require("./configuration/react/element");
const contexts_1 = require("./contexts");
const DX_REMOVE_EVENT = 'dxremove';
exports.DX_REMOVE_EVENT = DX_REMOVE_EVENT;
(0, config_1.default)({
buyNowLink: 'https://go.devexpress.com/Licensing_Installer_Watermark_DevExtremeReact.aspx',
licensingDocLink: 'https://go.devexpress.com/Licensing_Documentation_DevExtremeReact.aspx',
});
const ComponentBase = (0, react_1.forwardRef)((props, ref) => {
const { templateProps = [], defaults = {}, expectedChildren = {}, isPortalComponent = false, useRequestAnimationFrameFlag = false, subscribableOptions = [], WidgetClass, independentEvents = [], renderChildren, beforeCreateWidget = () => undefined, afterCreateWidget = () => undefined, } = props;
const [, setForceUpdateToken] = (0, react_1.useState)(Symbol('initial force update token'));
const removalLocker = (0, react_1.useContext)(contexts_1.RemovalLockerContext);
const restoreParentLink = (0, react_1.useContext)(contexts_1.RestoreTreeContext);
const instance = (0, react_1.useRef)();
const element = (0, react_1.useRef)();
const portalContainer = (0, react_1.useRef)();
const useDeferUpdateForTemplates = (0, react_1.useRef)(false);
const guardsUpdateScheduled = (0, react_1.useRef)(false);
const childElementsDetached = (0, react_1.useRef)(false);
const shouldRestoreFocus = (0, react_1.useRef)(false);
const optionsManager = (0, react_1.useRef)(new options_manager_1.OptionsManager());
const childNodes = (0, react_1.useRef)();
const createDXTemplates = (0, react_1.useRef)();
const clearInstantiationModels = (0, react_1.useRef)();
const updateTemplates = (0, react_1.useRef)();
const prevPropsRef = (0, react_1.useRef)();
const childrenContainerRef = (0, react_1.useRef)(null);
const { parentType } = (0, react_1.useContext)(contexts_1.NestedOptionContext);
const [widgetConfig, context] = (0, use_option_scanning_1.useOptionScanning)({
type: element_1.ElementType.Option,
descriptor: {
name: '',
isCollection: false,
templates: templateProps,
initialValuesProps: defaults,
predefinedValuesProps: {},
expectedChildren,
},
props,
}, () => !!childrenContainerRef.current?.childNodes.length, Symbol('initial update token'), 'component');
const restoreTree = (0, react_1.useCallback)(() => {
if (childElementsDetached.current && childNodes.current?.length && element.current) {
element.current.append(...childNodes.current);
childElementsDetached.current = false;
}
if (restoreParentLink && element.current && !element.current.isConnected) {
restoreParentLink();
}
}, [
childNodes.current,
element.current,
childElementsDetached.current,
restoreParentLink,
]);
const updateCssClasses = (0, react_1.useCallback)((prevProps, newProps) => {
const prevClassName = prevProps ? (0, widget_config_1.getClassName)(prevProps) : undefined;
const newClassName = (0, widget_config_1.getClassName)(newProps);
if (prevClassName === newClassName) {
return;
}
if (prevClassName) {
const classNames = prevClassName.split(' ').filter((c) => c);
if (classNames.length) {
element.current?.classList.remove(...classNames);
}
}
if (newClassName) {
const classNames = newClassName.split(' ').filter((c) => c);
if (classNames.length) {
element.current?.classList.add(...classNames);
}
}
}, [element.current]);
const setInlineStyles = (0, react_1.useCallback)((styles) => {
if (element.current) {
const el = element.current;
Object.entries(styles).forEach(([name, value]) => {
el.style[name] = value;
});
}
}, [element.current]);
const setTemplateManagerHooks = (0, react_1.useCallback)(({ createDXTemplates: createDXTemplatesFn, clearInstantiationModels: clearInstantiationModelsFn, updateTemplates: updateTemplatesFn, }) => {
createDXTemplates.current = createDXTemplatesFn;
clearInstantiationModels.current = clearInstantiationModelsFn;
updateTemplates.current = updateTemplatesFn;
}, [
createDXTemplates.current,
clearInstantiationModels.current,
updateTemplates.current,
]);
const getElementProps = (0, react_1.useCallback)(() => {
const elementProps = {
ref: (el) => {
if (el) {
element.current = el;
}
},
};
widget_config_1.elementPropNames.forEach((name) => {
if (name in props) {
elementProps[name] = props[name];
}
});
return elementProps;
}, [element.current]);
const scheduleTemplatesUpdate = (0, react_1.useCallback)(() => {
if (guardsUpdateScheduled.current) {
return;
}
guardsUpdateScheduled.current = true;
const updateFunc = useDeferUpdateForTemplates.current ? common_1.deferUpdate : frame_1.requestAnimationFrame;
updateFunc(() => {
guardsUpdateScheduled.current = false;
updateTemplates.current?.(() => (0, options_manager_1.scheduleGuards)());
});
(0, options_manager_1.unscheduleGuards)();
}, [
guardsUpdateScheduled.current,
useDeferUpdateForTemplates.current,
updateTemplates.current,
]);
const createWidget = (0, react_1.useCallback)((el) => {
beforeCreateWidget();
el = el || element.current;
let options = {
templatesRenderAsynchronously: true,
...optionsManager.current.getInitialOptions(widgetConfig),
};
const templateOptions = optionsManager.current.getTemplateOptions(widgetConfig);
const dxTemplates = createDXTemplates.current?.(templateOptions);
if (dxTemplates && Object.keys(dxTemplates).length) {
options = {
...options,
integrationOptions: {
templates: dxTemplates,
},
};
}
clearInstantiationModels.current?.();
instance.current = new WidgetClass(el, options);
if (!useRequestAnimationFrameFlag) {
useDeferUpdateForTemplates.current = instance.current.option('integrationOptions.useDeferUpdateForTemplates');
}
optionsManager.current.setInstance(instance.current, widgetConfig, subscribableOptions, independentEvents);
instance.current.on('optionChanged', optionsManager.current.onOptionChanged);
afterCreateWidget();
}, [
beforeCreateWidget,
afterCreateWidget,
element.current,
optionsManager.current,
createDXTemplates.current,
clearInstantiationModels.current,
WidgetClass,
useRequestAnimationFrameFlag,
useDeferUpdateForTemplates.current,
instance.current,
subscribableOptions,
independentEvents,
widgetConfig,
]);
const onTemplatesRendered = (0, react_1.useCallback)(() => {
if (shouldRestoreFocus.current && instance.current?.focus) {
instance.current.focus();
shouldRestoreFocus.current = false;
}
}, [shouldRestoreFocus.current, instance.current]);
const onComponentUpdated = (0, react_1.useCallback)(() => {
if (parentType === 'option') {
return;
}
if (!optionsManager.current?.isInstanceSet) {
return;
}
updateCssClasses(prevPropsRef.current, props);
const templateOptions = optionsManager.current.getTemplateOptions(widgetConfig);
const dxTemplates = createDXTemplates.current?.(templateOptions) || {};
optionsManager.current.update(widgetConfig, dxTemplates);
scheduleTemplatesUpdate();
prevPropsRef.current = props;
}, [
optionsManager.current,
prevPropsRef.current,
createDXTemplates.current,
scheduleTemplatesUpdate,
updateCssClasses,
props,
widgetConfig,
]);
const onComponentMounted = (0, react_1.useCallback)(() => {
if (parentType === 'option') {
return;
}
const { style } = props;
if (childElementsDetached.current) {
restoreTree();
}
else if (element.current?.childNodes.length) {
childNodes.current = Array.from(element.current?.childNodes);
}
updateCssClasses(undefined, props);
if (style) {
setInlineStyles(style);
}
prevPropsRef.current = props;
}, [
childNodes.current,
element.current,
childElementsDetached.current,
updateCssClasses,
setInlineStyles,
props,
]);
const onComponentUnmounted = (0, react_1.useCallback)(() => {
removalLocker?.lock();
if (instance.current) {
const dxRemoveArgs = { isUnmounting: true };
shouldRestoreFocus.current = !!element.current?.contains(document.activeElement);
childNodes.current?.forEach((child) => child.parentNode?.removeChild(child));
childElementsDetached.current = true;
if (element.current) {
const preventFocusOut = (e) => e.stopPropagation();
events.on(element.current, 'focusout', preventFocusOut);
events.triggerHandler(element.current, DX_REMOVE_EVENT, dxRemoveArgs);
events.off(element.current, 'focusout', preventFocusOut);
}
instance.current.dispose();
instance.current = null;
}
optionsManager.current.dispose();
removalLocker?.unlock();
}, [
removalLocker,
instance.current,
childNodes.current,
element.current,
optionsManager.current,
childElementsDetached.current,
shouldRestoreFocus.current,
]);
(0, react_1.useLayoutEffect)(() => {
onComponentMounted();
return () => {
onComponentUnmounted();
};
}, []);
(0, react_1.useLayoutEffect)(() => {
onComponentUpdated();
});
(0, react_1.useImperativeHandle)(ref, () => ({
getInstance() {
return instance.current;
},
getElement() {
return element.current;
},
createWidget(el) {
createWidget(el);
},
}), [instance.current, element.current, createWidget]);
const _renderChildren = (0, react_1.useCallback)(() => {
if (renderChildren) {
return renderChildren();
}
const { children } = props;
return children;
}, [props, renderChildren]);
const renderPortal = (0, react_1.useCallback)(() => portalContainer.current && (0, react_dom_1.createPortal)(_renderChildren(), portalContainer.current), [portalContainer.current, _renderChildren]);
const renderContent = (0, react_1.useCallback)(() => {
const { children } = props;
return isPortalComponent && children
? React.createElement('div', {
ref: (node) => {
if (node && portalContainer.current !== node) {
portalContainer.current = node;
setForceUpdateToken(Symbol('force update token'));
}
},
style: { display: 'contents' },
})
: _renderChildren();
}, [
props,
isPortalComponent,
portalContainer.current,
_renderChildren,
]);
const renderContextValue = (0, react_1.useMemo)(() => ({
isTemplateRendering: false,
}), []);
return (React.createElement(contexts_1.RestoreTreeContext.Provider, { value: restoreTree },
React.createElement(contexts_1.TemplateRenderingContext.Provider, { value: renderContextValue },
React.createElement("div", { ref: childrenContainerRef, ...getElementProps() },
React.createElement(contexts_1.NestedOptionContext.Provider, { value: context }, renderContent()),
React.createElement(template_manager_1.TemplateManager, { init: setTemplateManagerHooks, onTemplatesRendered: onTemplatesRendered }),
isPortalComponent
&& React.createElement(contexts_1.NestedOptionContext.Provider, { value: context }, renderPortal())))));
});
exports.ComponentBase = ComponentBase;