From a087e9404db1f1fc187fab077bf52ed21fcf81f5 Mon Sep 17 00:00:00 2001 From: mohamed yahia Date: Tue, 1 Jul 2025 12:13:04 +0300 Subject: [PATCH] Change dynamic style events behviour (#6555) * Emit event on setting a style with dynamic value * Fix event when binding to a data value equals to the previous static value * Fix dataResolver listening bug after loading a project --- .../model/DataResolverListener.ts | 15 ++- .../model/ModelDataResolverWatchers.ts | 2 +- .../domain_abstract/model/StyleableModel.ts | 10 +- .../test/specs/data_sources/serialization.ts | 104 ++++++++++++++++++ 4 files changed, 120 insertions(+), 11 deletions(-) diff --git a/packages/core/src/data_sources/model/DataResolverListener.ts b/packages/core/src/data_sources/model/DataResolverListener.ts index e2ad1ec12..cf3d4c9c3 100644 --- a/packages/core/src/data_sources/model/DataResolverListener.ts +++ b/packages/core/src/data_sources/model/DataResolverListener.ts @@ -75,12 +75,11 @@ export default class DataResolverListener { private listenToDataVariable(dataVariable: DataVariable): ListenerWithCallback[] { const { em } = this; const dataListeners: ListenerWithCallback[] = []; - dataListeners.push( - this.createListener(dataVariable, 'change', () => { - this.listenToResolver(); - this.onChange(); - }), - ); + const onChangeAndRewatch = () => { + this.listenToResolver(); + this.onChange(); + }; + dataListeners.push(this.createListener(dataVariable, 'change', onChangeAndRewatch)); const path = dataVariable.getResolverPath(); if (!path) return dataListeners; @@ -89,7 +88,7 @@ export default class DataResolverListener { const [ds, dr] = em.DataSources.fromPath(path!); if (ds) { - dataListeners.push(this.createListener(ds.records, 'add remove reset')); + dataListeners.push(this.createListener(ds.records, 'add remove reset', onChangeAndRewatch)); } if (dr) { @@ -97,7 +96,7 @@ export default class DataResolverListener { } dataListeners.push( - this.createListener(em.DataSources.all, 'add remove reset'), + this.createListener(em.DataSources.all, 'add remove reset', onChangeAndRewatch), this.createListener(em, `${DataSourcesEvents.path}:${normPath}`), ); diff --git a/packages/core/src/dom_components/model/ModelDataResolverWatchers.ts b/packages/core/src/dom_components/model/ModelDataResolverWatchers.ts index 1fb432824..25ad6d24d 100644 --- a/packages/core/src/dom_components/model/ModelDataResolverWatchers.ts +++ b/packages/core/src/dom_components/model/ModelDataResolverWatchers.ts @@ -32,7 +32,7 @@ export class ModelDataResolverWatchers { } private onStyleUpdate(component: StyleableModel | undefined, key: string, value: any) { - component?.addStyle({ [key]: value }, { ...updateFromWatcher, noEvent: true, partial: true, avoidStore: true }); + component?.addStyle({ [key]: value }, { ...updateFromWatcher, partial: true, avoidStore: true }); } bindModel(model: StyleableModel) { diff --git a/packages/core/src/domain_abstract/model/StyleableModel.ts b/packages/core/src/domain_abstract/model/StyleableModel.ts index 5a0ad0ed5..67474e851 100644 --- a/packages/core/src/domain_abstract/model/StyleableModel.ts +++ b/packages/core/src/domain_abstract/model/StyleableModel.ts @@ -91,7 +91,7 @@ export default class StyleableModel extends Model extends Model { + return { + ...acc, + [key]: newStyle[key], + }; + }, {}); // Delete the property used for partial updates delete diff.__p; diff --git a/packages/core/test/specs/data_sources/serialization.ts b/packages/core/test/specs/data_sources/serialization.ts index b09b53240..ebf9ccb3a 100644 --- a/packages/core/test/specs/data_sources/serialization.ts +++ b/packages/core/test/specs/data_sources/serialization.ts @@ -412,5 +412,109 @@ describe('DataSource Serialization', () => { }, }); }); + + test('should resolve styles, props, and attributes if the entire datasource is added after load', () => { + const styleVar = { + type: DataVariableType, + defaultValue: 'black', + path: 'new-unified-data.styleRecord.color', + }; + const propAttrVar = { + type: DataVariableType, + defaultValue: 'default-value', + path: 'new-unified-data.propRecord.value', + }; + + const componentProjectData: ProjectData = { + pages: [ + { + frames: [ + { + component: { + components: [ + { + attributes: { id: 'selectorid', 'data-test': propAttrVar }, + tagName: 'div', + customProp: propAttrVar, + }, + ], + }, + }, + ], + }, + ], + styles: [{ selectors: ['#selectorid'], style: { color: styleVar } }], + dataSources: [], // Start with no datasources + }; + + editor.loadProjectData(componentProjectData); + const component = editor.getComponents().at(0); // Assert fallback to defaults before adding the data source + + expect(component.getStyle()).toEqual({ color: 'black' }); + expect(component.get('customProp')).toBe('default-value'); + expect(component.getAttributes()['data-test']).toBe('default-value'); + + editor.DataSources.add({ + id: 'new-unified-data', + records: [ + { id: 'styleRecord', color: 'green' }, + { id: 'propRecord', value: 'resolved-value' }, + ], + }); + + expect(component.getStyle()).toEqual({ color: 'green' }); + expect(component.get('customProp')).toBe('resolved-value'); + expect(component.getAttributes()['data-test']).toBe('resolved-value'); + }); + + test('should resolve styles, props, and attributes if a record is added to an existing datasource after load', () => { + const styleVar = { + type: DataVariableType, + defaultValue: 'black', + path: 'unified-source.newStyleRecord.color', + }; + const propAttrVar = { + type: DataVariableType, + defaultValue: 'default-value', + path: 'unified-source.newPropRecord.value', + }; + + const componentProjectData: ProjectData = { + pages: [ + { + frames: [ + { + component: { + components: [ + { + attributes: { id: 'selectorid', 'data-test': propAttrVar }, + tagName: 'div', + customProp: propAttrVar, + }, + ], + }, + }, + ], + }, + ], + styles: [{ selectors: ['#selectorid'], style: { color: styleVar } }], + dataSources: [{ id: 'unified-source', records: [] }], // Data source exists but is empty + }; + + editor.loadProjectData(componentProjectData); + const component = editor.getComponents().at(0); // Assert fallback to defaults because records are missing + + expect(component.getStyle()).toEqual({ color: 'black' }); + expect(component.get('customProp')).toBe('default-value'); + expect(component.getAttributes()['data-test']).toBe('default-value'); + + const ds = editor.DataSources.get('unified-source'); + ds?.addRecord({ id: 'newStyleRecord', color: 'purple' }); + ds?.addRecord({ id: 'newPropRecord', value: 'resolved-record-value' }); + + expect(component.getStyle()).toEqual({ color: 'purple' }); + expect(component.get('customProp')).toBe('resolved-record-value'); + expect(component.getAttributes()['data-test']).toBe('resolved-record-value'); + }); }); });