@nordcraft/runtime 1.0.58 → 1.0.60

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.
@@ -3,12 +3,11 @@
3
3
  /* eslint-disable no-case-declarations */
4
4
  /* eslint-disable no-fallthrough */
5
5
  import { isLegacyApi } from '@nordcraft/core/dist/api/api';
6
+ import { isLegacyPluginAction } from '@nordcraft/core/dist/component/actionUtils';
6
7
  import { HeadTagTypes, } from '@nordcraft/core/dist/component/component.types';
7
8
  import { isPageComponent } from '@nordcraft/core/dist/component/isPageComponent';
8
9
  import { applyFormula, isToddleFormula, } from '@nordcraft/core/dist/formula/formula';
9
10
  import {} from '@nordcraft/core/dist/formula/formulaTypes';
10
- import { valueFormula } from '@nordcraft/core/dist/formula/formulaUtils';
11
- import { getClassName } from '@nordcraft/core/dist/styling/className';
12
11
  import { appendUnit } from '@nordcraft/core/dist/styling/customProperty';
13
12
  import { getThemeCss, renderTheme } from '@nordcraft/core/dist/styling/theme';
14
13
  import { mapObject, omitKeys } from '@nordcraft/core/dist/utils/collections';
@@ -28,55 +27,17 @@ import { dragMove } from './editor/drag-drop/dragMove';
28
27
  import { dragReorder } from './editor/drag-drop/dragReorder';
29
28
  import { dragStarted } from './editor/drag-drop/dragStarted';
30
29
  import { introspectApiRequest } from './editor/graphql';
30
+ import { isInputTarget } from './editor/input';
31
+ import { updateComponentLinks } from './editor/links';
32
+ import { getRectData } from './editor/overlay';
33
+ import { TextNodeComputedStyles, } from './editor/types';
31
34
  import { handleAction } from './events/handleAction';
32
35
  import { signal } from './signal/signal';
33
36
  import { insertStyles, styleToCss } from './styles/style';
34
37
  import { createFormulaCache } from './utils/createFormulaCache';
35
38
  import { getNodeAndAncestors, isNodeOrAncestorConditional } from './utils/nodes';
36
- import { omitSubnodeStyleForComponent } from './utils/omitStyle';
37
39
  import { rectHasPoint } from './utils/rectHasPoint';
38
40
  import { getScrollStateRestorer, storeScrollState, } from './utils/storeScrollState';
