@elementor/editor-canvas 4.0.0-551 → 4.0.0-564
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +123 -130
- package/dist/index.mjs +130 -133
- package/package.json +18 -18
- package/src/init-settings-transformers.ts +2 -0
- package/src/legacy/__tests__/twig-rendering-utils.test.ts +115 -0
- package/src/legacy/create-nested-templated-element-type.ts +18 -26
- package/src/legacy/create-templated-element-type.ts +26 -96
- package/src/legacy/replacements/inline-editing/__tests__/inline-editing-eligibility.test.ts +7 -7
- package/src/legacy/replacements/inline-editing/canvas-inline-editor.tsx +0 -3
- package/src/legacy/replacements/inline-editing/inline-editing-elements.tsx +20 -8
- package/src/legacy/replacements/inline-editing/inline-editing-eligibility.ts +6 -6
- package/src/legacy/tabs-model-extensions.ts +2 -2
- package/src/legacy/twig-rendering-utils.ts +110 -51
- package/src/legacy/types.ts +2 -0
- package/src/mcp/tools/build-composition/schema.ts +4 -1
- package/src/renderers/__tests__/create-dom-renderer.test.ts +0 -1
- package/src/renderers/__tests__/create-styles-renderer.test.ts +0 -1
- package/src/transformers/settings/html-v2-transformer.ts +10 -0
|
@@ -6,9 +6,9 @@ import { canBeTemplated, type TemplatedElementConfig } from './create-templated-
|
|
|
6
6
|
import {
|
|
7
7
|
createAfterRender,
|
|
8
8
|
createBeforeRender,
|
|
9
|
+
createTwigRenderState,
|
|
10
|
+
renderChildrenWithOptimization,
|
|
9
11
|
renderTwigTemplate,
|
|
10
|
-
setupTwigRenderer,
|
|
11
|
-
type TwigRenderContext,
|
|
12
12
|
} from './twig-rendering-utils';
|
|
13
13
|
import { type ElementType, type ElementView, type LegacyWindow } from './types';
|
|
14
14
|
|
|
@@ -91,11 +91,7 @@ export function createNestedTemplatedElementView( {
|
|
|
91
91
|
}: CreateNestedTemplatedElementViewOptions ): typeof ElementView {
|
|
92
92
|
const legacyWindow = window as unknown as LegacyWindow;
|
|
93
93
|
|
|
94
|
-
const {
|
|
95
|
-
type,
|
|
96
|
-
renderer,
|
|
97
|
-
element,
|
|
98
|
-
} );
|
|
94
|
+
const renderState = createTwigRenderState( { renderer, element } );
|
|
99
95
|
|
|
100
96
|
const AtomicElementBaseView = legacyWindow.elementor.modules.elements.views.createAtomicElementBase( type );
|
|
101
97
|
const parentRenderChildren = AtomicElementBaseView.prototype._renderChildren;
|
|
@@ -110,6 +106,10 @@ export function createNestedTemplatedElementView( {
|
|
|
110
106
|
return 'twig';
|
|
111
107
|
},
|
|
112
108
|
|
|
109
|
+
invalidateRenderCache() {
|
|
110
|
+
renderState.cacheState.invalidate();
|
|
111
|
+
},
|
|
112
|
+
|
|
113
113
|
render() {
|
|
114
114
|
this._abortController?.abort();
|
|
115
115
|
this._abortController = new AbortController();
|
|
@@ -117,7 +117,6 @@ export function createNestedTemplatedElementView( {
|
|
|
117
117
|
const process = signalizedProcess( this._abortController.signal )
|
|
118
118
|
.then( () => this._beforeRender() )
|
|
119
119
|
.then( () => this._renderTemplate() )
|
|
120
|
-
// Dispatch the render event after the template is ready
|
|
121
120
|
.then( () => this._onTemplateReady() )
|
|
122
121
|
.then( () => this._renderChildren() )
|
|
123
122
|
.then( () => this._afterRender() );
|
|
@@ -152,14 +151,13 @@ export function createNestedTemplatedElementView( {
|
|
|
152
151
|
|
|
153
152
|
await renderTwigTemplate( {
|
|
154
153
|
view: this,
|
|
155
|
-
signal: this._abortController?.signal
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
...context,
|
|
154
|
+
signal: this._abortController?.signal,
|
|
155
|
+
renderState,
|
|
156
|
+
buildContext: ( resolvedSettings ) => ( {
|
|
157
|
+
id: model.get( 'id' ),
|
|
158
|
+
type,
|
|
159
|
+
settings: resolvedSettings,
|
|
160
|
+
base_styles: element.base_styles_dictionary,
|
|
163
161
|
editor_attributes: buildEditorAttributes( model ),
|
|
164
162
|
editor_classes: buildEditorClasses( model ),
|
|
165
163
|
} ),
|
|
@@ -199,18 +197,12 @@ export function createNestedTemplatedElementView( {
|
|
|
199
197
|
},
|
|
200
198
|
|
|
201
199
|
async _renderChildren() {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
this.children.each( ( childView: ElementView ) => {
|
|
207
|
-
if ( childView._currentRenderPromise ) {
|
|
208
|
-
renderPromises.push( childView._currentRenderPromise );
|
|
209
|
-
}
|
|
200
|
+
await renderChildrenWithOptimization( {
|
|
201
|
+
children: this.children,
|
|
202
|
+
domUpdateWasSkipped: renderState.cacheState.domUpdateWasSkipped,
|
|
203
|
+
renderChildren: () => parentRenderChildren.call( this ),
|
|
210
204
|
} );
|
|
211
205
|
|
|
212
|
-
await Promise.all( renderPromises );
|
|
213
|
-
|
|
214
206
|
this._removeChildrenPlaceholder();
|
|
215
207
|
},
|
|
216
208
|
|
|
@@ -6,7 +6,9 @@ import { createElementViewClassDeclaration } from './create-element-type';
|
|
|
6
6
|
import {
|
|
7
7
|
createAfterRender,
|
|
8
8
|
createBeforeRender,
|
|
9
|
-
|
|
9
|
+
createTwigRenderState,
|
|
10
|
+
renderChildrenWithOptimization,
|
|
11
|
+
renderTwigTemplate,
|
|
10
12
|
type TwigViewInterface,
|
|
11
13
|
} from './twig-rendering-utils';
|
|
12
14
|
import {
|
|
@@ -67,17 +69,10 @@ export function createTemplatedElementView( {
|
|
|
67
69
|
}: CreateTemplatedElementTypeOptions ): typeof ElementView {
|
|
68
70
|
const BaseView = createElementViewClassDeclaration();
|
|
69
71
|
|
|
70
|
-
const {
|
|
71
|
-
type,
|
|
72
|
-
renderer,
|
|
73
|
-
element,
|
|
74
|
-
} );
|
|
72
|
+
const renderState = createTwigRenderState( { renderer, element } );
|
|
75
73
|
|
|
76
74
|
return class extends BaseView {
|
|
77
|
-
|
|
78
|
-
#childrenRenderPromises: Promise< void >[] = [];
|
|
79
|
-
#lastResolvedSettingsHash: string | null = null;
|
|
80
|
-
#domUpdateWasSkipped = false;
|
|
75
|
+
_abortController: AbortController | null = null;
|
|
81
76
|
|
|
82
77
|
getTemplateType() {
|
|
83
78
|
return 'twig';
|
|
@@ -100,14 +95,14 @@ export function createTemplatedElementView( {
|
|
|
100
95
|
}
|
|
101
96
|
|
|
102
97
|
invalidateRenderCache() {
|
|
103
|
-
|
|
98
|
+
renderState.cacheState.invalidate();
|
|
104
99
|
}
|
|
105
100
|
|
|
106
101
|
render() {
|
|
107
|
-
this
|
|
108
|
-
this
|
|
102
|
+
this._abortController?.abort();
|
|
103
|
+
this._abortController = new AbortController();
|
|
109
104
|
|
|
110
|
-
const process = signalizedProcess( this
|
|
105
|
+
const process = signalizedProcess( this._abortController.signal )
|
|
111
106
|
.then( () => this._beforeRender() )
|
|
112
107
|
.then( () => this._renderTemplate() )
|
|
113
108
|
.then( () => this._renderChildren() )
|
|
@@ -119,92 +114,27 @@ export function createTemplatedElementView( {
|
|
|
119
114
|
}
|
|
120
115
|
|
|
121
116
|
async _renderChildren() {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.#rerenderExistingChildren();
|
|
127
|
-
} else {
|
|
128
|
-
super._renderChildren();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
this.#collectChildrenRenderPromises();
|
|
132
|
-
await this._waitForChildrenToComplete();
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
#shouldReuseChildren() {
|
|
136
|
-
return this.#domUpdateWasSkipped && this.children?.length > 0;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
#rerenderExistingChildren() {
|
|
140
|
-
this.children?.each( ( childView: ElementView ) => {
|
|
141
|
-
childView.render();
|
|
142
|
-
} );
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
#collectChildrenRenderPromises() {
|
|
146
|
-
this.children?.each( ( childView: ElementView ) => {
|
|
147
|
-
if ( childView._currentRenderPromise ) {
|
|
148
|
-
this.#childrenRenderPromises.push( childView._currentRenderPromise );
|
|
149
|
-
}
|
|
117
|
+
await renderChildrenWithOptimization( {
|
|
118
|
+
children: this.children,
|
|
119
|
+
domUpdateWasSkipped: renderState.cacheState.domUpdateWasSkipped,
|
|
120
|
+
renderChildren: () => super._renderChildren(),
|
|
150
121
|
} );
|
|
151
122
|
}
|
|
152
123
|
|
|
153
|
-
async _waitForChildrenToComplete() {
|
|
154
|
-
if ( this.#childrenRenderPromises.length > 0 ) {
|
|
155
|
-
await Promise.all( this.#childrenRenderPromises );
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
124
|
async _renderTemplate() {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
} )
|
|
174
|
-
.then( async ( settings ) => {
|
|
175
|
-
const settingsHash = JSON.stringify( settings );
|
|
176
|
-
const settingsChanged = settingsHash !== this.#lastResolvedSettingsHash;
|
|
177
|
-
|
|
178
|
-
if ( ! settingsChanged && this.isRendered ) {
|
|
179
|
-
this.#domUpdateWasSkipped = true;
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
this.#domUpdateWasSkipped = false;
|
|
183
|
-
|
|
184
|
-
this.#lastResolvedSettingsHash = settingsHash;
|
|
185
|
-
|
|
186
|
-
const context = {
|
|
187
|
-
id: this.model.get( 'id' ),
|
|
188
|
-
type,
|
|
189
|
-
settings,
|
|
190
|
-
base_styles: baseStylesDictionary,
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
return renderer.render( templateKey, context );
|
|
194
|
-
} )
|
|
195
|
-
.then( ( html ) => {
|
|
196
|
-
if ( html === null ) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
this.$el.html( html );
|
|
201
|
-
} );
|
|
202
|
-
|
|
203
|
-
await process.execute();
|
|
204
|
-
|
|
205
|
-
this.bindUIElements();
|
|
206
|
-
|
|
207
|
-
this.triggerMethod( 'render:template' );
|
|
125
|
+
await renderTwigTemplate( {
|
|
126
|
+
view: this,
|
|
127
|
+
signal: this._abortController?.signal,
|
|
128
|
+
renderState,
|
|
129
|
+
buildContext: ( resolvedSettings ) => ( {
|
|
130
|
+
id: this.model.get( 'id' ),
|
|
131
|
+
type,
|
|
132
|
+
settings: resolvedSettings,
|
|
133
|
+
base_styles: element.base_styles_dictionary,
|
|
134
|
+
} ),
|
|
135
|
+
attachContent: ( html: string ) => this.$el.html( html ),
|
|
136
|
+
afterSettingsResolve: ( settings ) => this.afterSettingsResolve( settings ),
|
|
137
|
+
} );
|
|
208
138
|
}
|
|
209
139
|
|
|
210
140
|
afterSettingsResolve( settings: { [ key: string ]: unknown } ) {
|
|
@@ -13,10 +13,10 @@ const createUnionPropType = ( keys: string[] ): PropType =>
|
|
|
13
13
|
} ) as PropType;
|
|
14
14
|
|
|
15
15
|
describe( 'isInlineEditingAllowed', () => {
|
|
16
|
-
it( 'should allow inline editing for html prop values', () => {
|
|
16
|
+
it( 'should allow inline editing for html-v2 prop values', () => {
|
|
17
17
|
expect(
|
|
18
18
|
isInlineEditingAllowed( {
|
|
19
|
-
rawValue: { $$type: 'html', value: 'Hello' },
|
|
19
|
+
rawValue: { $$type: 'html-v2', value: { content: 'Hello', children: [] } },
|
|
20
20
|
propTypeFromSchema: null,
|
|
21
21
|
} )
|
|
22
22
|
).toBe( true );
|
|
@@ -40,11 +40,11 @@ describe( 'isInlineEditingAllowed', () => {
|
|
|
40
40
|
).toBe( false );
|
|
41
41
|
} );
|
|
42
42
|
|
|
43
|
-
it( 'should allow when value is unset but schema key is html', () => {
|
|
43
|
+
it( 'should allow when value is unset but schema key is html-v2', () => {
|
|
44
44
|
expect(
|
|
45
45
|
isInlineEditingAllowed( {
|
|
46
46
|
rawValue: undefined,
|
|
47
|
-
propTypeFromSchema: createPlainPropType( 'html' ),
|
|
47
|
+
propTypeFromSchema: createPlainPropType( 'html-v2' ),
|
|
48
48
|
} )
|
|
49
49
|
).toBe( true );
|
|
50
50
|
} );
|
|
@@ -67,16 +67,16 @@ describe( 'isInlineEditingAllowed', () => {
|
|
|
67
67
|
).toBe( false );
|
|
68
68
|
} );
|
|
69
69
|
|
|
70
|
-
it( 'should allow when value is unset and union schema includes html', () => {
|
|
70
|
+
it( 'should allow when value is unset and union schema includes html-v2', () => {
|
|
71
71
|
expect(
|
|
72
72
|
isInlineEditingAllowed( {
|
|
73
73
|
rawValue: undefined,
|
|
74
|
-
propTypeFromSchema: createUnionPropType( [ 'dynamic', 'html' ] ),
|
|
74
|
+
propTypeFromSchema: createUnionPropType( [ 'dynamic', 'html-v2' ] ),
|
|
75
75
|
} )
|
|
76
76
|
).toBe( true );
|
|
77
77
|
} );
|
|
78
78
|
|
|
79
|
-
it( 'should disallow when value is unset and union schema does not include html/string', () => {
|
|
79
|
+
it( 'should disallow when value is unset and union schema does not include html-v2/string', () => {
|
|
80
80
|
expect(
|
|
81
81
|
isInlineEditingAllowed( {
|
|
82
82
|
rawValue: undefined,
|
|
@@ -56,9 +56,6 @@ export const CanvasInlineEditor = ( {
|
|
|
56
56
|
<InlineEditingOverlay expectedTag={ expectedTag } rootElement={ rootElement } id={ id } />
|
|
57
57
|
<style>
|
|
58
58
|
{ `
|
|
59
|
-
.${ EDITOR_WRAPPER_SELECTOR }, .${ EDITOR_WRAPPER_SELECTOR } > * {
|
|
60
|
-
height: 100%;
|
|
61
|
-
}
|
|
62
59
|
.ProseMirror > * {
|
|
63
60
|
height: 100%;
|
|
64
61
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { createRoot, type Root } from 'react-dom/client';
|
|
3
3
|
import { getContainer, getElementLabel, getElementType } from '@elementor/editor-elements';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
htmlV2PropTypeUtil,
|
|
6
|
+
parseHtmlChildren,
|
|
7
|
+
type PropType,
|
|
8
|
+
type PropValue,
|
|
9
|
+
stringPropTypeUtil,
|
|
10
|
+
} from '@elementor/editor-props';
|
|
5
11
|
import { __privateRunCommandSync as runCommandSync, getCurrentEditMode, undoable } from '@elementor/editor-v1-adapters';
|
|
6
12
|
import { __ } from '@wordpress/i18n';
|
|
7
13
|
|
|
@@ -122,12 +128,18 @@ export default class InlineEditingReplacement extends ReplacementBase {
|
|
|
122
128
|
getExtractedContentValue() {
|
|
123
129
|
const propValue = this.getInlineEditablePropValue();
|
|
124
130
|
|
|
125
|
-
return
|
|
131
|
+
return htmlV2PropTypeUtil.extract( propValue )?.content ?? '';
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
setContentValue( value: string | null ) {
|
|
129
135
|
const settingKey = this.getInlineEditablePropertyName();
|
|
130
|
-
const
|
|
136
|
+
const html = value || '';
|
|
137
|
+
const parsed = parseHtmlChildren( html );
|
|
138
|
+
|
|
139
|
+
const valueToSave = htmlV2PropTypeUtil.create( {
|
|
140
|
+
content: parsed.content || null,
|
|
141
|
+
children: parsed.children,
|
|
142
|
+
} );
|
|
131
143
|
|
|
132
144
|
undoable(
|
|
133
145
|
{
|
|
@@ -162,12 +174,12 @@ export default class InlineEditingReplacement extends ReplacementBase {
|
|
|
162
174
|
}
|
|
163
175
|
|
|
164
176
|
if ( propType.kind === 'union' ) {
|
|
165
|
-
|
|
166
|
-
return htmlPropTypeUtil.key;
|
|
167
|
-
}
|
|
177
|
+
const textKeys = [ htmlV2PropTypeUtil.key, stringPropTypeUtil.key ];
|
|
168
178
|
|
|
169
|
-
|
|
170
|
-
|
|
179
|
+
for ( const key of textKeys ) {
|
|
180
|
+
if ( propType.prop_types[ key ] ) {
|
|
181
|
+
return key;
|
|
182
|
+
}
|
|
171
183
|
}
|
|
172
184
|
|
|
173
185
|
return null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { htmlV2PropTypeUtil, type PropType, stringPropTypeUtil } from '@elementor/editor-props';
|
|
2
2
|
|
|
3
3
|
type InlineEditingEligibilityArgs = {
|
|
4
4
|
rawValue: unknown;
|
|
@@ -9,8 +9,10 @@ const hasKey = ( propType: PropType ): propType is PropType & { key: unknown } =
|
|
|
9
9
|
return 'key' in propType;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
const TEXT_PROP_TYPE_KEYS = new Set( [ htmlV2PropTypeUtil.key, stringPropTypeUtil.key ] );
|
|
13
|
+
|
|
12
14
|
const isCoreTextPropTypeKey = ( key: unknown ): boolean => {
|
|
13
|
-
return
|
|
15
|
+
return ( TEXT_PROP_TYPE_KEYS as Set< unknown > ).has( key );
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
const isAllowedBySchema = ( propTypeFromSchema: PropType | null ): boolean => {
|
|
@@ -26,9 +28,7 @@ const isAllowedBySchema = ( propTypeFromSchema: PropType | null ): boolean => {
|
|
|
26
28
|
return false;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
return
|
|
30
|
-
propTypeFromSchema.prop_types[ htmlPropTypeUtil.key ] || propTypeFromSchema.prop_types[ stringPropTypeUtil.key ]
|
|
31
|
-
);
|
|
31
|
+
return [ ...TEXT_PROP_TYPE_KEYS ].some( ( key ) => propTypeFromSchema.prop_types[ key ] );
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
export const isInlineEditingAllowed = ( { rawValue, propTypeFromSchema }: InlineEditingEligibilityArgs ): boolean => {
|
|
@@ -36,5 +36,5 @@ export const isInlineEditingAllowed = ( { rawValue, propTypeFromSchema }: Inline
|
|
|
36
36
|
return isAllowedBySchema( propTypeFromSchema );
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
return
|
|
39
|
+
return htmlV2PropTypeUtil.isValid( rawValue ) || stringPropTypeUtil.isValid( rawValue );
|
|
40
40
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { htmlV2PropTypeUtil } from '@elementor/editor-props';
|
|
2
2
|
|
|
3
3
|
import { type ModelExtensions } from './create-nested-templated-element-type';
|
|
4
4
|
import { registerModelExtensions } from './init-legacy-views';
|
|
@@ -23,7 +23,7 @@ const tabModelExtensions: ModelExtensions = {
|
|
|
23
23
|
...paragraphElement,
|
|
24
24
|
settings: {
|
|
25
25
|
...paragraphElement.settings,
|
|
26
|
-
paragraph:
|
|
26
|
+
paragraph: htmlV2PropTypeUtil.create( { content: `Tab ${ position }`, children: [] } ),
|
|
27
27
|
},
|
|
28
28
|
};
|
|
29
29
|
|
|
@@ -5,42 +5,26 @@ import { createPropsResolver, type PropsResolver } from '../renderers/create-pro
|
|
|
5
5
|
import { settingsTransformersRegistry } from '../settings-transformers-registry';
|
|
6
6
|
import { type ElementView, type RenderContext } from './types';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
type TwigElementConfig = Required<
|
|
9
9
|
Pick< V1ElementConfig, 'twig_templates' | 'twig_main_template' | 'atomic_props_schema' | 'base_styles_dictionary' >
|
|
10
10
|
>;
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
[ key: string ]: unknown;
|
|
12
|
+
type TwigRenderState = {
|
|
13
|
+
templateKey: string;
|
|
14
|
+
resolveProps: PropsResolver;
|
|
15
|
+
renderer: DomRenderer;
|
|
16
|
+
cacheState: RenderCacheState;
|
|
18
17
|
};
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
type: string;
|
|
19
|
+
type CreateTwigRenderStateOptions = {
|
|
22
20
|
renderer: DomRenderer;
|
|
23
21
|
element: TwigElementConfig;
|
|
24
22
|
};
|
|
25
23
|
|
|
26
|
-
export
|
|
27
|
-
templateKey: string;
|
|
28
|
-
baseStylesDictionary: Record< string, unknown >;
|
|
29
|
-
resolveProps: PropsResolver;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export function canBeTwigTemplated( element: Partial< TwigElementConfig > ): element is TwigElementConfig {
|
|
33
|
-
return !! (
|
|
34
|
-
element.atomic_props_schema &&
|
|
35
|
-
element.twig_templates &&
|
|
36
|
-
element.twig_main_template &&
|
|
37
|
-
element.base_styles_dictionary
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function setupTwigRenderer( { renderer, element }: SetupTwigRendererOptions ): SetupTwigRendererResult {
|
|
24
|
+
export function createTwigRenderState( { renderer, element }: CreateTwigRenderStateOptions ): TwigRenderState {
|
|
42
25
|
const templateKey = element.twig_main_template;
|
|
43
|
-
|
|
26
|
+
|
|
27
|
+
const cacheState = createRenderCacheState();
|
|
44
28
|
|
|
45
29
|
Object.entries( element.twig_templates ).forEach( ( [ key, template ] ) => {
|
|
46
30
|
renderer.register( key, template );
|
|
@@ -51,7 +35,7 @@ export function setupTwigRenderer( { renderer, element }: SetupTwigRendererOptio
|
|
|
51
35
|
schema: element.atomic_props_schema,
|
|
52
36
|
} );
|
|
53
37
|
|
|
54
|
-
return { templateKey,
|
|
38
|
+
return { templateKey, resolveProps, renderer, cacheState };
|
|
55
39
|
}
|
|
56
40
|
|
|
57
41
|
export interface TwigViewInterface extends Omit< ElementView, 'getResolverRenderContext' > {
|
|
@@ -72,60 +56,87 @@ export function createAfterRender< TView extends TwigViewInterface >( view: TVie
|
|
|
72
56
|
view.triggerMethod( 'render', view );
|
|
73
57
|
}
|
|
74
58
|
|
|
59
|
+
type RenderCacheState = {
|
|
60
|
+
lastResolvedSettingsHash: string | null;
|
|
61
|
+
domUpdateWasSkipped: boolean;
|
|
62
|
+
invalidate: () => void;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export function createRenderCacheState(): RenderCacheState {
|
|
66
|
+
return {
|
|
67
|
+
lastResolvedSettingsHash: null,
|
|
68
|
+
domUpdateWasSkipped: false,
|
|
69
|
+
invalidate() {
|
|
70
|
+
this.lastResolvedSettingsHash = null;
|
|
71
|
+
this.domUpdateWasSkipped = false;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type TwigRenderContext = {
|
|
77
|
+
id: string;
|
|
78
|
+
type: string;
|
|
79
|
+
settings: Record< string, unknown >;
|
|
80
|
+
base_styles: Record< string, unknown >;
|
|
81
|
+
[ key: string ]: unknown;
|
|
82
|
+
};
|
|
83
|
+
|
|
75
84
|
export type RenderTwigTemplateOptions< TView extends TwigViewInterface > = {
|
|
76
85
|
view: TView;
|
|
77
|
-
signal
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
baseStylesDictionary: Record< string, unknown >;
|
|
81
|
-
type: string;
|
|
82
|
-
renderer: DomRenderer;
|
|
83
|
-
buildContext?: ( context: TwigRenderContext ) => TwigRenderContext;
|
|
86
|
+
signal?: AbortSignal;
|
|
87
|
+
renderState: TwigRenderState;
|
|
88
|
+
buildContext: ( resolvedSettings: Record< string, unknown > ) => TwigRenderContext;
|
|
84
89
|
attachContent: ( html: string ) => void;
|
|
90
|
+
afterSettingsResolve?: ( settings: Record< string, unknown > ) => Record< string, unknown >;
|
|
85
91
|
};
|
|
86
92
|
|
|
87
93
|
export async function renderTwigTemplate< TView extends TwigViewInterface >( {
|
|
88
94
|
view,
|
|
89
95
|
signal,
|
|
90
|
-
|
|
91
|
-
templateKey,
|
|
92
|
-
baseStylesDictionary,
|
|
93
|
-
type,
|
|
94
|
-
renderer,
|
|
96
|
+
renderState,
|
|
95
97
|
buildContext,
|
|
96
98
|
attachContent,
|
|
99
|
+
afterSettingsResolve,
|
|
97
100
|
}: RenderTwigTemplateOptions< TView > ): Promise< void > {
|
|
98
101
|
view.triggerMethod( 'before:render:template' );
|
|
102
|
+
const { resolveProps, cacheState, renderer, templateKey } = renderState;
|
|
99
103
|
|
|
100
|
-
if ( signal
|
|
104
|
+
if ( signal?.aborted ) {
|
|
101
105
|
return;
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
const settings = view.model.get( 'settings' ).toJSON();
|
|
105
|
-
|
|
109
|
+
let resolvedSettings = await resolveProps( {
|
|
106
110
|
props: settings,
|
|
107
111
|
signal,
|
|
108
112
|
renderContext: view.getResolverRenderContext?.(),
|
|
109
113
|
} );
|
|
110
114
|
|
|
111
|
-
if ( signal
|
|
115
|
+
if ( signal?.aborted ) {
|
|
112
116
|
return;
|
|
113
117
|
}
|
|
114
118
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
settings: resolvedSettings,
|
|
119
|
-
base_styles: baseStylesDictionary,
|
|
120
|
-
};
|
|
119
|
+
if ( afterSettingsResolve ) {
|
|
120
|
+
resolvedSettings = afterSettingsResolve( resolvedSettings );
|
|
121
|
+
}
|
|
121
122
|
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
const settingsHash = JSON.stringify( resolvedSettings );
|
|
124
|
+
const settingsChanged = settingsHash !== cacheState.lastResolvedSettingsHash;
|
|
125
|
+
|
|
126
|
+
if ( ! settingsChanged && view.isRendered ) {
|
|
127
|
+
cacheState.domUpdateWasSkipped = true;
|
|
128
|
+
view.bindUIElements();
|
|
129
|
+
view.triggerMethod( 'render:template' );
|
|
130
|
+
return;
|
|
124
131
|
}
|
|
125
132
|
|
|
133
|
+
cacheState.domUpdateWasSkipped = false;
|
|
134
|
+
cacheState.lastResolvedSettingsHash = settingsHash;
|
|
135
|
+
|
|
136
|
+
const context = buildContext( resolvedSettings );
|
|
126
137
|
const html = await renderer.render( templateKey, context );
|
|
127
138
|
|
|
128
|
-
if ( signal
|
|
139
|
+
if ( signal?.aborted ) {
|
|
129
140
|
return;
|
|
130
141
|
}
|
|
131
142
|
|
|
@@ -134,3 +145,51 @@ export async function renderTwigTemplate< TView extends TwigViewInterface >( {
|
|
|
134
145
|
view.bindUIElements();
|
|
135
146
|
view.triggerMethod( 'render:template' );
|
|
136
147
|
}
|
|
148
|
+
|
|
149
|
+
export type ChildrenCollection = ElementView[ 'children' ];
|
|
150
|
+
|
|
151
|
+
export function collectChildrenRenderPromises( children: ChildrenCollection | undefined ): Promise< void >[] {
|
|
152
|
+
const promises: Promise< void >[] = [];
|
|
153
|
+
|
|
154
|
+
children?.each( ( childView: ElementView ) => {
|
|
155
|
+
if ( childView._currentRenderPromise ) {
|
|
156
|
+
promises.push( childView._currentRenderPromise );
|
|
157
|
+
}
|
|
158
|
+
} );
|
|
159
|
+
|
|
160
|
+
return promises;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type RenderChildrenOptions = {
|
|
164
|
+
children: ChildrenCollection | undefined;
|
|
165
|
+
domUpdateWasSkipped: boolean;
|
|
166
|
+
renderChildren: () => void;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export async function renderChildrenWithOptimization( {
|
|
170
|
+
children,
|
|
171
|
+
domUpdateWasSkipped,
|
|
172
|
+
renderChildren,
|
|
173
|
+
}: RenderChildrenOptions ): Promise< void > {
|
|
174
|
+
// Safe side: when children is empty, fall back to original renderChildren function to handle emptyView.
|
|
175
|
+
const shouldReuseChildren = domUpdateWasSkipped && !! children?.length;
|
|
176
|
+
|
|
177
|
+
if ( shouldReuseChildren ) {
|
|
178
|
+
rerenderExistingChildViews( children );
|
|
179
|
+
} else {
|
|
180
|
+
renderChildren();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const promises = collectChildrenRenderPromises( children );
|
|
184
|
+
await waitForChildrenToComplete( promises );
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function rerenderExistingChildViews( children: ChildrenCollection | undefined ) {
|
|
188
|
+
children?.each( ( childView ) => childView.render() );
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function waitForChildrenToComplete( promises: Promise< void >[] ): Promise< void > {
|
|
192
|
+
if ( promises.length > 0 ) {
|
|
193
|
+
await Promise.all( promises );
|
|
194
|
+
}
|
|
195
|
+
}
|
package/src/legacy/types.ts
CHANGED
|
@@ -35,5 +35,8 @@ export const outputSchema = {
|
|
|
35
35
|
'The built XML structure as a string. Must use this XML after completion of building the composition, it contains real IDs.'
|
|
36
36
|
)
|
|
37
37
|
.optional(),
|
|
38
|
-
llm_instructions: z
|
|
38
|
+
llm_instructions: z
|
|
39
|
+
.string()
|
|
40
|
+
.describe( 'Instructions what to do next, Important to follow these instructions!' )
|
|
41
|
+
.optional(),
|
|
39
42
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createTransformer } from '../create-transformer';
|
|
2
|
+
|
|
3
|
+
type HtmlV2Value = {
|
|
4
|
+
content: string | null;
|
|
5
|
+
children: Record< string, unknown >;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const htmlV2Transformer = createTransformer( ( value: HtmlV2Value ) => {
|
|
9
|
+
return value?.content ?? '';
|
|
10
|
+
} );
|