UNPKG

devextreme-react

Version:

DevExtreme React UI and Visualization Components

353 lines (351 loc) • 15 kB
/*! * 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 */ "use strict"; 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;