39
- /**
40
- * Styles required for rendering the same exact text again somewhere else (on a overlay rect in the editor)
41
- */
42
- var TextNodeComputedStyles;
43
- (function (TextNodeComputedStyles) {
44
- // Caret color is important as it is the only visible part of the text node (when text is not highlighted)
45
- TextNodeComputedStyles["CARET_COLOR"] = "caret-color";
46
- TextNodeComputedStyles["DISPLAY"] = "display";
47
- TextNodeComputedStyles["FONT_FAMILY"] = "font-family";
48
- TextNodeComputedStyles["FONT_SIZE"] = "font-size";
49
- TextNodeComputedStyles["FONT_WEIGHT"] = "font-weight";
50
- TextNodeComputedStyles["FONT_STYLE"] = "font-style";
51
- TextNodeComputedStyles["FONT_VARIANT"] = "font-variant";
52
- TextNodeComputedStyles["FONT_STRETCH"] = "font-stretch";
53
- TextNodeComputedStyles["LINE_HEIGHT"] = "line-height";
54
- TextNodeComputedStyles["TEXT_ALIGN"] = "text-align";
55
- TextNodeComputedStyles["TEXT_TRANSFORM"] = "text-transform";
56
- TextNodeComputedStyles["LETTER_SPACING"] = "letter-spacing";
57
- TextNodeComputedStyles["WHITE_SPACE"] = "white-space";
58
- TextNodeComputedStyles["WORD_SPACING"] = "word-spacing";
59
- TextNodeComputedStyles["TEXT_INDENT"] = "text-indent";
60
- TextNodeComputedStyles["TEXT_OVERFLOW"] = "text-overflow";
61
- TextNodeComputedStyles["TEXT_RENDERING"] = "text-rendering";
62
- TextNodeComputedStyles["WORD_BREAK"] = "word-break";
63
- TextNodeComputedStyles["WORD_WRAP"] = "word-wrap";
64
- TextNodeComputedStyles["DIRECTION"] = "direction";
65
- TextNodeComputedStyles["UNICODE_BIDI"] = "unicode-bidi";
66
- TextNodeComputedStyles["VERTICAL_ALIGN"] = "vertical-align";
67
- TextNodeComputedStyles["FONT_KERNING"] = "font-kerning";
68
- TextNodeComputedStyles["FONT_FEATURE_SETTINGS"] = "font-feature-settings";
69
- TextNodeComputedStyles["FONT_VARIATION_SETTINGS"] = "font-variation-settings";
70
- TextNodeComputedStyles["FONT_SMOOTHING"] = "-webkit-font-smoothing";
71
- TextNodeComputedStyles["ANTI_ALIASING"] = "-moz-osx-font-smoothing";
72
- TextNodeComputedStyles["FONT_OPTICAL_SIZING"] = "font-optical-sizing";
73
- TextNodeComputedStyles["TAB_SIZE"] = "tab-size";
74
- TextNodeComputedStyles["HYPHENS"] = "hyphens";
75
- TextNodeComputedStyles["TEXT_ORIENTATION"] = "text-orientation";
76
- TextNodeComputedStyles["WRITING_MODE"] = "writing-mode";
77
- TextNodeComputedStyles["LINE_BREAK"] = "line-break";
78
- TextNodeComputedStyles["OVERFLOW_WRAP"] = "overflow-wrap";
79
- })(TextNodeComputedStyles || (TextNodeComputedStyles = {}));
80
41
  let env;
81
42
  export const initGlobalObject = () => {
82
43
  env = {
@@ -159,37 +120,23 @@ export const initGlobalObject = () => {
159
120
  : undefined));
160
121
  Object.entries(libActions).forEach(([name, module]) => window.toddle.registerAction('@toddle/' + name, module.default));
161
122
  };
123
+ const EMPTY_COMPONENT_DATA = {
124
+ Location: {
125
+ query: {},
126
+ params: {},
127
+ page: '/',
128
+ path: '/',
129
+ hash: '',
130
+ },
131
+ Attributes: {},
132
+ Variables: {},
133
+ };
162
134
  // imported by "/.toddle/preview" (see worker/src/preview.ts)
