diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 6ac028f52..7ff836b64 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -2080,7 +2080,7 @@ export default class Component extends StyleableModel { const current = list[id]; if (!current) { - list[id] = model; + Component.setListId(list, id, model); } else if (current !== model) { const keepIdsCrossPages = model.em?.Components.config.keepAttributeIdsCrossPages; const currentPage = current.page; diff --git a/packages/core/src/dom_components/model/Components.ts b/packages/core/src/dom_components/model/Components.ts index 9492f3265..ce45676f5 100644 --- a/packages/core/src/dom_components/model/Components.ts +++ b/packages/core/src/dom_components/model/Components.ts @@ -405,7 +405,8 @@ Component> { onAdd(model: Component, c?: any, opts: { temporary?: boolean } = {}) { const { domc, em } = this; const avoidInline = em.config.avoidInlineStyle; - domc && domc.Component.ensureInList(model); + const allById = domc?.allById(); + allById?.[model.getId()] !== model && domc?.Component.ensureInList(model); if (!avoidInline && em.config.forceClass && !opts.temporary) { const style = model.getStyle(); diff --git a/packages/core/src/dom_components/model/ModelDataResolverWatchers.ts b/packages/core/src/dom_components/model/ModelDataResolverWatchers.ts index 4748da4b3..907465dea 100644 --- a/packages/core/src/dom_components/model/ModelDataResolverWatchers.ts +++ b/packages/core/src/dom_components/model/ModelDataResolverWatchers.ts @@ -9,6 +9,7 @@ import { getSymbolsToUpdate, isSymbol } from './SymbolUtils'; import Component, { keySymbolOvrd } from './Component'; import { StyleableModelProperties } from '../../domain_abstract/model/StyleableModel'; import { isEmpty, isObject } from 'underscore'; +import { isDataResolverProps } from '../../data_sources/utils'; export const updateFromWatcher = { fromDataSource: true, avoidStore: true }; export const keyDataValues = '__data_values'; @@ -63,6 +64,25 @@ export class ModelDataResolverWatchers { return evaluatedProps; } + shouldResolveProps(props: ObjectAny) { + if (this.hasDataResolvers()) return true; + if (this.hasDataValues(props[keyDataValues])) return true; + if (this.hasResolverValue(props.attributes) || this.hasResolverValue(props.style)) return true; + + const { excludedFromEvaluation } = this; + const propKeys = Object.keys(props); + + for (let index = 0; index < propKeys.length; index++) { + const key = propKeys[index]; + + if (!excludedFromEvaluation.includes(key) && isDataResolverProps(props[key])) { + return true; + } + } + + return false; + } + getProps(data: ObjectAny): ObjectAny { const resolvedProps = this.getValueOrResolver('props', data); const result = { @@ -137,6 +157,24 @@ export class ModelDataResolverWatchers { return [this.propertyWatcher, this.styleWatcher, this.attributeWatcher]; } + private get excludedFromEvaluation() { + return ['components', 'dataResolver', 'status', 'state', 'open', keySymbolOvrd, keyDataValues]; + } + + private hasDataResolvers() { + return this.watchers.some((watcher) => watcher.hasDataResolvers()); + } + + private hasDataValues(dataValues: ObjectAny | undefined) { + return Object.values(dataValues || {}).some((value) => isObject(value) && !isEmpty(value)); + } + + private hasResolverValue(values: ObjectAny | string | undefined) { + if (!isObject(values)) return false; + + return Object.values(values).some(isDataResolverProps); + } + private isComponent(model: any): model is Component { return model instanceof Component; } @@ -187,15 +225,7 @@ export class ModelDataResolverWatchers { } private filterProps(props: ObjectAny) { - const excludedFromEvaluation = [ - 'components', - 'dataResolver', - 'status', - 'state', - 'open', - keySymbolOvrd, - keyDataValues, - ]; + const { excludedFromEvaluation } = this; const filteredProps = Object.fromEntries( Object.entries(props).filter(([key]) => !excludedFromEvaluation.includes(key)), ); diff --git a/packages/core/src/dom_components/model/ModelResolverWatcher.ts b/packages/core/src/dom_components/model/ModelResolverWatcher.ts index 2f9ad8848..af683204b 100644 --- a/packages/core/src/dom_components/model/ModelResolverWatcher.ts +++ b/packages/core/src/dom_components/model/ModelResolverWatcher.ts @@ -245,6 +245,10 @@ export class ModelResolverWatcher { return serializableValues; } + hasDataResolvers() { + return Object.keys(this.resolverListeners).length > 0; + } + getValuesResolvingFromCollections() { const keys = Object.keys(this.resolverListeners).filter((key: string) => { return this.resolverListeners[key].resolver.resolvesFromCollection(); diff --git a/packages/core/src/domain_abstract/model/StyleableModel.ts b/packages/core/src/domain_abstract/model/StyleableModel.ts index 36005186f..51ccc9a51 100644 --- a/packages/core/src/domain_abstract/model/StyleableModel.ts +++ b/packages/core/src/domain_abstract/model/StyleableModel.ts @@ -98,7 +98,10 @@ export default class StyleableModel ex } this.dataResolverWatchers = this.dataResolverWatchers ?? options.dataResolverWatchers; - const evaluatedValues = this.dataResolverWatchers.addProps(attributes, options) as Partial; + const shouldResolveProps = this.dataResolverWatchers.shouldResolveProps(attributes); + const evaluatedValues: Partial = shouldResolveProps + ? (this.dataResolverWatchers.addProps(attributes, options) as Partial) + : (attributes as Partial); return super.set(evaluatedValues, options); } diff --git a/packages/core/src/undo_manager/index.ts b/packages/core/src/undo_manager/index.ts index 07deafeaa..88ec5a96b 100644 --- a/packages/core/src/undo_manager/index.ts +++ b/packages/core/src/undo_manager/index.ts @@ -148,9 +148,13 @@ export default class UndoManagerModule extends Module this.um.on(ev, () => em.trigger(ev))); } + get isDisabled() { + return !!this.config._disable; + } + postLoad() { const { config, em } = this; - config.trackSelection && em && this.add(em.get('selected')); + config.trackSelection && !this.isDisabled && em && this.add(em.get('selected')); } /** @@ -169,6 +173,7 @@ export default class UndoManagerModule extends Module { expect(um.getStack()).toHaveLength(0); }); + test('Disabled undo manager does not track changes', () => { + const { editor, um } = setupTestEditor({ + withCanvas: true, + config: { undoManager: false }, + }); + const wrapper = editor.getWrapper()!; + + wrapper.append('
'); + + expect(um.isDisabled).toBe(true); + expect(um.hasUndo()).toBe(false); + expect(um.getStack()).toHaveLength(0); + + editor.destroy(); + }); + describe('Component changes', () => { test('Add component', () => { expect(wrapper.components()).toHaveLength(0);