@nordcraft/runtime 1.0.0
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/README.md +5 -0
- package/dist/api/createAPI.d.ts +20 -0
- package/dist/api/createAPI.js +319 -0
- package/dist/api/createAPI.js.map +1 -0
- package/dist/api/createAPIv2.d.ts +7 -0
- package/dist/api/createAPIv2.js +686 -0
- package/dist/api/createAPIv2.js.map +1 -0
- package/dist/components/createComponent.d.ts +13 -0
- package/dist/components/createComponent.js +216 -0
- package/dist/components/createComponent.js.map +1 -0
- package/dist/components/createElement.d.ts +3 -0
- package/dist/components/createElement.js +208 -0
- package/dist/components/createElement.js.map +1 -0
- package/dist/components/createNode.d.ts +22 -0
- package/dist/components/createNode.js +272 -0
- package/dist/components/createNode.js.map +1 -0
- package/dist/components/createSlot.d.ts +3 -0
- package/dist/components/createSlot.js +49 -0
- package/dist/components/createSlot.js.map +1 -0
- package/dist/components/createText.d.ts +23 -0
- package/dist/components/createText.js +68 -0
- package/dist/components/createText.js.map +1 -0
- package/dist/components/createText.test.d.ts +1 -0
- package/dist/components/createText.test.js +113 -0
- package/dist/components/createText.test.js.map +1 -0
- package/dist/components/renderComponent.d.ts +34 -0
- package/dist/components/renderComponent.js +66 -0
- package/dist/components/renderComponent.js.map +1 -0
- package/dist/context/isContextProvider.d.ts +2 -0
- package/dist/context/isContextProvider.js +5 -0
- package/dist/context/isContextProvider.js.map +1 -0
- package/dist/context/subscribeToContext.d.ts +4 -0
- package/dist/context/subscribeToContext.js +93 -0
- package/dist/context/subscribeToContext.js.map +1 -0
- package/dist/custom-components/components.d.ts +1 -0
- package/dist/custom-components/components.js +2 -0
- package/dist/custom-components/components.js.map +1 -0
- package/dist/custom-components/toddle-portal.d.ts +6 -0
- package/dist/custom-components/toddle-portal.js +20 -0
- package/dist/custom-components/toddle-portal.js.map +1 -0
- package/dist/custom-element/ToddleComponent.d.ts +37 -0
- package/dist/custom-element/ToddleComponent.js +244 -0
- package/dist/custom-element/ToddleComponent.js.map +1 -0
- package/dist/custom-element/defineComponents.d.ts +26 -0
- package/dist/custom-element/defineComponents.js +42 -0
- package/dist/custom-element/defineComponents.js.map +1 -0
- package/dist/custom-element.main.d.ts +3 -0
- package/dist/custom-element.main.esm.js +266 -0
- package/dist/custom-element.main.esm.js.map +7 -0
- package/dist/custom-element.main.js +14 -0
- package/dist/custom-element.main.js.map +1 -0
- package/dist/debug/logState.d.ts +4 -0
- package/dist/debug/logState.js +19 -0
- package/dist/debug/logState.js.map +1 -0
- package/dist/editor/drag-drop/dragEnded.d.ts +2 -0
- package/dist/editor/drag-drop/dragEnded.js +56 -0
- package/dist/editor/drag-drop/dragEnded.js.map +1 -0
- package/dist/editor/drag-drop/dragMove.d.ts +3 -0
- package/dist/editor/drag-drop/dragMove.js +74 -0
- package/dist/editor/drag-drop/dragMove.js.map +1 -0
- package/dist/editor/drag-drop/dragReorder.d.ts +3 -0
- package/dist/editor/drag-drop/dragReorder.js +92 -0
- package/dist/editor/drag-drop/dragReorder.js.map +1 -0
- package/dist/editor/drag-drop/dragStarted.d.ts +9 -0
- package/dist/editor/drag-drop/dragStarted.js +100 -0
- package/dist/editor/drag-drop/dragStarted.js.map +1 -0
- package/dist/editor/drag-drop/dropHighlight.d.ts +16 -0
- package/dist/editor/drag-drop/dropHighlight.js +50 -0
- package/dist/editor/drag-drop/dropHighlight.js.map +1 -0
- package/dist/editor/drag-drop/getInsertAreas.d.ts +20 -0
- package/dist/editor/drag-drop/getInsertAreas.js +220 -0
- package/dist/editor/drag-drop/getInsertAreas.js.map +1 -0
- package/dist/editor-preview.main.d.ts +19 -0
- package/dist/editor-preview.main.js +1303 -0
- package/dist/editor-preview.main.js.map +1 -0
- package/dist/events/handleAction.d.ts +3 -0
- package/dist/events/handleAction.js +307 -0
- package/dist/events/handleAction.js.map +1 -0
- package/dist/page.main.d.ts +7 -0
- package/dist/page.main.esm.js +8 -0
- package/dist/page.main.esm.js.map +7 -0
- package/dist/page.main.js +395 -0
- package/dist/page.main.js.map +1 -0
- package/dist/signal/signal.d.ts +19 -0
- package/dist/signal/signal.js +65 -0
- package/dist/signal/signal.js.map +1 -0
- package/dist/styles/style.d.ts +4 -0
- package/dist/styles/style.js +196 -0
- package/dist/styles/style.js.map +1 -0
- package/dist/utils/BatchQueue.d.ts +10 -0
- package/dist/utils/BatchQueue.js +25 -0
- package/dist/utils/BatchQueue.js.map +1 -0
- package/dist/utils/createFormulaCache.d.ts +3 -0
- package/dist/utils/createFormulaCache.js +81 -0
- package/dist/utils/createFormulaCache.js.map +1 -0
- package/dist/utils/findNearestLine.d.ts +13 -0
- package/dist/utils/findNearestLine.js +74 -0
- package/dist/utils/findNearestLine.js.map +1 -0
- package/dist/utils/findNearestLine.test.d.ts +1 -0
- package/dist/utils/findNearestLine.test.js +59 -0
- package/dist/utils/findNearestLine.test.js.map +1 -0
- package/dist/utils/getDragData.d.ts +1 -0
- package/dist/utils/getDragData.js +10 -0
- package/dist/utils/getDragData.js.map +1 -0
- package/dist/utils/getElementTagName.d.ts +3 -0
- package/dist/utils/getElementTagName.js +7 -0
- package/dist/utils/getElementTagName.js.map +1 -0
- package/dist/utils/nodes.d.ts +21 -0
- package/dist/utils/nodes.js +89 -0
- package/dist/utils/nodes.js.map +1 -0
- package/dist/utils/omitStyle.d.ts +2 -0
- package/dist/utils/omitStyle.js +13 -0
- package/dist/utils/omitStyle.js.map +1 -0
- package/dist/utils/rectHasPoint.d.ts +2 -0
- package/dist/utils/rectHasPoint.js +4 -0
- package/dist/utils/rectHasPoint.js.map +1 -0
- package/dist/utils/setAttribute.d.ts +4 -0
- package/dist/utils/setAttribute.js +57 -0
- package/dist/utils/setAttribute.js.map +1 -0
- package/dist/utils/tryStartViewTransition.d.ts +5 -0
- package/dist/utils/tryStartViewTransition.js +14 -0
- package/dist/utils/tryStartViewTransition.js.map +1 -0
- package/dist/utils/url.d.ts +2 -0
- package/dist/utils/url.js +36 -0
- package/dist/utils/url.js.map +1 -0
- package/package.json +25 -0
- package/src/api/createAPI.ts +375 -0
- package/src/api/createAPIv2.ts +931 -0
- package/src/components/createComponent.ts +280 -0
- package/src/components/createElement.ts +240 -0
- package/src/components/createNode.ts +381 -0
- package/src/components/createSlot.ts +61 -0
- package/src/components/createText.test.ts +117 -0
- package/src/components/createText.ts +104 -0
- package/src/components/renderComponent.ts +145 -0
- package/src/context/isContextProvider.ts +12 -0
- package/src/context/subscribeToContext.ts +135 -0
- package/src/custom-components/components.ts +1 -0
- package/src/custom-components/toddle-portal.ts +19 -0
- package/src/custom-element/ToddleComponent.ts +315 -0
- package/src/custom-element/defineComponents.ts +65 -0
- package/src/custom-element.main.ts +24 -0
- package/src/debug/logState.ts +30 -0
- package/src/editor/drag-drop/dragEnded.ts +75 -0
- package/src/editor/drag-drop/dragMove.ts +95 -0
- package/src/editor/drag-drop/dragReorder.ts +137 -0
- package/src/editor/drag-drop/dragStarted.ts +145 -0
- package/src/editor/drag-drop/dropHighlight.ts +82 -0
- package/src/editor/drag-drop/getInsertAreas.ts +235 -0
- package/src/editor/types.d.ts +36 -0
- package/src/editor-preview.main.ts +1782 -0
- package/src/events/handleAction.ts +387 -0
- package/src/page.main.ts +489 -0
- package/src/signal/signal.ts +74 -0
- package/src/styles/style.ts +254 -0
- package/src/types.d.ts +93 -0
- package/src/utils/BatchQueue.ts +24 -0
- package/src/utils/createFormulaCache.ts +96 -0
- package/src/utils/findNearestLine.test.ts +65 -0
- package/src/utils/findNearestLine.ts +92 -0
- package/src/utils/getDragData.ts +11 -0
- package/src/utils/getElementTagName.ts +14 -0
- package/src/utils/nodes.ts +125 -0
- package/src/utils/omitStyle.ts +19 -0
- package/src/utils/rectHasPoint.ts +5 -0
- package/src/utils/setAttribute.ts +56 -0
- package/src/utils/tryStartViewTransition.ts +32 -0
- package/src/utils/url.ts +45 -0
|
@@ -0,0 +1,1303 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
|
3
|
+
/* eslint-disable no-case-declarations */
|
|
4
|
+
/* eslint-disable no-fallthrough */
|
|
5
|
+
import { isLegacyApi } from '@nordcraft/core/dist/api/api';
|
|
6
|
+
import { isPageComponent } from '@nordcraft/core/dist/component/isPageComponent';
|
|
7
|
+
import { applyFormula } from '@nordcraft/core/dist/formula/formula';
|
|
8
|
+
import { valueFormula } from '@nordcraft/core/dist/formula/formulaUtils';
|
|
9
|
+
import { getClassName } from '@nordcraft/core/dist/styling/className';
|
|
10
|
+
import { getThemeCss } from '@nordcraft/core/dist/styling/theme';
|
|
11
|
+
import { theme } from '@nordcraft/core/dist/styling/theme.const';
|
|
12
|
+
import { mapObject, omitKeys } from '@nordcraft/core/dist/utils/collections';
|
|
13
|
+
import * as libActions from '@nordcraft/std-lib/dist/actions';
|
|
14
|
+
import * as libFormulas from '@nordcraft/std-lib/dist/formulas';
|
|
15
|
+
import fastDeepEqual from 'fast-deep-equal';
|
|
16
|
+
import { createLegacyAPI } from './api/createAPI';
|
|
17
|
+
import { createAPI } from './api/createAPIv2';
|
|
18
|
+
import { createNode } from './components/createNode';
|
|
19
|
+
import { isContextProvider } from './context/isContextProvider';
|
|
20
|
+
import { dragEnded } from './editor/drag-drop/dragEnded';
|
|
21
|
+
import { dragMove } from './editor/drag-drop/dragMove';
|
|
22
|
+
import { dragReorder } from './editor/drag-drop/dragReorder';
|
|
23
|
+
import { dragStarted } from './editor/drag-drop/dragStarted';
|
|
24
|
+
import { handleAction } from './events/handleAction';
|
|
25
|
+
import { signal } from './signal/signal';
|
|
26
|
+
import { insertStyles, styleToCss } from './styles/style';
|
|
27
|
+
import { createFormulaCache } from './utils/createFormulaCache';
|
|
28
|
+
import { getNodeAndAncestors, isNodeOrAncestorConditional } from './utils/nodes';
|
|
29
|
+
import { omitSubnodeStyleForComponent } from './utils/omitStyle';
|
|
30
|
+
import { rectHasPoint } from './utils/rectHasPoint';
|
|
31
|
+
/**
|
|
32
|
+
* Styles required for rendering the same exact text again somewhere else (on a overlay rect in the editor)
|
|
33
|
+
*/
|
|
34
|
+
var TextNodeComputedStyles;
|
|
35
|
+
(function (TextNodeComputedStyles) {
|
|
36
|
+
// Caret color is important as it is the only visible part of the text node (when text is not highlighted)
|
|
37
|
+
TextNodeComputedStyles["CARET_COLOR"] = "caret-color";
|
|
38
|
+
TextNodeComputedStyles["FONT_FAMILY"] = "font-family";
|
|
39
|
+
TextNodeComputedStyles["FONT_SIZE"] = "font-size";
|
|
40
|
+
TextNodeComputedStyles["FONT_WEIGHT"] = "font-weight";
|
|
41
|
+
TextNodeComputedStyles["FONT_STYLE"] = "font-style";
|
|
42
|
+
TextNodeComputedStyles["FONT_VARIANT"] = "font-variant";
|
|
43
|
+
TextNodeComputedStyles["FONT_STRETCH"] = "font-stretch";
|
|
44
|
+
TextNodeComputedStyles["LINE_HEIGHT"] = "line-height";
|
|
45
|
+
TextNodeComputedStyles["TEXT_ALIGN"] = "text-align";
|
|
46
|
+
TextNodeComputedStyles["TEXT_TRANSFORM"] = "text-transform";
|
|
47
|
+
TextNodeComputedStyles["LETTER_SPACING"] = "letter-spacing";
|
|
48
|
+
TextNodeComputedStyles["WHITE_SPACE"] = "white-space";
|
|
49
|
+
TextNodeComputedStyles["WORD_SPACING"] = "word-spacing";
|
|
50
|
+
TextNodeComputedStyles["TEXT_INDENT"] = "text-indent";
|
|
51
|
+
TextNodeComputedStyles["TEXT_OVERFLOW"] = "text-overflow";
|
|
52
|
+
TextNodeComputedStyles["TEXT_RENDERING"] = "text-rendering";
|
|
53
|
+
TextNodeComputedStyles["WORD_BREAK"] = "word-break";
|
|
54
|
+
TextNodeComputedStyles["WORD_WRAP"] = "word-wrap";
|
|
55
|
+
TextNodeComputedStyles["DIRECTION"] = "direction";
|
|
56
|
+
TextNodeComputedStyles["UNICODE_BIDI"] = "unicode-bidi";
|
|
57
|
+
TextNodeComputedStyles["VERTICAL_ALIGN"] = "vertical-align";
|
|
58
|
+
})(TextNodeComputedStyles || (TextNodeComputedStyles = {}));
|
|
59
|
+
let env;
|
|
60
|
+
export const initGlobalObject = ({ formulas, actions, }) => {
|
|
61
|
+
env = {
|
|
62
|
+
isServer: false,
|
|
63
|
+
branchName: window.__toddle.branch,
|
|
64
|
+
request: undefined,
|
|
65
|
+
runtime: 'preview',
|
|
66
|
+
logErrors: true,
|
|
67
|
+
};
|
|
68
|
+
window.toddle = (() => {
|
|
69
|
+
const legacyActions = {};
|
|
70
|
+
const legacyFormulas = {};
|
|
71
|
+
const argumentInputDataList = {};
|
|
72
|
+
const toddle = {
|
|
73
|
+
isEqual: fastDeepEqual,
|
|
74
|
+
errors: [],
|
|
75
|
+
formulas,
|
|
76
|
+
actions,
|
|
77
|
+
registerAction: (name, handler) => {
|
|
78
|
+
if (legacyActions[name]) {
|
|
79
|
+
console.error('There already exists an action with the name ', name);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
legacyActions[name] = handler;
|
|
83
|
+
},
|
|
84
|
+
getAction: (name) => legacyActions[name],
|
|
85
|
+
registerFormula: (name, handler, getArgumentInputData) => {
|
|
86
|
+
if (legacyFormulas[name]) {
|
|
87
|
+
console.error('There already exists a formula with the name ', name);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
legacyFormulas[name] = handler;
|
|
91
|
+
if (getArgumentInputData) {
|
|
92
|
+
argumentInputDataList[name] = getArgumentInputData;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
getFormula: (name) => legacyFormulas[name],
|
|
96
|
+
getCustomAction: (name, packageName) => {
|
|
97
|
+
return (toddle.actions[packageName ?? window.__toddle.project]?.[name] ??
|
|
98
|
+
toddle.actions[window.__toddle.project]?.[name]);
|
|
99
|
+
},
|
|
100
|
+
getCustomFormula: (name, packageName) => {
|
|
101
|
+
return (toddle.formulas[packageName ?? window.__toddle.project]?.[name] ??
|
|
102
|
+
toddle.formulas[window.__toddle.project]?.[name]);
|
|
103
|
+
},
|
|
104
|
+
// eslint-disable-next-line max-params
|
|
105
|
+
getArgumentInputData: (formulaName, args, argIndex, data) => argumentInputDataList[formulaName]?.(args, argIndex, data) || data,
|
|
106
|
+
data: {},
|
|
107
|
+
eventLog: [],
|
|
108
|
+
project: window.__toddle.project,
|
|
109
|
+
branch: window.__toddle.branch,
|
|
110
|
+
commit: window.__toddle.commit,
|
|
111
|
+
components: window.__toddle.components,
|
|
112
|
+
pageState: window.__toddle.pageState,
|
|
113
|
+
locationSignal: signal({
|
|
114
|
+
query: {},
|
|
115
|
+
params: {},
|
|
116
|
+
}),
|
|
117
|
+
env,
|
|
118
|
+
};
|
|
119
|
+
return toddle;
|
|
120
|
+
})();
|
|
121
|
+
// load default formulas and actions
|
|
122
|
+
Object.entries(libFormulas).forEach(([name, module]) => window.toddle.registerFormula('@toddle/' + name, module.default, 'getArgumentInputData' in module
|
|
123
|
+
? module.getArgumentInputData
|
|
124
|
+
: undefined));
|
|
125
|
+
Object.entries(libActions).forEach(([name, module]) => window.toddle.registerAction('@toddle/' + name, module.default));
|
|
126
|
+
};
|
|
127
|
+
// imported by "/.toddle/preview" (see worker/src/preview.ts)
|
|
128
|
+
export const createRoot = (domNode = document.getElementById('App')) => {
|
|
129
|
+
if (!domNode) {
|
|
130
|
+
throw new Error('Cant find root domNode');
|
|
131
|
+
}
|
|
132
|
+
const isInputTarget = (event) => {
|
|
133
|
+
const target = event.target;
|
|
134
|
+
if (target instanceof HTMLElement) {
|
|
135
|
+
if (target.tagName === 'INPUT' ||
|
|
136
|
+
target.tagName === 'TEXTAREA' ||
|
|
137
|
+
target.tagName === 'SELECT' ||
|
|
138
|
+
target.tagName === 'STYLE-EDITOR') {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
if (target.contentEditable?.toLocaleLowerCase() === 'true') {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
};
|
|
147
|
+
insertTheme(document.head, theme);
|
|
148
|
+
const dataSignal = signal({
|
|
149
|
+
Location: {
|
|
150
|
+
query: {},
|
|
151
|
+
params: {},
|
|
152
|
+
page: '/',
|
|
153
|
+
path: '/',
|
|
154
|
+
hash: '',
|
|
155
|
+
},
|
|
156
|
+
Attributes: {},
|
|
157
|
+
Variables: {},
|
|
158
|
+
});
|
|
159
|
+
let ctxDataSignal;
|
|
160
|
+
let ctx = null;
|
|
161
|
+
let mode = 'design';
|
|
162
|
+
// Signal for overriding conditional elements when they're
|
|
163
|
+
// selected in design mode and for reverting back to normal
|
|
164
|
+
// in test mode
|
|
165
|
+
const showSignal = signal({
|
|
166
|
+
displayedNodes: [],
|
|
167
|
+
testMode: false,
|
|
168
|
+
});
|
|
169
|
+
window.toddle._preview = { showSignal };
|
|
170
|
+
document.body.setAttribute('data-mode', 'design');
|
|
171
|
+
let components = null;
|
|
172
|
+
let packageComponents = null;
|
|
173
|
+
const getAllComponents = () => [
|
|
174
|
+
...(components ?? []),
|
|
175
|
+
...(packageComponents ?? []),
|
|
176
|
+
];
|
|
177
|
+
let component = null;
|
|
178
|
+
let selectedNodeId = null;
|
|
179
|
+
let highlightedNodeId = null;
|
|
180
|
+
let styleVariantSelection = null;
|
|
181
|
+
let routeSignal = null;
|
|
182
|
+
let dragState = null;
|
|
183
|
+
let animationState = null;
|
|
184
|
+
let altKey = false;
|
|
185
|
+
let metaKey = false;
|
|
186
|
+
let previewStyleAnimationFrame = -1;
|
|
187
|
+
/**
|
|
188
|
+
* Modifies all link nodes on a component
|
|
189
|
+
* NOTE: alters in place
|
|
190
|
+
*/
|
|
191
|
+
const updateComponentLinks = (component) => {
|
|
192
|
+
// Find all links and add target="_blank" to them
|
|
193
|
+
Object.entries(component.nodes ?? {}).forEach(([_, node]) => {
|
|
194
|
+
if (node.type === 'element' && node.tag === 'a') {
|
|
195
|
+
node.attrs['target'] = valueFormula('_blank');
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return component;
|
|
199
|
+
};
|
|
200
|
+
window.addEventListener('message', (message) => {
|
|
201
|
+
if (!message.isTrusted) {
|
|
202
|
+
console.error('UNTRUSTED MESSAGE');
|
|
203
|
+
}
|
|
204
|
+
switch (message.data?.type) {
|
|
205
|
+
case 'update':
|
|
206
|
+
{
|
|
207
|
+
if (highlightedNodeId) {
|
|
208
|
+
const highlightedNode = getDOMNodeFromNodeId(highlightedNodeId);
|
|
209
|
+
if (highlightedNode) {
|
|
210
|
+
window.parent?.postMessage({
|
|
211
|
+
type: 'highlightRect',
|
|
212
|
+
rect: getRectData(highlightedNode),
|
|
213
|
+
}, '*');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (selectedNodeId) {
|
|
217
|
+
const selectedNode = getDOMNodeFromNodeId(selectedNodeId);
|
|
218
|
+
if (selectedNode) {
|
|
219
|
+
window.parent?.postMessage({
|
|
220
|
+
type: 'selectionRect',
|
|
221
|
+
rect: getRectData(selectedNode),
|
|
222
|
+
}, '*');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
case 'component': {
|
|
228
|
+
if (!message.data.component) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (message.data.component.name != component?.name) {
|
|
232
|
+
showSignal.cleanSubscribers();
|
|
233
|
+
}
|
|
234
|
+
component = updateComponentLinks(message.data.component);
|
|
235
|
+
if (components && packageComponents && ctx) {
|
|
236
|
+
// Since we're not receiving the current component in
|
|
237
|
+
// "components" updates (see `SetupCanvas` action)
|
|
238
|
+
// we need to manually update the component in components
|
|
239
|
+
const componentIndex = components.findIndex((c) => c.name === component.name);
|
|
240
|
+
if (componentIndex !== -1) {
|
|
241
|
+
components[componentIndex] = component;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
components.push(component);
|
|
245
|
+
}
|
|
246
|
+
ctx.components = getAllComponents();
|
|
247
|
+
}
|
|
248
|
+
dataSignal.update((data) => {
|
|
249
|
+
const newData = {
|
|
250
|
+
...data,
|
|
251
|
+
Location: data.Location
|
|
252
|
+
? {
|
|
253
|
+
...data.Location,
|
|
254
|
+
path: component?.page ?? '',
|
|
255
|
+
}
|
|
256
|
+
: undefined,
|
|
257
|
+
// Ensure that URL parameters are only available for pages and not components
|
|
258
|
+
'URL parameters': component?.route
|
|
259
|
+
? data['URL parameters']
|
|
260
|
+
: undefined,
|
|
261
|
+
};
|
|
262
|
+
return newData;
|
|
263
|
+
});
|
|
264
|
+
update();
|
|
265
|
+
if (highlightedNodeId) {
|
|
266
|
+
const highlightedNode = getDOMNodeFromNodeId(highlightedNodeId);
|
|
267
|
+
if (highlightedNode) {
|
|
268
|
+
window.parent?.postMessage({
|
|
269
|
+
type: 'highlightRect',
|
|
270
|
+
rect: getRectData(highlightedNode),
|
|
271
|
+
}, '*');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (selectedNodeId) {
|
|
275
|
+
if (styleVariantSelection) {
|
|
276
|
+
updateSelectedStyleVariant(styleVariantSelection.styleVariantIndex);
|
|
277
|
+
}
|
|
278
|
+
const selectedNode = getDOMNodeFromNodeId(selectedNodeId);
|
|
279
|
+
if (selectedNode) {
|
|
280
|
+
window.parent?.postMessage({
|
|
281
|
+
type: 'selectionRect',
|
|
282
|
+
rect: getRectData(selectedNode),
|
|
283
|
+
}, '*');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
case 'components': {
|
|
289
|
+
if (Array.isArray(message.data.components)) {
|
|
290
|
+
components = message.data.components.map(updateComponentLinks);
|
|
291
|
+
const allComponents = getAllComponents();
|
|
292
|
+
if (ctx) {
|
|
293
|
+
ctx.components = allComponents;
|
|
294
|
+
}
|
|
295
|
+
updateStyle();
|
|
296
|
+
update();
|
|
297
|
+
}
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
case 'packages': {
|
|
301
|
+
if (message.data.packages) {
|
|
302
|
+
packageComponents = Object.values(message.data.packages ?? {})
|
|
303
|
+
.flatMap((p) => Object.values(p.components).map((c) => ({
|
|
304
|
+
...c,
|
|
305
|
+
name: `${p.manifest.name}/${c.name}`,
|
|
306
|
+
})))
|
|
307
|
+
.map(updateComponentLinks);
|
|
308
|
+
const allComponents = getAllComponents();
|
|
309
|
+
if (ctx) {
|
|
310
|
+
ctx.components = allComponents;
|
|
311
|
+
}
|
|
312
|
+
updateStyle();
|
|
313
|
+
update();
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
case 'theme': {
|
|
318
|
+
insertTheme(document.head, message.data.theme);
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case 'mode': {
|
|
322
|
+
mode = message.data.mode;
|
|
323
|
+
document.body.setAttribute('data-mode', message.data.mode);
|
|
324
|
+
updateConditionalElements();
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
case 'attrs': {
|
|
328
|
+
if (message.data.attrs &&
|
|
329
|
+
fastDeepEqual(message.data.attrs, dataSignal.get().Attributes) ===
|
|
330
|
+
false) {
|
|
331
|
+
const attrs = message.data.attrs;
|
|
332
|
+
dataSignal.update((data) => {
|
|
333
|
+
// TODO: We should figure out if "Props" is used anywhere and get rid of it if it's not
|
|
334
|
+
const newData = {
|
|
335
|
+
...data,
|
|
336
|
+
Location: data.Location && component?.page
|
|
337
|
+
? {
|
|
338
|
+
...data.Location,
|
|
339
|
+
query: attrs,
|
|
340
|
+
}
|
|
341
|
+
: data.Location,
|
|
342
|
+
Props: attrs ?? {},
|
|
343
|
+
};
|
|
344
|
+
return newData;
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
case 'selection': {
|
|
350
|
+
if (selectedNodeId !== message.data.selectedNodeId) {
|
|
351
|
+
selectedNodeId = message.data.selectedNodeId ?? null;
|
|
352
|
+
clearSelectedStyleVariant();
|
|
353
|
+
updateConditionalElements();
|
|
354
|
+
const selectedNode = getDOMNodeFromNodeId(selectedNodeId);
|
|
355
|
+
window.parent?.postMessage({
|
|
356
|
+
type: 'selectionRect',
|
|
357
|
+
rect: getRectData(selectedNode),
|
|
358
|
+
}, '*');
|
|
359
|
+
const node = getDOMNodeFromNodeId(selectedNodeId);
|
|
360
|
+
const element = component?.nodes[node?.getAttribute('data-node-id') ?? ''];
|
|
361
|
+
if (node &&
|
|
362
|
+
element &&
|
|
363
|
+
element.type === 'text' &&
|
|
364
|
+
element.value.type === 'value') {
|
|
365
|
+
const computedStyle = window.getComputedStyle(node);
|
|
366
|
+
window.parent?.postMessage({
|
|
367
|
+
type: 'textComputedStyle',
|
|
368
|
+
computedStyle: Object.fromEntries(Object.values(TextNodeComputedStyles).map((style) => [
|
|
369
|
+
style,
|
|
370
|
+
computedStyle.getPropertyValue(style),
|
|
371
|
+
])),
|
|
372
|
+
}, '*');
|
|
373
|
+
}
|
|
374
|
+
else if (node && node.getAttribute('data-node-type') !== 'text') {
|
|
375
|
+
// Reset computed style on blur
|
|
376
|
+
window.parent?.postMessage({
|
|
377
|
+
type: 'textComputedStyle',
|
|
378
|
+
computedStyle: {},
|
|
379
|
+
}, '*');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
case 'update_inner_text': {
|
|
385
|
+
const { innerText } = message.data;
|
|
386
|
+
const selectedNode = getDOMNodeFromNodeId(selectedNodeId);
|
|
387
|
+
if (selectedNode &&
|
|
388
|
+
selectedNode.getAttribute('data-node-type') === 'text') {
|
|
389
|
+
;
|
|
390
|
+
selectedNode.innerText = innerText;
|
|
391
|
+
window.parent?.postMessage({
|
|
392
|
+
type: 'selectionRect',
|
|
393
|
+
rect: getRectData(selectedNode),
|
|
394
|
+
}, '*');
|
|
395
|
+
}
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
case 'highlight': {
|
|
399
|
+
if (highlightedNodeId !== message.data.highlightedNodeId) {
|
|
400
|
+
highlightedNodeId = message.data.highlightedNodeId ?? null;
|
|
401
|
+
const highlightedNode = getDOMNodeFromNodeId(highlightedNodeId);
|
|
402
|
+
window.parent?.postMessage({
|
|
403
|
+
type: 'highlightRect',
|
|
404
|
+
rect: getRectData(highlightedNode),
|
|
405
|
+
}, '*');
|
|
406
|
+
}
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
case 'mousemove':
|
|
410
|
+
if (dragState && !dragState.destroying) {
|
|
411
|
+
const { x, y } = message.data;
|
|
412
|
+
dragState.lastCursorPosition = { x, y };
|
|
413
|
+
const draggingInsideContainer = rectHasPoint(dragState.initialContainer.getBoundingClientRect(), { x, y });
|
|
414
|
+
if (draggingInsideContainer && !metaKey) {
|
|
415
|
+
dragReorder(dragState);
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
dragMove(dragState, metaKey
|
|
419
|
+
? [dragState.element]
|
|
420
|
+
: [dragState.element, dragState.initialContainer]);
|
|
421
|
+
}
|
|
422
|
+
dragState.element.style.setProperty('translate', `${x - dragState.offset.x}px ${y - dragState.offset.y}px`);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
case 'click':
|
|
426
|
+
case 'dblclick':
|
|
427
|
+
if (mode === 'test' || !component) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const { x, y, type } = message.data;
|
|
431
|
+
const elementsAtPoint = document.elementsFromPoint(x, y);
|
|
432
|
+
let element = elementsAtPoint.find((elem) => {
|
|
433
|
+
const id = elem.getAttribute('data-id');
|
|
434
|
+
if (typeof id !== 'string' ||
|
|
435
|
+
component === null ||
|
|
436
|
+
elem.getAttribute('data-component')) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
const nodeId = getNodeId(component, id.split('.').slice(1));
|
|
440
|
+
const node = nodeId ? component?.nodes[nodeId] : undefined;
|
|
441
|
+
if (!node) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
if (elem.getAttribute('data-node-type') === 'text') {
|
|
445
|
+
return (
|
|
446
|
+
// Select text nodes if the meta key is pressed or the text node is double-clicked
|
|
447
|
+
metaKey ||
|
|
448
|
+
type === 'dblclick' ||
|
|
449
|
+
// Select text nodes if the selected node is a text node. This is useful as the user is likely in a text editing mode
|
|
450
|
+
getDOMNodeFromNodeId(selectedNodeId)?.getAttribute('data-node-type') === 'text');
|
|
451
|
+
}
|
|
452
|
+
return true;
|
|
453
|
+
});
|
|
454
|
+
// Bubble selection to the topmost parent that has the exact same size as the element.
|
|
455
|
+
// This is important for drag and drop as you are often left with childless parents after dragging.
|
|
456
|
+
while (element?.parentElement &&
|
|
457
|
+
element.getAttribute('data-node-id') !== 'root' &&
|
|
458
|
+
fastDeepEqual(element.getBoundingClientRect().toJSON(), element.parentElement.getBoundingClientRect().toJSON()) &&
|
|
459
|
+
element.getAttribute('data-node-type') !== 'text') {
|
|
460
|
+
element = element.parentElement;
|
|
461
|
+
}
|
|
462
|
+
const id = element?.getAttribute('data-id') ?? null;
|
|
463
|
+
if (type === 'click' && id !== selectedNodeId) {
|
|
464
|
+
if (message.data.metaKey) {
|
|
465
|
+
// Figure out if the clicked element is a text element
|
|
466
|
+
// or if one of its descendants is a text element
|
|
467
|
+
const root = component.nodes.root;
|
|
468
|
+
if (root && id) {
|
|
469
|
+
const nodeLookup = getNodeAndAncestors(component, root, id);
|
|
470
|
+
if (nodeLookup?.node.type === 'text') {
|
|
471
|
+
window.parent?.postMessage({
|
|
472
|
+
type: 'selection',
|
|
473
|
+
selectedNodeId: id,
|
|
474
|
+
}, '*');
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
const firstTextChild = nodeLookup?.node.type === 'element'
|
|
478
|
+
? nodeLookup.node.children.find((c) => component?.nodes[c]?.type === 'text')
|
|
479
|
+
: undefined;
|
|
480
|
+
if (firstTextChild) {
|
|
481
|
+
window.parent?.postMessage({
|
|
482
|
+
type: 'selection',
|
|
483
|
+
selectedNodeId: `${id}.0`,
|
|
484
|
+
}, '*');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
window.parent?.postMessage({
|
|
491
|
+
type: 'selection',
|
|
492
|
+
selectedNodeId: id,
|
|
493
|
+
}, '*');
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
else if (type === 'mousemove' && id !== highlightedNodeId) {
|
|
497
|
+
window.parent?.postMessage({
|
|
498
|
+
type: 'highlight',
|
|
499
|
+
highlightedNodeId: id,
|
|
500
|
+
}, '*');
|
|
501
|
+
}
|
|
502
|
+
else if (type === 'dblclick' &&
|
|
503
|
+
id &&
|
|
504
|
+
// We only allow dblclick --> navigation if we're not in test mode
|
|
505
|
+
mode === 'design') {
|
|
506
|
+
// Figure out if the clicked element is a component
|
|
507
|
+
const root = component.nodes.root;
|
|
508
|
+
if (root) {
|
|
509
|
+
const nodeLookup = getNodeAndAncestors(component, root, id);
|
|
510
|
+
if (nodeLookup?.node.type === 'component' &&
|
|
511
|
+
nodeLookup.node.name) {
|
|
512
|
+
window.parent?.postMessage({
|
|
513
|
+
type: 'navigate',
|
|
514
|
+
name: nodeLookup.node.name,
|
|
515
|
+
}, '*');
|
|
516
|
+
}
|
|
517
|
+
// Double click on text node should select the text node for editing
|
|
518
|
+
else if (nodeLookup?.node.type === 'text') {
|
|
519
|
+
window.parent?.postMessage({
|
|
520
|
+
type: 'selection',
|
|
521
|
+
selectedNodeId: id,
|
|
522
|
+
}, '*');
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
break;
|
|
527
|
+
case 'style_variant_changed':
|
|
528
|
+
const { variantIndex } = message.data;
|
|
529
|
+
updateSelectedStyleVariant(variantIndex);
|
|
530
|
+
break;
|
|
531
|
+
// We request manually instead of automatic to avoid mutation observer spam.
|
|
532
|
+
// Also, reporting automatically proved unreliable when elements' height was in %
|
|
533
|
+
case 'report_document_scroll_size':
|
|
534
|
+
window.parent?.postMessage({
|
|
535
|
+
type: 'documentScrollSize',
|
|
536
|
+
scrollHeight: domNode.scrollHeight,
|
|
537
|
+
scrollWidth: domNode.scrollWidth,
|
|
538
|
+
}, '*');
|
|
539
|
+
break;
|
|
540
|
+
case 'reload':
|
|
541
|
+
window.location.reload();
|
|
542
|
+
break;
|
|
543
|
+
case 'fetch_api':
|
|
544
|
+
const { apiKey } = message.data;
|
|
545
|
+
dataSignal.update((data) => ({
|
|
546
|
+
...data,
|
|
547
|
+
Apis: {
|
|
548
|
+
...data.Apis,
|
|
549
|
+
[apiKey]: {
|
|
550
|
+
isLoading: true,
|
|
551
|
+
data: null,
|
|
552
|
+
error: null,
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
}));
|
|
556
|
+
ctx?.apis[apiKey]?.fetch({});
|
|
557
|
+
break;
|
|
558
|
+
case 'drag-started':
|
|
559
|
+
const draggedElement = getDOMNodeFromNodeId(selectedNodeId);
|
|
560
|
+
if (!draggedElement || !draggedElement.parentElement) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const repeatedNodes = Array.from(draggedElement.parentElement.children).filter((node) => node instanceof HTMLElement &&
|
|
564
|
+
node.getAttribute('data-id')?.startsWith(selectedNodeId + '('));
|
|
565
|
+
dragState = dragStarted({
|
|
566
|
+
element: draggedElement,
|
|
567
|
+
lastCursorPosition: { x: message.data.x, y: message.data.y },
|
|
568
|
+
repeatedNodes,
|
|
569
|
+
asCopy: altKey,
|
|
570
|
+
});
|
|
571
|
+
if (altKey) {
|
|
572
|
+
const nextRect = dragState.element.getBoundingClientRect();
|
|
573
|
+
dragState.offset.x += nextRect.left - dragState.initialRect.left;
|
|
574
|
+
dragState.offset.y += nextRect.top - dragState.initialRect.top;
|
|
575
|
+
}
|
|
576
|
+
break;
|
|
577
|
+
case 'drag-ended':
|
|
578
|
+
switch (dragState?.mode) {
|
|
579
|
+
case 'reorder':
|
|
580
|
+
const parentDataId = dragState?.initialContainer.getAttribute('data-id');
|
|
581
|
+
const parentNodeId = dragState?.initialContainer.getAttribute('data-node-id');
|
|
582
|
+
if (!parentDataId || !parentNodeId) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
const nextSibling = dragState?.element.nextElementSibling;
|
|
586
|
+
const nextSiblingId = parseInt(nextSibling?.getAttribute('data-id')?.split('.').at(-1) ?? '');
|
|
587
|
+
const rect = dragState?.element?.getBoundingClientRect();
|
|
588
|
+
if (rect &&
|
|
589
|
+
!message.data.canceled &&
|
|
590
|
+
(nextSibling !== dragState?.initialNextSibling ||
|
|
591
|
+
dragState?.copy)) {
|
|
592
|
+
void dragEnded(dragState, false).then(() => {
|
|
593
|
+
window.parent?.postMessage({
|
|
594
|
+
type: 'nodeMoved',
|
|
595
|
+
copy: Boolean(dragState?.copy),
|
|
596
|
+
parent: parentDataId,
|
|
597
|
+
index: !isNaN(nextSiblingId)
|
|
598
|
+
? nextSiblingId
|
|
599
|
+
: component?.nodes[parentNodeId]?.children?.length,
|
|
600
|
+
}, '*');
|
|
601
|
+
dragState = null;
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
void dragEnded(dragState, true).then(() => {
|
|
606
|
+
dragState = null;
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
break;
|
|
610
|
+
case 'insert':
|
|
611
|
+
const selectedPermutation = dragState?.insertAreas?.[dragState?.selectedInsertAreaIndex ?? -1];
|
|
612
|
+
if (selectedPermutation && !message.data.canceled) {
|
|
613
|
+
void dragEnded(dragState, false).then(() => {
|
|
614
|
+
window.parent?.postMessage({
|
|
615
|
+
type: 'nodeMoved',
|
|
616
|
+
copy: Boolean(dragState?.copy),
|
|
617
|
+
parent: selectedPermutation?.parent.getAttribute('data-id'),
|
|
618
|
+
index: selectedPermutation?.index,
|
|
619
|
+
}, '*');
|
|
620
|
+
dragState = null;
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
void dragEnded(dragState, true).then(() => {
|
|
625
|
+
dragState = null;
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
break;
|
|
631
|
+
case 'keydown':
|
|
632
|
+
case 'keyup':
|
|
633
|
+
// If the `altKey` is pressed/released and the user is currently dragging, then restart the drag with/without a copy.
|
|
634
|
+
if (dragState &&
|
|
635
|
+
!dragState.destroying &&
|
|
636
|
+
message.data.altKey !== altKey) {
|
|
637
|
+
const asCopy = message.data.altKey;
|
|
638
|
+
const prevRect = dragState.element.getBoundingClientRect();
|
|
639
|
+
void dragEnded(dragState, true).then(() => {
|
|
640
|
+
if (!dragState)
|
|
641
|
+
return;
|
|
642
|
+
dragState = dragStarted({
|
|
643
|
+
element: dragState.element,
|
|
644
|
+
lastCursorPosition: dragState.lastCursorPosition,
|
|
645
|
+
repeatedNodes: dragState.repeatedNodes,
|
|
646
|
+
asCopy,
|
|
647
|
+
initialContainer: dragState.initialContainer,
|
|
648
|
+
initialNextSibling: dragState.initialNextSibling,
|
|
649
|
+
});
|
|
650
|
+
const nextRect = dragState.element.getBoundingClientRect();
|
|
651
|
+
dragState.offset.x += nextRect.left - prevRect.left;
|
|
652
|
+
dragState.offset.y += nextRect.top - prevRect.top;
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
altKey = message.data.altKey;
|
|
656
|
+
metaKey = message.data.metaKey;
|
|
657
|
+
break;
|
|
658
|
+
case 'get_computed_style':
|
|
659
|
+
const selectedNode = getDOMNodeFromNodeId(selectedNodeId);
|
|
660
|
+
if (!selectedNode) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const { styles } = message.data;
|
|
664
|
+
const computedStyle = window.getComputedStyle(selectedNode);
|
|
665
|
+
window.parent?.postMessage({
|
|
666
|
+
type: 'computedStyle',
|
|
667
|
+
computedStyle: Object.fromEntries(styles.map((style) => [
|
|
668
|
+
style,
|
|
669
|
+
computedStyle.getPropertyValue(style),
|
|
670
|
+
])),
|
|
671
|
+
}, '*');
|
|
672
|
+
break;
|
|
673
|
+
case 'set_timeline_keyframes':
|
|
674
|
+
const { keyframes } = message.data;
|
|
675
|
+
document.head.querySelector('[data-timeline-keyframes]')?.remove();
|
|
676
|
+
if (!keyframes) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const styleElem = document.createElement('style');
|
|
680
|
+
styleElem.appendChild(document.createTextNode(`
|
|
681
|
+
@keyframes preview_timeline {
|
|
682
|
+
${Object.values(keyframes)
|
|
683
|
+
.map(({ key, value, position, easing }) => `${position * 100}% {
|
|
684
|
+
${key}: ${value};
|
|
685
|
+
${easing ? `animation-timing-function: ${easing};` : ''}
|
|
686
|
+
}`)
|
|
687
|
+
.join('\n')}
|
|
688
|
+
}
|
|
689
|
+
`));
|
|
690
|
+
styleElem.setAttribute('data-timeline-keyframes', '');
|
|
691
|
+
document.head.appendChild(styleElem);
|
|
692
|
+
window.parent?.postMessage({
|
|
693
|
+
type: 'selectionRect',
|
|
694
|
+
rect: getRectData(getDOMNodeFromNodeId(selectedNodeId) ?? document.body),
|
|
695
|
+
}, '*');
|
|
696
|
+
break;
|
|
697
|
+
case 'set_timeline_time':
|
|
698
|
+
const { time, timingFunction, fillMode } = message.data;
|
|
699
|
+
const prevAnimatedElement = getDOMNodeFromNodeId(animationState?.animatedElementId ?? '');
|
|
700
|
+
animationState = {
|
|
701
|
+
animatedElementId: time !== null ? selectedNodeId : null,
|
|
702
|
+
time,
|
|
703
|
+
timingFunction,
|
|
704
|
+
fillMode,
|
|
705
|
+
};
|
|
706
|
+
const animatedElement = getDOMNodeFromNodeId(animationState.animatedElementId);
|
|
707
|
+
if (prevAnimatedElement === null ||
|
|
708
|
+
prevAnimatedElement !== animatedElement) {
|
|
709
|
+
prevAnimatedElement?.classList.remove('editor-preview-timeline');
|
|
710
|
+
}
|
|
711
|
+
if (animatedElement && time !== null) {
|
|
712
|
+
animatedElement.classList.add('editor-preview-timeline');
|
|
713
|
+
document.body.style.setProperty('--editor-timeline-position', `${time}s`);
|
|
714
|
+
document.body.style.setProperty('--editor-timeline-timing-function', timingFunction ?? 'ease');
|
|
715
|
+
document.body.style.setProperty('--editor-timeline-fill-mode', fillMode ?? 'none');
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
document.body.style.removeProperty('--editor-timeline-position');
|
|
719
|
+
document.body.style.removeProperty('--editor-timeline-timing-function');
|
|
720
|
+
document.body.style.removeProperty('--editor-timeline-fill-mode');
|
|
721
|
+
update();
|
|
722
|
+
}
|
|
723
|
+
window.parent?.postMessage({
|
|
724
|
+
type: 'selectionRect',
|
|
725
|
+
rect: getRectData(animatedElement),
|
|
726
|
+
}, '*');
|
|
727
|
+
break;
|
|
728
|
+
case 'preview_style':
|
|
729
|
+
const { styles: previewStyleStyles } = message.data;
|
|
730
|
+
cancelAnimationFrame(previewStyleAnimationFrame);
|
|
731
|
+
previewStyleAnimationFrame = requestAnimationFrame(() => {
|
|
732
|
+
// Update or create a new style tag and set the given styles with important priority
|
|
733
|
+
let styleTag = document.head.querySelector('[data-id="selected-node-styles"]');
|
|
734
|
+
// Cleanup when null styles are sent
|
|
735
|
+
if (!previewStyleStyles) {
|
|
736
|
+
styleTag?.remove();
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (!styleTag) {
|
|
740
|
+
styleTag = document.createElement('style');
|
|
741
|
+
styleTag.setAttribute('data-id', 'selected-node-styles');
|
|
742
|
+
document.head.appendChild(styleTag);
|
|
743
|
+
}
|
|
744
|
+
const previewStyles = Object.entries(previewStyleStyles)
|
|
745
|
+
.map(([key, value]) => `${key}: ${value} !important;`)
|
|
746
|
+
.join('\n');
|
|
747
|
+
styleTag.innerHTML = `[data-id="${selectedNodeId}"], [data-id="${selectedNodeId}"] ~ [data-id^="${selectedNodeId}("] {
|
|
748
|
+
${previewStyles}
|
|
749
|
+
transition: none !important;
|
|
750
|
+
}`;
|
|
751
|
+
window.parent?.postMessage({
|
|
752
|
+
type: 'selectionRect',
|
|
753
|
+
rect: getRectData(getDOMNodeFromNodeId(selectedNodeId)),
|
|
754
|
+
}, '*');
|
|
755
|
+
});
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
const updateStyle = () => {
|
|
760
|
+
if (component) {
|
|
761
|
+
insertStyles(document.head, component, getAllComponents());
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
/**
|
|
765
|
+
* Get the current representation of the component, but with
|
|
766
|
+
* updated conditions based on selectedNodeId and updated
|
|
767
|
+
* styling based on styleVariantSelection
|
|
768
|
+
*/
|
|
769
|
+
const getCurrentComponent = () => {
|
|
770
|
+
const _component = structuredClone(component);
|
|
771
|
+
if (!_component) {
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
if (mode === 'design') {
|
|
775
|
+
if (selectedNodeId !== null) {
|
|
776
|
+
const root = _component?.nodes.root;
|
|
777
|
+
if (root) {
|
|
778
|
+
const nodeLookup = getNodeAndAncestors(_component, root, selectedNodeId);
|
|
779
|
+
if (nodeLookup) {
|
|
780
|
+
if (isNodeOrAncestorConditional(nodeLookup)) {
|
|
781
|
+
// Show the selected node and all its ancestors by
|
|
782
|
+
// removing their "show" condition
|
|
783
|
+
nodeLookup.node.condition = undefined;
|
|
784
|
+
nodeLookup.ancestors.forEach((a) => (a.condition = undefined));
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return _component;
|
|
791
|
+
};
|
|
792
|
+
const updateSelectedStyleVariant = (variantIndex) => {
|
|
793
|
+
clearSelectedStyleVariant();
|
|
794
|
+
if (selectedNodeId !== null && typeof variantIndex === 'number') {
|
|
795
|
+
styleVariantSelection = {
|
|
796
|
+
nodeId: selectedNodeId,
|
|
797
|
+
styleVariantIndex: variantIndex,
|
|
798
|
+
};
|
|
799
|
+
const root = component?.nodes.root;
|
|
800
|
+
if (root && component) {
|
|
801
|
+
const nodeLookup = getNodeAndAncestors(component, root, selectedNodeId);
|
|
802
|
+
if (nodeLookup) {
|
|
803
|
+
if (styleVariantSelection?.nodeId === selectedNodeId &&
|
|
804
|
+
(nodeLookup.node.type === 'element' ||
|
|
805
|
+
nodeLookup.node.type === 'component')) {
|
|
806
|
+
const selectedStyleVariant = nodeLookup.node.variants?.[styleVariantSelection.styleVariantIndex] ?? { style: {} };
|
|
807
|
+
// Add a style element specific to the selected element which
|
|
808
|
+
// is only applied when the preview is in design mode
|
|
809
|
+
const styleElem = document.createElement('style');
|
|
810
|
+
styleElem.setAttribute('data-hash', selectedNodeId);
|
|
811
|
+
styleElem.appendChild(document.createTextNode(`
|
|
812
|
+
body[data-mode="design"] [data-id="${selectedNodeId}"] {
|
|
813
|
+
${styleToCss({
|
|
814
|
+
...nodeLookup.node.style,
|
|
815
|
+
...selectedStyleVariant.style,
|
|
816
|
+
})}
|
|
817
|
+
}
|
|
818
|
+
`));
|
|
819
|
+
const existingStyleElement = document.head.querySelector(`[data-hash="${selectedNodeId}"]`);
|
|
820
|
+
if (existingStyleElement) {
|
|
821
|
+
document.head.removeChild(existingStyleElement);
|
|
822
|
+
}
|
|
823
|
+
document.head.appendChild(styleElem);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
const selectedNode = getDOMNodeFromNodeId(selectedNodeId);
|
|
829
|
+
window.parent?.postMessage({
|
|
830
|
+
type: 'selectionRect',
|
|
831
|
+
rect: getRectData(selectedNode),
|
|
832
|
+
}, '*');
|
|
833
|
+
};
|
|
834
|
+
const update = () => {
|
|
835
|
+
const _component = getCurrentComponent();
|
|
836
|
+
if (!_component || !components || !packageComponents) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
let { Attributes, Variables, Contexts } = dataSignal.get();
|
|
840
|
+
if (fastDeepEqual(ctx?.component.attributes, _component.attributes) === false) {
|
|
841
|
+
Attributes = mapObject(_component.attributes, ([name, { testValue }]) => [
|
|
842
|
+
name,
|
|
843
|
+
testValue,
|
|
844
|
+
]);
|
|
845
|
+
}
|
|
846
|
+
if (_component.route &&
|
|
847
|
+
fastDeepEqual(ctx?.component.route, _component.route) === false) {
|
|
848
|
+
// Subscribe to the route signal so we can preview URL parameter changes in the editor
|
|
849
|
+
routeSignal?.destroy();
|
|
850
|
+
if (_component.route) {
|
|
851
|
+
// Populate initial URL parameters with test data
|
|
852
|
+
window.toddle.locationSignal.update((location) => {
|
|
853
|
+
if (!_component.route)
|
|
854
|
+
return location;
|
|
855
|
+
return {
|
|
856
|
+
...location,
|
|
857
|
+
route: _component.route,
|
|
858
|
+
params: Object.fromEntries(_component.route.path
|
|
859
|
+
.filter((p) => p.type === 'param')
|
|
860
|
+
.map((p) => [p.name, p.testValue])),
|
|
861
|
+
query: mapObject(_component.route.query, ([name, { testValue }]) => [
|
|
862
|
+
name,
|
|
863
|
+
testValue,
|
|
864
|
+
]),
|
|
865
|
+
};
|
|
866
|
+
});
|
|
867
|
+
routeSignal = window.toddle.locationSignal.map(({ query, params }) => {
|
|
868
|
+
return { ...query, ...params };
|
|
869
|
+
});
|
|
870
|
+
routeSignal.subscribe((route) => dataSignal.update((data) => ({
|
|
871
|
+
...data,
|
|
872
|
+
'URL parameters': route,
|
|
873
|
+
Attributes: route,
|
|
874
|
+
})));
|
|
875
|
+
}
|
|
876
|
+
Attributes = mapObject(_component.attributes, ([name, { testValue }]) => [
|
|
877
|
+
name,
|
|
878
|
+
testValue,
|
|
879
|
+
]);
|
|
880
|
+
}
|
|
881
|
+
if (fastDeepEqual(ctx?.component.route?.info?.meta, _component.route?.info?.meta) === false) {
|
|
882
|
+
insertHeadTags(_component.route?.info?.meta ?? {}, {
|
|
883
|
+
component: _component,
|
|
884
|
+
data: { Attributes },
|
|
885
|
+
root: document,
|
|
886
|
+
package: ctx?.package,
|
|
887
|
+
toddle: window.toddle,
|
|
888
|
+
env,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
if (fastDeepEqual(_component.contexts, ctx?.component.contexts) === false) {
|
|
892
|
+
Contexts = (function createStaticContextFromComponent(component, contextProvidersCreated) {
|
|
893
|
+
contextProvidersCreated?.add(component.name);
|
|
894
|
+
return mapObject(component.contexts ?? {}, ([providerName, context]) => {
|
|
895
|
+
if (contextProvidersCreated?.has(providerName)) {
|
|
896
|
+
// Circular dependency detected in context-providers (ie. A -> B -> A -> ...), stop recursion
|
|
897
|
+
return [providerName, {}];
|
|
898
|
+
}
|
|
899
|
+
const providerComponent = getAllComponents().find((c) => c.name === providerName);
|
|
900
|
+
if (!providerComponent) {
|
|
901
|
+
console.warn(`Could not find a provider-component named "${providerName}" in files`);
|
|
902
|
+
return [providerName, {}];
|
|
903
|
+
}
|
|
904
|
+
// TODO: Should we also run APIs for the provider?
|
|
905
|
+
const formulaContext = {
|
|
906
|
+
data: {
|
|
907
|
+
Attributes: mapObject(providerComponent.attributes, ([name, attr]) => [name, attr.testValue]),
|
|
908
|
+
// Recursively resolve contexts providers before their children to build up the fake context tree in preview mode
|
|
909
|
+
Contexts: createStaticContextFromComponent(providerComponent, contextProvidersCreated ?? new Set()),
|
|
910
|
+
},
|
|
911
|
+
component: providerComponent,
|
|
912
|
+
root: ctx?.root,
|
|
913
|
+
formulaCache: {},
|
|
914
|
+
package: ctx?.package,
|
|
915
|
+
toddle: window.toddle,
|
|
916
|
+
env,
|
|
917
|
+
};
|
|
918
|
+
// Pages can also be context-providers!
|
|
919
|
+
// Exposed formulas can derive their preview output from URL data,
|
|
920
|
+
// so we must populate Url parameters with their test data
|
|
921
|
+
if (providerComponent.route) {
|
|
922
|
+
formulaContext.data['URL parameters'] = {
|
|
923
|
+
...Object.fromEntries(providerComponent.route.path
|
|
924
|
+
.filter((p) => p.type === 'param')
|
|
925
|
+
.map((p) => [p.name, p.testValue])),
|
|
926
|
+
...mapObject(providerComponent.route.query, ([name, { testValue }]) => [name, testValue]),
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
formulaContext.data.Variables = mapObject(providerComponent.variables, ([name, variable]) => [
|
|
930
|
+
name,
|
|
931
|
+
applyFormula(variable.initialValue, formulaContext),
|
|
932
|
+
]);
|
|
933
|
+
return [
|
|
934
|
+
providerName,
|
|
935
|
+
Object.fromEntries(context.formulas.map((formulaName) => {
|
|
936
|
+
const formula = providerComponent.formulas?.[formulaName];
|
|
937
|
+
if (!formula) {
|
|
938
|
+
console.warn(`Could not find formula "${formulaName}" in component "${providerName}"`);
|
|
939
|
+
return [formulaName, null];
|
|
940
|
+
}
|
|
941
|
+
return [
|
|
942
|
+
formulaName,
|
|
943
|
+
applyFormula(formula.formula, formulaContext),
|
|
944
|
+
];
|
|
945
|
+
})),
|
|
946
|
+
];
|
|
947
|
+
});
|
|
948
|
+
})(_component);
|
|
949
|
+
}
|
|
950
|
+
if (fastDeepEqual(_component.variables, ctx?.component.variables) === false) {
|
|
951
|
+
Variables = mapObject(_component.variables, ([name, { initialValue }]) => [
|
|
952
|
+
name,
|
|
953
|
+
applyFormula(initialValue, {
|
|
954
|
+
data: { Attributes, Contexts },
|
|
955
|
+
component: _component,
|
|
956
|
+
root: document,
|
|
957
|
+
package: ctx?.package,
|
|
958
|
+
toddle: window.toddle,
|
|
959
|
+
env,
|
|
960
|
+
}),
|
|
961
|
+
]);
|
|
962
|
+
}
|
|
963
|
+
dataSignal.update((data) => {
|
|
964
|
+
return {
|
|
965
|
+
...data,
|
|
966
|
+
'URL parameters': component && isPageComponent(component)
|
|
967
|
+
? {
|
|
968
|
+
...window.toddle.locationSignal.get().query,
|
|
969
|
+
...window.toddle.locationSignal.get().params,
|
|
970
|
+
}
|
|
971
|
+
: {},
|
|
972
|
+
Attributes,
|
|
973
|
+
Variables,
|
|
974
|
+
Contexts,
|
|
975
|
+
};
|
|
976
|
+
});
|
|
977
|
+
const newCtx = {
|
|
978
|
+
...(ctx ?? createContext(_component, getAllComponents())),
|
|
979
|
+
component: _component,
|
|
980
|
+
};
|
|
981
|
+
for (const api in newCtx.component.apis) {
|
|
982
|
+
// check if the api has changed (ignoring onCompleted and onFailed).
|
|
983
|
+
const apiInstance = newCtx.component.apis[api];
|
|
984
|
+
const previousApiInstance = ctx?.component.apis[api];
|
|
985
|
+
if (isLegacyApi(apiInstance)) {
|
|
986
|
+
if (fastDeepEqual(omitKeys(apiInstance, ['onCompleted', 'onFailed']), previousApiInstance && isLegacyApi(previousApiInstance)
|
|
987
|
+
? omitKeys(previousApiInstance, ['onCompleted', 'onFailed'])
|
|
988
|
+
: (previousApiInstance ?? {})) === false) {
|
|
989
|
+
newCtx.apis[api]?.destroy();
|
|
990
|
+
dataSignal.update((data) => {
|
|
991
|
+
return {
|
|
992
|
+
...data,
|
|
993
|
+
Apis: omitKeys(data.Apis ?? {}, [
|
|
994
|
+
...Object.keys(data.Apis ?? {}).filter(
|
|
995
|
+
// remove any data from an api that is not part of the component
|
|
996
|
+
(key) => !newCtx.component.apis[key]),
|
|
997
|
+
api,
|
|
998
|
+
]),
|
|
999
|
+
};
|
|
1000
|
+
});
|
|
1001
|
+
newCtx.apis[api] = createLegacyAPI(apiInstance, newCtx);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
else {
|
|
1005
|
+
if (!newCtx.apis[api]) {
|
|
1006
|
+
newCtx.apis[api] = createAPI(apiInstance, newCtx);
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
1010
|
+
newCtx.apis[api].update && newCtx.apis[api].update(apiInstance);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (fastDeepEqual(newCtx.component.nodes, ctx?.component?.nodes) === false) {
|
|
1015
|
+
updateStyle();
|
|
1016
|
+
// Remove preview styles automatically when the component changes
|
|
1017
|
+
document.head.querySelector('[data-id="selected-node-styles"]')?.remove();
|
|
1018
|
+
if (fastDeepEqual(omitSubnodeStyleForComponent(newCtx.component), omitSubnodeStyleForComponent(ctx?.component))) {
|
|
1019
|
+
// If we're in here, then the latest update was only a style change, so we should try some optimistic updates
|
|
1020
|
+
Object.keys(newCtx.component.nodes).forEach((nodeId) => {
|
|
1021
|
+
const newNode = newCtx.component.nodes[nodeId];
|
|
1022
|
+
const oldNode = ctx?.component.nodes[nodeId];
|
|
1023
|
+
if ((newNode.type === 'element' || newNode.type === 'component') &&
|
|
1024
|
+
(oldNode?.type === 'element' || oldNode?.type === 'component') &&
|
|
1025
|
+
(!fastDeepEqual(newNode.style, oldNode.style) ||
|
|
1026
|
+
!fastDeepEqual(newNode.variants, oldNode.variants))) {
|
|
1027
|
+
document
|
|
1028
|
+
.querySelectorAll(`[data-node-id="${nodeId}"]`)
|
|
1029
|
+
.forEach((nodeInstance) => {
|
|
1030
|
+
nodeInstance.classList.remove(getClassName([oldNode.style, oldNode.variants]));
|
|
1031
|
+
nodeInstance.classList.add(getClassName([newNode.style, newNode.variants]));
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
else {
|
|
1037
|
+
Array.from(domNode.children).forEach((child) => {
|
|
1038
|
+
if (child.tagName !== 'SCRIPT') {
|
|
1039
|
+
child.remove();
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
// Clear old root signal and create a new one to not keep old signals with previous root around
|
|
1043
|
+
ctxDataSignal?.destroy();
|
|
1044
|
+
ctxDataSignal = dataSignal.map((data) => data);
|
|
1045
|
+
const rootElem = createNode({
|
|
1046
|
+
id: 'root',
|
|
1047
|
+
path: '0',
|
|
1048
|
+
dataSignal: ctxDataSignal,
|
|
1049
|
+
ctx: newCtx,
|
|
1050
|
+
parentElement: domNode,
|
|
1051
|
+
instance: { [newCtx.component.name]: 'root' },
|
|
1052
|
+
});
|
|
1053
|
+
newCtx.component.onLoad?.actions.forEach((action) => {
|
|
1054
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1055
|
+
handleAction(action, dataSignal.get(), newCtx);
|
|
1056
|
+
});
|
|
1057
|
+
rootElem.forEach((elem) => domNode.appendChild(elem));
|
|
1058
|
+
window.parent?.postMessage({
|
|
1059
|
+
type: 'style',
|
|
1060
|
+
time: new Intl.DateTimeFormat('en-GB', {
|
|
1061
|
+
timeStyle: 'long',
|
|
1062
|
+
}).format(new Date()),
|
|
1063
|
+
}, '*');
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
// Rerendering may clear editor-preview-only styles, so we need to reapply them
|
|
1067
|
+
getDOMNodeFromNodeId(animationState?.animatedElementId)?.classList.add('editor-preview-timeline');
|
|
1068
|
+
ctx = newCtx;
|
|
1069
|
+
};
|
|
1070
|
+
const createContext = (component, components) => {
|
|
1071
|
+
const ctx = {
|
|
1072
|
+
component,
|
|
1073
|
+
components,
|
|
1074
|
+
triggerEvent: (event, data) => {
|
|
1075
|
+
window.parent?.postMessage({
|
|
1076
|
+
type: 'component event',
|
|
1077
|
+
event,
|
|
1078
|
+
time: new Intl.DateTimeFormat('en-GB', {
|
|
1079
|
+
timeStyle: 'long',
|
|
1080
|
+
}).format(new Date()),
|
|
1081
|
+
data,
|
|
1082
|
+
}, '*');
|
|
1083
|
+
},
|
|
1084
|
+
dataSignal,
|
|
1085
|
+
root: document,
|
|
1086
|
+
isRootComponent: true,
|
|
1087
|
+
apis: {},
|
|
1088
|
+
children: {},
|
|
1089
|
+
abortSignal: new AbortController().signal,
|
|
1090
|
+
formulaCache: createFormulaCache(component),
|
|
1091
|
+
providers: {},
|
|
1092
|
+
package: undefined,
|
|
1093
|
+
toddle: window.toddle,
|
|
1094
|
+
env,
|
|
1095
|
+
};
|
|
1096
|
+
if (isContextProvider(component)) {
|
|
1097
|
+
// Subscribe to exposed formulas and update the component's data signal
|
|
1098
|
+
const formulaDataSignals = Object.fromEntries(Object.entries(component.formulas ?? {})
|
|
1099
|
+
.filter(([, formula]) => formula.exposeInContext)
|
|
1100
|
+
.map(([name, formula]) => [
|
|
1101
|
+
name,
|
|
1102
|
+
dataSignal.map((data) => applyFormula(formula.formula, {
|
|
1103
|
+
data,
|
|
1104
|
+
component,
|
|
1105
|
+
formulaCache: ctx.formulaCache,
|
|
1106
|
+
root: ctx.root,
|
|
1107
|
+
package: ctx.package,
|
|
1108
|
+
toddle: window.toddle,
|
|
1109
|
+
env,
|
|
1110
|
+
})),
|
|
1111
|
+
]));
|
|
1112
|
+
ctx.providers = {
|
|
1113
|
+
...ctx.providers,
|
|
1114
|
+
[component.name]: {
|
|
1115
|
+
component,
|
|
1116
|
+
formulaDataSignals,
|
|
1117
|
+
ctx,
|
|
1118
|
+
},
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
return ctx;
|
|
1122
|
+
};
|
|
1123
|
+
document.addEventListener('keydown', (event) => {
|
|
1124
|
+
if (isInputTarget(event)) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
switch (event.key) {
|
|
1128
|
+
case 'k':
|
|
1129
|
+
if (event.metaKey) {
|
|
1130
|
+
event.preventDefault();
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
window.parent?.postMessage({
|
|
1134
|
+
type: 'keydown',
|
|
1135
|
+
event: {
|
|
1136
|
+
key: event.key,
|
|
1137
|
+
metaKey: event.metaKey,
|
|
1138
|
+
shiftKey: event.shiftKey,
|
|
1139
|
+
altKey: event.altKey,
|
|
1140
|
+
},
|
|
1141
|
+
}, '*');
|
|
1142
|
+
});
|
|
1143
|
+
document.addEventListener('keyup', (event) => {
|
|
1144
|
+
if (isInputTarget(event)) {
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
window.parent?.postMessage({
|
|
1148
|
+
type: 'keyup',
|
|
1149
|
+
event: {
|
|
1150
|
+
key: event.key,
|
|
1151
|
+
metaKey: event.metaKey,
|
|
1152
|
+
shiftKey: event.shiftKey,
|
|
1153
|
+
altKey: event.altKey,
|
|
1154
|
+
},
|
|
1155
|
+
}, '*');
|
|
1156
|
+
});
|
|
1157
|
+
document.addEventListener('keypress', (event) => {
|
|
1158
|
+
if (isInputTarget(event)) {
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
window.parent?.postMessage({
|
|
1162
|
+
type: 'keypress',
|
|
1163
|
+
event: {
|
|
1164
|
+
key: event.key,
|
|
1165
|
+
metaKey: event.metaKey,
|
|
1166
|
+
shiftKey: event.shiftKey,
|
|
1167
|
+
altKey: event.altKey,
|
|
1168
|
+
},
|
|
1169
|
+
}, '*');
|
|
1170
|
+
});
|
|
1171
|
+
dataSignal.subscribe((data) => {
|
|
1172
|
+
if (component && components && packageComponents && data) {
|
|
1173
|
+
try {
|
|
1174
|
+
window.parent?.postMessage({ type: 'data', data }, '*');
|
|
1175
|
+
}
|
|
1176
|
+
catch {
|
|
1177
|
+
// If we're unable to send the data, let's try to JSON serialize it
|
|
1178
|
+
window.parent?.postMessage({ type: 'data', data: JSON.parse(JSON.stringify(data)) }, '*');
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
const clearSelectedStyleVariant = () => {
|
|
1183
|
+
if (styleVariantSelection) {
|
|
1184
|
+
const styleElem = document.head.querySelector(`[data-hash="${styleVariantSelection.nodeId}"]`);
|
|
1185
|
+
if (styleElem) {
|
|
1186
|
+
document.head.removeChild(styleElem);
|
|
1187
|
+
}
|
|
1188
|
+
styleVariantSelection = null;
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
const updateConditionalElements = () => {
|
|
1192
|
+
const displayedNodes = [];
|
|
1193
|
+
if (selectedNodeId && component) {
|
|
1194
|
+
const root = component.nodes.root;
|
|
1195
|
+
if (root) {
|
|
1196
|
+
const nodeLookup = getNodeAndAncestors(component, root, selectedNodeId);
|
|
1197
|
+
if (isNodeOrAncestorConditional(nodeLookup)) {
|
|
1198
|
+
displayedNodes.push(selectedNodeId);
|
|
1199
|
+
displayedNodes.push(...[...nodeLookup.ancestors, nodeLookup.node]
|
|
1200
|
+
.filter((a) => a.condition)
|
|
1201
|
+
.map((a) => a.nodeId));
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
showSignal.set({
|
|
1206
|
+
displayedNodes,
|
|
1207
|
+
testMode: mode === 'test',
|
|
1208
|
+
});
|
|
1209
|
+
};
|
|
1210
|
+
};
|
|
1211
|
+
const insertOrReplaceHeadNode = (id, node) => {
|
|
1212
|
+
const existing = document.head.querySelector(`[data-meta-id="${id}"]`);
|
|
1213
|
+
if (existing) {
|
|
1214
|
+
existing.replaceWith(node);
|
|
1215
|
+
}
|
|
1216
|
+
else {
|
|
1217
|
+
document.head.appendChild(node);
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
const insertHeadTags = (entries, context) => {
|
|
1221
|
+
// Remove all tags that has a data-meta-id attribute that is not in the entries
|
|
1222
|
+
Array.from(document.head.querySelectorAll('[data-meta-id]'))
|
|
1223
|
+
.filter((elem) => !entries[elem.getAttribute('data-meta-id')])
|
|
1224
|
+
.forEach((elem) => elem.remove());
|
|
1225
|
+
// Skip anything that is not <link> or <script> tags, as they don't have any influence on the preview
|
|
1226
|
+
Object.entries(entries).forEach(([id, entry]) => {
|
|
1227
|
+
switch (entry.tag) {
|
|
1228
|
+
case 'link':
|
|
1229
|
+
return insertOrReplaceHeadNode(id, document.createRange().createContextualFragment(`
|
|
1230
|
+
<link
|
|
1231
|
+
data-meta-id="${id}"
|
|
1232
|
+
${Object.entries(entry.attrs)
|
|
1233
|
+
.map(([key, value]) => `${key}="${applyFormula(value, context)}"`)
|
|
1234
|
+
.join(' ')}
|
|
1235
|
+
/>
|
|
1236
|
+
`));
|
|
1237
|
+
case 'script':
|
|
1238
|
+
return insertOrReplaceHeadNode(id, document.createRange().createContextualFragment(`
|
|
1239
|
+
<script
|
|
1240
|
+
data-meta-id="${id}"
|
|
1241
|
+
${Object.entries(entry.attrs)
|
|
1242
|
+
.map(([key, value]) => `${key}="${applyFormula(value, context)}"`)
|
|
1243
|
+
.join(' ')}
|
|
1244
|
+
></script>
|
|
1245
|
+
`));
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
};
|
|
1249
|
+
export function getDOMNodeFromNodeId(selectedNodeId) {
|
|
1250
|
+
if (!selectedNodeId) {
|
|
1251
|
+
return null;
|
|
1252
|
+
}
|
|
1253
|
+
return document.querySelector(`[data-id="${selectedNodeId}"]:not([data-component])`);
|
|
1254
|
+
}
|
|
1255
|
+
export function getRectData(selectedNode) {
|
|
1256
|
+
if (!selectedNode) {
|
|
1257
|
+
return null;
|
|
1258
|
+
}
|
|
1259
|
+
const rect = selectedNode.getBoundingClientRect();
|
|
1260
|
+
return {
|
|
1261
|
+
left: rect.left,
|
|
1262
|
+
right: rect.right,
|
|
1263
|
+
top: rect.top,
|
|
1264
|
+
bottom: rect.bottom,
|
|
1265
|
+
width: rect.width,
|
|
1266
|
+
height: rect.height,
|
|
1267
|
+
x: rect.x,
|
|
1268
|
+
y: rect.y,
|
|
1269
|
+
borderRadius: window
|
|
1270
|
+
.getComputedStyle(selectedNode)
|
|
1271
|
+
.borderRadius.split(' ')
|
|
1272
|
+
.map(parseFloat),
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
function getNodeId(component, path) {
|
|
1276
|
+
function getId([nextChild, ...path], currentId) {
|
|
1277
|
+
if (nextChild === undefined || currentId === undefined) {
|
|
1278
|
+
return currentId ?? null;
|
|
1279
|
+
}
|
|
1280
|
+
const currentNode = component.nodes[currentId];
|
|
1281
|
+
if (!currentNode?.children) {
|
|
1282
|
+
return null;
|
|
1283
|
+
}
|
|
1284
|
+
// We only allow selecting the first element in a repeat (which does not have a repeat-index "()")
|
|
1285
|
+
if (nextChild.endsWith(')')) {
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
return getId(path, currentNode.children[parseInt(nextChild)]);
|
|
1289
|
+
}
|
|
1290
|
+
return getId(path, 'root');
|
|
1291
|
+
}
|
|
1292
|
+
const insertTheme = (parent, theme) => {
|
|
1293
|
+
document.getElementById('theme-style')?.remove();
|
|
1294
|
+
const styleElem = document.createElement('style');
|
|
1295
|
+
styleElem.setAttribute('type', 'text/css');
|
|
1296
|
+
styleElem.setAttribute('id', 'theme-style');
|
|
1297
|
+
styleElem.innerHTML = getThemeCss(theme, {
|
|
1298
|
+
includeResetStyle: false,
|
|
1299
|
+
createFontFaces: true,
|
|
1300
|
+
});
|
|
1301
|
+
parent.appendChild(styleElem);
|
|
1302
|
+
};
|
|
1303
|
+
//# sourceMappingURL=editor-preview.main.js.map
|