163
135
  export const createRoot = (domNode = document.getElementById('App')) => {
164
136
  if (!domNode) {
165
137
  throw new Error('Cant find root domNode');
166
138
  }
167
- const isInputTarget = (event) => {
168
- const target = event.target;
169
- if (target instanceof HTMLElement) {
170
- if (target.tagName === 'INPUT' ||
171
- target.tagName === 'TEXTAREA' ||
172
- target.tagName === 'SELECT' ||
173
- target.tagName === 'STYLE-EDITOR') {
174
- return true;
175
- }
176
- if (target.contentEditable?.toLocaleLowerCase() === 'true') {
177
- return true;
178
- }
179
- }
180
- return false;
181
- };
182
- const dataSignal = signal({
183
- Location: {
184
- query: {},
185
- params: {},
186
- page: '/',
187
- path: '/',
188
- hash: '',
189
- },
190
- Attributes: {},
191
- Variables: {},
192
- });
139
+ const dataSignal = signal(EMPTY_COMPONENT_DATA);
193
140
  let ctxDataSignal;
194
141
  let ctx = null;
195
142
  let mode = 'design';
@@ -219,63 +166,23 @@ export const createRoot = (domNode = document.getElementById('App')) => {
219
166
  let metaKey = false;
220
167
  let previewStyleAnimationFrame = -1;
221
168
  let timelineTimeAnimationFrame = -1;
222
- /**
223
- * Modifies all link nodes on a component
224
- * NOTE: alters in place
225
- */
226
- const updateComponentLinks = (component) => {
227
- // Find all links and add target="_blank" to them
228
- Object.entries(component.nodes ?? {}).forEach(([_, node]) => {
229
- if (node.type === 'element' && node.tag === 'a') {
230
- node.attrs['target'] = valueFormula('_blank');
231
- }
232
- });
233
- return component;
234
- };
235
- const registerActions = (allActions, packageName) => {
236
- const actions = {};
237
- Object.entries(allActions ?? {}).forEach(([name, action]) => {
238
- if (typeof action.name === 'string' && action.version === undefined) {
239
- // Legacy actions are self-registering. We need to execute them to register them
240
- Function(action.handler)();
241
- return;
242
- }
243
- // We need to convert the handler string into a real function
244
- actions[name] = {
245
- ...action,
246
- handler: typeof action.handler === 'string'
247
- ? new Function('args, ctx', `${action.handler}
248
- return ${safeFunctionName(action.name)}(args, ctx)`)
249
- : action.handler,
250
- };
251
- });
252
- window.toddle.actions[packageName ?? window.__toddle.project] = actions;
253
- };
254
- const registerFormulas = (allFormulas, packageName) => {
255
- const formulas = {};
256
- Object.entries(allFormulas ?? {}).forEach(([name, formula]) => {
257
- if (!isToddleFormula(formula) &&
258
- typeof formula.name === 'string' &&
259
- formula.version === undefined) {
260
- // Legacy formulas are self-registering. We need to execute them to register them
261
- Function(formula.handler)();
262
- return;
263
- }
264
- else if (!isToddleFormula(formula)) {
265
- // For code formulas we need to convert the handler string into a real function
266
- formulas[name] = {
267
- ...formula,
268
- handler: typeof formula.handler === 'string'
269
- ? new Function('args, ctx', `${formula.handler}
270
- return ${safeFunctionName(formula.name)}(args, ctx)`)
271
- : formula.handler,
272
- };
273
- return;
169
+ const setupDataSignalSubscribers = () => {
170
+ dataSignal.subscribe((data) => {
171
+ if (component && components && packageComponents && data) {
172
+ try {
173
+ postMessageToEditor({ type: 'data', data });
174
+ }
175
+ catch {
176
+ // If we're unable to send the data, let's try to JSON serialize it
177
+ postMessageToEditor({
178
+ type: 'data',
179
+ data: JSON.parse(JSON.stringify(data)),
180
+ });
181
+ }
274
182
  }
275
- formulas[name] = formula;
276
183
  });
277
- window.toddle.formulas[packageName ?? window.__toddle.project] = formulas;
278
184
  };
185
+ setupDataSignalSubscribers();
279
186
  window.addEventListener('message', async (message) => {
280
187
  if (!message.isTrusted) {
281
188
  console.error('UNTRUSTED MESSAGE');
@@ -287,9 +194,20 @@ export const createRoot = (domNode = document.getElementById('App')) => {
287
194
  }
288
195
  let scrollStateRestorer;
289
196
  if (message.data.component.name !== component?.name) {
197
+ // Store scroll state for the previous component
290
198
  storeScrollState(component?.name);
199
+ // Remove all subscribers from the previous showSignal
291
200
  showSignal.cleanSubscribers();
201
+ // Clear any previously overridden conditional elements
202
+ showSignal.set({ displayedNodes: [], testMode: mode === 'test' });
203
+ // Restore scroll state for the new component
292
204
  scrollStateRestorer = getScrollStateRestorer(message.data.component.name);
205
+ // Destroy the dataSignal (including subscribers) for the previous component
206
+ dataSignal.destroy();
207
+ // Re-subscribe all dataSignal subscribers
208
+ setupDataSignalSubscribers();
209
+ // Re-initialize the data signal for the new component
210
+ ctxDataSignal?.destroy();
293
211
  }
294
212
  component = updateComponentLinks(message.data.component);
295
213
  if (components && packageComponents && ctx) {
@@ -339,7 +257,7 @@ export const createRoot = (domNode = document.getElementById('App')) => {
339
257
  if (ctx) {
340
258
  ctx.components = allComponents;
341
259
  }
342
- updateStyle();
260
+ updateStyle(component);
343
261
  update();
344
262
  }
345
263
  break;
@@ -366,7 +284,7 @@ export const createRoot = (domNode = document.getElementById('App')) => {
366
284
  if (ctx) {
367
285
  ctx.components = allComponents;
368
286
  }
369
- updateStyle();
287
+ updateStyle(component);
370
288
  update();
371
289
  }
372
290
  Object.values(message.data.packages ?? {}).forEach((pkg) => {
@@ -878,7 +796,7 @@ export const createRoot = (domNode = document.getElementById('App')) => {
878
796
  window.addEventListener('beforeunload', () => {
879
797
  storeScrollState(component?.name);
880
798
  });
881
- const updateStyle = () => {
799
+ const updateStyle = (component) => {
882
800
  if (component) {
883
801
  insertStyles(document.head, component, getAllComponents());
884
802
  }
@@ -1157,68 +1075,49 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1157
1075
  }
1158
1076
  }
1159
1077
  if (fastDeepEqual(newCtx.component.nodes, ctx?.component?.nodes) === false) {
1160
- updateStyle();
1078
+ updateStyle(newCtx.component);
1161
1079
  // Remove preview styles automatically when the component changes
1162
1080
  document.head.querySelector('[data-id="selected-node-styles"]')?.remove();
1163
- if (fastDeepEqual(omitSubnodeStyleForComponent(newCtx.component), omitSubnodeStyleForComponent(ctx?.component))) {
1164
- // If we're in here, then the latest update was only a style change, so we should try some optimistic updates
1165
- Object.keys(newCtx.component.nodes).forEach((nodeId) => {
1166
- const newNode = newCtx.component.nodes[nodeId];
1167
- const oldNode = ctx?.component.nodes[nodeId];
1168
- if ((newNode.type === 'element' || newNode.type === 'component') &&
1169
- (oldNode?.type === 'element' || oldNode?.type === 'component') &&
1170
- (!fastDeepEqual(newNode.style, oldNode.style) ||
1171
- !fastDeepEqual(newNode.variants, oldNode.variants))) {
1172
- document
1173
- .querySelectorAll(`[data-node-id="${nodeId}"]`)
1174
- .forEach((nodeInstance) => {
1175
- nodeInstance.classList.remove(getClassName([oldNode.style, oldNode.variants]));
1176
- nodeInstance.classList.add(getClassName([newNode.style, newNode.variants]));
1177
- });
1178
- }
1081
+ Array.from(domNode.children).forEach((child) => {
1082
+ if (child.tagName !== 'SCRIPT') {
1083
+ child.remove();
1084
+ }
1085
+ });
1086
+ // Clear old root signal and create a new one to not keep old signals with previous root around
1087
+ ctxDataSignal?.destroy();
1088
+ ctxDataSignal = dataSignal.map((data) => data);
1089
+ try {
1090
+ const rootElem = createNode({
1091
+ id: 'root',
1092
+ path: '0',
1093
+ dataSignal: ctxDataSignal,
1094
+ ctx: newCtx,
1095
+ parentElement: domNode,
1096
+ instance: { [newCtx.component.name]: 'root' },
1179
1097
  });
1180
- }
1181
- else {
1182
- Array.from(domNode.children).forEach((child) => {
1183
- if (child.tagName !== 'SCRIPT') {
1184
- child.remove();
1185
- }
1098
+ newCtx.component.onLoad?.actions.forEach((action) => {
1099
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1100
+ handleAction(action, dataSignal.get(), newCtx);
1186
1101
  });
1187
- // Clear old root signal and create a new one to not keep old signals with previous root around
1188
- ctxDataSignal?.destroy();
1189
- ctxDataSignal = dataSignal.map((data) => data);
1190
- try {
1191
- const rootElem = createNode({
1192
- id: 'root',
1193
- path: '0',
1194
- dataSignal: ctxDataSignal,
1195
- ctx: newCtx,
1196
- parentElement: domNode,
1197
- instance: { [newCtx.component.name]: 'root' },
1198
- });
1199
- newCtx.component.onLoad?.actions.forEach((action) => {
1200
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1201
- handleAction(action, dataSignal.get(), newCtx);
1202
- });
1203
- rootElem.forEach((elem) => domNode.appendChild(elem));
1102
+ rootElem.forEach((elem) => domNode.appendChild(elem));
1103
+ }
1104
+ catch (error) {
1105
+ const isPage = isPageComponent(newCtx.component);
1106
+ let name = `Unexpected error while rendering ${isPage ? 'page' : 'component'}`;
1107
+ let message = error instanceof Error ? error.message : String(error);
1108
+ let panic = false;
1109
+ if (error instanceof RangeError) {
1110
+ // RangeError is unrecoverable
1111
+ panic = true;
1112
+ name = 'Infinite loop detected';
1113
+ message =
1114
+ 'RangeError (Maximum call stack size exceeded): Remove any circular dependencies or recursive calls (Try undoing your last change). This is most likely caused by a component, formula or action using itself.';
1204
1115
  }
1205
- catch (error) {
1206
- const isPage = isPageComponent(newCtx.component);
1207
- let name = `Unexpected error while rendering ${isPage ? 'page' : 'component'}`;
1208
- let message = error instanceof Error ? error.message : String(error);
1209
- let panic = false;
1210
- if (error instanceof RangeError) {
1211
- // RangeError is unrecoverable
1212
- panic = true;
1213
- name = 'Infinite loop detected';
1214
- message =
1215
- 'RangeError (Maximum call stack size exceeded): Remove any circular dependencies or recursive calls (Try undoing your last change). This is most likely caused by a component, formula or action using itself.';
1216
- }
1217
- // This can be triggered by setting "type" on a select etc.
1218
- if (error instanceof TypeError) {
1219
- panic = true;
1220
- name = 'TypeError';
1221
- message = `Type errors are often caused by:
1116
+ // This can be triggered by setting "type" on a select etc.
1117
+ if (error instanceof TypeError) {
1118
+ panic = true;
1119
+ name = 'TypeError';
1120
+ message = `Type errors are often caused by:
1222
1121
 
1223
1122
  • Trying to set a read-only property (like "type" on a select element).
1224
1123
 
@@ -1227,34 +1126,33 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1227
1126
  • Trying to access a property on an undefined or null value.
1228
1127
 
1229
1128
  • Trying to call a method on an undefined or null value.`;
1230
- }
1231
- console.error(name, message, error);
1232
- if (panic) {
1233
- // Show error overlay in the editor until next update
1234
- const panicScreen = createPanicScreen({
1235
- name: name,
1236
- message,
1237
- isPage,
1238
- cause: error,
1239
- });
1240
- // Replace the inner HTML of the editor preview with the panic screen
1241
- domNode.innerHTML = '';
1242
- domNode.appendChild(panicScreen);
1243
- }
1244
- else {
1245
- // Otherwise send a toast to the editor with the error (unknown errors may be recoverable), if not please add the error-type to the above
1246
- sendEditorToast(name, message, {
1247
- type: 'critical',
1248
- });
1249
- }
1250
1129
  }
1251
- postMessageToEditor({
1252
- type: 'style',
1253
- time: new Intl.DateTimeFormat('en-GB', {
1254
- timeStyle: 'long',
1255
- }).format(new Date()),
1256
- });
1130
+ console.error(name, message, error);
1131
+ if (panic) {
1132
+ // Show error overlay in the editor until next update
1133
+ const panicScreen = createPanicScreen({
1134
+ name: name,
1135
+ message,
1136
+ isPage,
1137
+ cause: error,
1138
+ });
1139
+ // Replace the inner HTML of the editor preview with the panic screen
1140
+ domNode.innerHTML = '';
1141
+ domNode.appendChild(panicScreen);
1142
+ }
1143
+ else {
1144
+ // Otherwise send a toast to the editor with the error (unknown errors may be recoverable), if not please add the error-type to the above
1145
+ sendEditorToast(name, message, {
1146
+ type: 'critical',
1147
+ });
1148
+ }
1257
1149
  }
1150
+ postMessageToEditor({
1151
+ type: 'style',
1152
+ time: new Intl.DateTimeFormat('en-GB', {
1153
+ timeStyle: 'long',
1154
+ }).format(new Date()),
1155
+ });
1258
1156
  }
1259
1157
  ctx = newCtx;
1260
1158
  scrollStateRestorer((nodeId) => document.querySelector(`[data-id="${nodeId}"]`));
@@ -1312,68 +1210,7 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1312
1210
  }
1313
1211
  return ctx;
1314
1212
  };
1315
- document.addEventListener('keydown', (event) => {
1316
- if (isInputTarget(event)) {
1317
- return;
1318
- }
1319
- switch (event.key) {
1320
- case 'k':
1321
- if (event.metaKey) {
1322
- event.preventDefault();
1323
- }
1324
- }
1325
- postMessageToEditor({
1326
- type: 'keydown',
1327
- event: {
1328
- key: event.key,
1329
- metaKey: event.metaKey,
1330
- shiftKey: event.shiftKey,
1331
- altKey: event.altKey,
1332
- },
1333
- });
1334
- });
1335
- document.addEventListener('keyup', (event) => {
1336
- if (isInputTarget(event)) {
1337
- return;
1338
- }
1339
- postMessageToEditor({
1340
- type: 'keyup',
1341
- event: {
1342
- key: event.key,
1343
- metaKey: event.metaKey,
1344
- shiftKey: event.shiftKey,
1345
- altKey: event.altKey,
1346
- },
1347
- });
1348
- });
1349
- document.addEventListener('keypress', (event) => {
1350
- if (isInputTarget(event)) {
1351
- return;
1352
- }
1353
- postMessageToEditor({
1354
- type: 'keypress',
1355
- event: {
1356
- key: event.key,
1357
- metaKey: event.metaKey,
1358
- shiftKey: event.shiftKey,
1359
- altKey: event.altKey,
1360
- },
1361
- });
1362
- });
1363
- dataSignal.subscribe((data) => {
1364
- if (component && components && packageComponents && data) {
1365
- try {
1366
- postMessageToEditor({ type: 'data', data });
1367
- }
1368
- catch {
1369
- // If we're unable to send the data, let's try to JSON serialize it
1370
- postMessageToEditor({
1371
- type: 'data',
1372
- data: JSON.parse(JSON.stringify(data)),
1373
- });
1374
- }
1375
- }
1376
- });
1213
+ initKeyListeners();
1377
1214
  const clearSelectedStyleVariant = () => {
1378
1215
  if (styleVariantSelection) {
1379
1216
  const styleElem = document.head.querySelector(`[data-hash="${styleVariantSelection.nodeId}"]`);
@@ -1420,25 +1257,6 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1420
1257
  requestAnimationFrame(() => syncOverlayRects(selectionRect, highlightRect));
1421
1258
  })();
1422
1259
  };
1423
- function getRectData(selectedNode) {
1424
- if (!selectedNode) {
1425
- return null;
1426
- }
1427
- const { borderRadius, rotate } = window.getComputedStyle(selectedNode);
1428
- const rect = selectedNode.getBoundingClientRect();
1429
- return {
1430
- left: rect.left,
1431
- right: rect.right,
1432
- top: rect.top,
1433
- bottom: rect.bottom,
1434
- width: rect.width,
1435
- height: rect.height,
1436
- x: rect.x,
1437
- y: rect.y,
1438
- borderRadius: borderRadius.split(' '),
1439
- rotate,
1440
- };
1441
- }
1442
1260
  const insertOrReplaceHeadNode = (id, node) => {
1443
1261
  const existing = document.head.querySelector(`[data-meta-id="${id}"]`);
1444
1262
  if (existing) {
@@ -1514,6 +1332,100 @@ const insertTheme = (parent, themes) => {
1514
1332
  });
1515
1333
  parent.appendChild(styleElem);
1516
1334
  };
1335
+ const initKeyListeners = () => {
1336
+ document.addEventListener('keydown', (event) => {
1337
+ if (isInputTarget(event)) {
1338
+ return;
1339
+ }
1340
+ switch (event.key) {
1341
+ case 'k':
1342
+ if (event.metaKey) {
1343
+ event.preventDefault();
1344
+ }
1345
+ }
1346
+ postMessageToEditor({
1347
+ type: 'keydown',
1348
+ event: {
1349
+ key: event.key,
1350
+ metaKey: event.metaKey,
1351
+ shiftKey: event.shiftKey,
1352
+ altKey: event.altKey,
1353
+ },
1354
+ });
1355
+ });
1356
+ document.addEventListener('keyup', (event) => {
1357
+ if (isInputTarget(event)) {
1358
+ return;
1359
+ }
1360
+ postMessageToEditor({
1361
+ type: 'keyup',
1362
+ event: {
1363
+ key: event.key,
1364
+ metaKey: event.metaKey,
1365
+ shiftKey: event.shiftKey,
1366
+ altKey: event.altKey,
1367
+ },
1368
+ });
1369
+ });
1370
+ document.addEventListener('keypress', (event) => {
1371
+ if (isInputTarget(event)) {
1372
+ return;
1373
+ }
1374
+ postMessageToEditor({
1375
+ type: 'keypress',
1376
+ event: {
1377
+ key: event.key,
1378
+ metaKey: event.metaKey,
1379
+ shiftKey: event.shiftKey,
1380
+ altKey: event.altKey,
1381
+ },
1382
+ });
1383
+ });
1384
+ };
1385
+ const registerActions = (allActions, packageName) => {
1386
+ const actions = {};
1387
+ Object.entries(allActions ?? {}).forEach(([name, action]) => {
1388
+ if (isLegacyPluginAction(action)) {
1389
+ // Legacy actions are self-registering. We need to execute them to register them
1390
+ Function(action.handler)();
1391
+ return;
1392
+ }
1393
+ // We need to convert the handler string into a real function
1394
+ actions[name] = {
1395
+ ...action,
1396
+ handler: typeof action.handler === 'string'
1397
+ ? new Function('args, ctx', `${action.handler}
1398
+ return ${safeFunctionName(action.name)}(args, ctx)`)
1399
+ : action.handler,
1400
+ };
1401
+ });
1402
+ window.toddle.actions[packageName ?? window.__toddle.project] = actions;
1403
+ };
1404
+ const registerFormulas = (allFormulas, packageName) => {
1405
+ const formulas = {};
1406
+ Object.entries(allFormulas ?? {}).forEach(([name, formula]) => {
1407
+ if (!isToddleFormula(formula) &&
1408
+ typeof formula.name === 'string' &&
1409
+ formula.version === undefined) {
1410
+ // Legacy formulas are self-registering. We need to execute them to register them
1411
+ Function(formula.handler)();
1412
+ return;
1413
+ }
1414
+ else if (!isToddleFormula(formula)) {
1415
+ // For code formulas we need to convert the handler string into a real function
1416
+ formulas[name] = {
1417
+ ...formula,
1418
+ handler: typeof formula.handler === 'string'
1419
+ ? new Function('args, ctx', `${formula.handler}
1420
+ return ${safeFunctionName(formula.name)}(args, ctx)`)
1421
+ : formula.handler,
1422
+ };
1423
+ return;
1424
+ }
1425
+ formulas[name] = formula;
1426
+ });
1427
+ window.toddle.formulas[packageName ?? window.__toddle.project] = formulas;
1428
+ };
1517
1429
  const postMessageToEditor = (message) => {
1518
1430
  window.parent?.postMessage(message, '*');
1519
1431
  };