devextreme-react
Version:
DevExtreme React UI and Visualization Components
231 lines (229 loc) • 8.16 kB
JavaScript
/*!
* devextreme-react
* Version: 25.1.5
* Build date: Wed Sep 03 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
*/
/* eslint-disable no-restricted-globals */
import { compareValues, getChanges } from './configuration/comparer';
import { buildConfig, findValue, ValueType } from './configuration/tree';
import { mergeNameParts, shallowEquals } from './configuration/utils';
import { capitalizeFirstLetter } from './helpers';
const optionsManagers = new Set();
let guardTimeoutHandler = -1;
let innerGuardTimeoutHandler = -1;
export function unscheduleGuards() {
clearTimeout(guardTimeoutHandler);
clearTimeout(innerGuardTimeoutHandler);
}
export function scheduleGuards() {
unscheduleGuards();
guardTimeoutHandler = window.setTimeout(() => {
innerGuardTimeoutHandler = window.setTimeout(() => {
optionsManagers.forEach((optionManager) => optionManager.execGuards());
});
});
}
class OptionsManager {
constructor() {
this.guards = {};
this.isUpdating = false;
this.onOptionChanged = this.onOptionChanged.bind(this);
this.wrapOptionValue = this.wrapOptionValue.bind(this);
}
setInstance(instance, config, subscribableOptions, independentEvents) {
this.instance = instance;
this.currentConfig = config;
this.subscribableOptions = new Set(subscribableOptions);
this.independentEvents = new Set(independentEvents);
optionsManagers.add(this);
}
getInitialOptions(rootNode) {
const config = buildConfig(rootNode, false);
const options = {};
Object.keys(config.options).forEach((key) => {
options[key] = this.wrapOptionValue(key, config.options[key]);
});
return options;
}
getTemplateOptions(rootNode) {
const config = buildConfig(rootNode, false);
return config.templates;
}
update(config, dxtemplates) {
const changedOptions = [];
const optionChangedHandler = ({ value, fullName }) => {
changedOptions.push([fullName, value]);
};
this.instance.on('optionChanged', optionChangedHandler);
const changes = getChanges(config, this.currentConfig);
if (!changes.options && !changes.templates && !changes.removedOptions.length) {
return;
}
this.instance.beginUpdate();
this.isUpdating = true;
changes.removedOptions.forEach((optionName) => {
this.resetOption(optionName);
});
if (Object.keys(dxtemplates).length > 0) {
this.setValue('integrationOptions', {
templates: dxtemplates,
});
}
Object.keys(changes.options).forEach((key) => {
this.setValue(key, changes.options[key]);
});
this.isUpdating = false;
this.instance.off('optionChanged', optionChangedHandler);
this.currentConfig = config;
changedOptions.forEach(([name, value]) => {
const currentPropValue = config.options[name];
if (Object.prototype.hasOwnProperty.call(config.options, name)
&& currentPropValue !== value) {
this.setValue(name, currentPropValue);
}
});
this.instance.endUpdate();
}
onOptionChanged(e) {
if (this.isUpdating) {
return;
}
let valueDescriptor = findValue(this.currentConfig, e.fullName.split('.'));
if (!valueDescriptor || valueDescriptor.value !== e.value) {
this.callOptionChangeHandler(e.fullName, e.value);
}
valueDescriptor = findValue(this.currentConfig, e.fullName.split('.'));
if (!valueDescriptor) {
return;
}
const { value, type } = valueDescriptor;
if (value instanceof Array && type === ValueType.Array) {
for (let i = 0; i < value.length; i += 1) {
const newValue = e.value?.[i];
if (value[i] !== newValue) {
this.addGuard(e.fullName, value, newValue);
return;
}
}
}
else if (type === ValueType.Complex && value instanceof Object) {
Object.keys(value).forEach((key) => {
const newValue = e.value?.[key];
if (value[key] === newValue) {
return;
}
this.addGuard(mergeNameParts(e.fullName, key), value[key], newValue);
});
}
else {
const valuesAreEqual = value === e.value;
const valuesAreEqualObjects = !valuesAreEqual
&& value instanceof Object
&& e.value instanceof Object
&& shallowEquals(value, e.value);
if (valuesAreEqual || valuesAreEqualObjects || this.instance.skipOptionsRollBack) {
return;
}
this.addGuard(e.fullName, value, e.value);
}
}
get isInstanceSet() {
return !!this.instance;
}
dispose() {
optionsManagers.delete(this);
Object.keys(this.guards).forEach((optionName) => {
delete this.guards[optionName];
});
this.instance = null;
}
isOptionSubscribable(optionName) {
return this.subscribableOptions.has(optionName);
}
isIndependentEvent(optionName) {
return this.independentEvents.has(optionName);
}
callOptionChangeHandler(optionName, optionValue) {
if (!this.isOptionSubscribable(optionName)) {
return;
}
const parts = optionName.split('.');
const propName = parts[parts.length - 1];
if (propName.startsWith('on')) {
return;
}
const eventName = `on${capitalizeFirstLetter(propName)}Change`;
parts[parts.length - 1] = eventName;
const changeEvent = findValue(this.currentConfig, parts);
if (!changeEvent) {
return;
}
if (typeof changeEvent.value !== 'function') {
throw new Error(`Invalid value for the ${eventName} property.
${eventName} must be a function.`);
}
changeEvent.value(optionValue);
}
wrapOptionValue(name, value) {
if (name.substr(0, 2) === 'on' && typeof value === 'function') {
return (...args) => {
if (!this.isUpdating || this.isIndependentEvent(name)) {
value(...args);
}
};
}
return value;
}
addGuard(optionName, initialValue, latestValue) {
if (this.guards[optionName] !== undefined) {
this.guards[optionName].latestValue = latestValue;
return;
}
const handler = () => {
this.setValue(optionName, initialValue);
delete this.guards[optionName];
};
this.guards[optionName] = {
handler,
latestValue,
};
scheduleGuards();
}
execGuards() {
Object.values(this.guards)
.forEach((guard) => guard.handler());
}
resetOption(name) {
if (this.isCollectionOption(name)) {
this.setValue(name, []);
}
else {
this.instance.resetOption(name);
}
}
isCollectionOption(name) {
const valueDescriptor = findValue(this.currentConfig, name.split('.'));
return valueDescriptor?.type === ValueType.Array;
}
setValue(name, value) {
if (this.guards[name]) {
try {
if (compareValues(this.guards[name].latestValue, value)) {
return;
}
}
finally {
delete this.guards[name];
}
}
this.instance.option(name, this.wrapOptionValue(name, value));
}
}
export { OptionsManager, };