@bpmn-io/form-js-playground 1.7.3 → 1.8.1

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.cjs CHANGED
@@ -147,7 +147,7 @@ function completions(context) {
147
147
  };
148
148
  }
149
149
 
150
- const NO_LINT_CLS = 'fjs-no-json-lint';
150
+ const NO_LINT_CLS = 'fjs-cm-no-lint';
151
151
 
152
152
  /**
153
153
  * @param {object} options
@@ -162,67 +162,53 @@ function JSONEditor(options = {}) {
162
162
  readonly = false
163
163
  } = options;
164
164
  const emitter = mitt();
165
- let language = new state.Compartment().of(langJson.json());
166
- let tabSize = new state.Compartment().of(state.EditorState.tabSize.of(2));
165
+ const languageCompartment = new state.Compartment().of(langJson.json());
166
+ const tabSizeCompartment = new state.Compartment().of(state.EditorState.tabSize.of(2));
167
+ const autocompletionConfCompartment = new state.Compartment();
168
+ const placeholderLinterExtension = createPlaceholderLinterExtension();
167
169
  let container = null;
168
-
169
- /**
170
- * @typedef {Array<string>} Variables
171
- */
172
-
173
- const autocompletionConf = new state.Compartment();
174
- const linterExtension = lint.linter(langJson.jsonParseLinter());
175
-
176
- // this sets no-linting mode if placeholders are present
177
- const placeholderLinterExtension = lint.linter(view => {
178
- const placeholders = view.dom.querySelectorAll('.cm-placeholder');
179
- if (placeholders.length > 0) {
180
- set(container, NO_LINT_CLS);
181
- } else {
182
- unset(container, NO_LINT_CLS);
183
- }
184
- return [];
185
- });
186
- function createState(doc, extensions = [], variables = []) {
187
- return state.EditorState.create({
188
- doc,
189
- extensions: [codemirror.basicSetup, language, tabSize, linterExtension, placeholderLinterExtension, lint.lintGutter(), autocompletionConf.of(variablesFacet.of(variables)), autocompletionExtension(), view.keymap.of([commands.indentWithTab]), editorPlaceholder ? view.placeholder(editorPlaceholder) : [], ...extensions]
190
- });
191
- }
192
- function createView(readonly) {
193
- const updateListener = view.EditorView.updateListener.of(update => {
170
+ function createState(doc, variables = []) {
171
+ const extensions = [codemirror.basicSetup, languageCompartment, tabSizeCompartment, lint.lintGutter(), lint.linter(langJson.jsonParseLinter()), placeholderLinterExtension, autocompletionConfCompartment.of(variablesFacet.of(variables)), autocompletionExtension(), view.keymap.of([commands.indentWithTab]), editorPlaceholder ? view.placeholder(editorPlaceholder) : [], view.EditorView.updateListener.of(update => {
194
172
  if (update.docChanged) {
195
173
  emitter.emit('changed', {
196
- value: update.view.state.doc.toString()
174
+ value: update.state.doc.toString()
197
175
  });
198
176
  }
177
+ }), view.EditorView.editable.of(!readonly), view.EditorView.contentAttributes.of(contentAttributes)];
178
+ return state.EditorState.create({
179
+ doc,
180
+ extensions
199
181
  });
200
- const editable = view.EditorView.editable.of(!readonly);
201
- const contentAttributesExtension = view.EditorView.contentAttributes.of(contentAttributes);
202
- const view$1 = new view.EditorView({
203
- state: createState('', [updateListener, editable, contentAttributesExtension])
204
- });
205
- view$1.setValue = function (value) {
206
- this.setState(createState(value, [updateListener, editable, contentAttributesExtension]));
207
- };
208
- view$1.setVariables = function (variables) {
209
- this.setState(createState(view$1.state.doc.toString(), [updateListener, editable, contentAttributesExtension], variables));
210
- };
211
- return view$1;
212
182
  }
213
- const view$1 = this._view = createView(readonly);
214
- this.setValue = function (value) {
215
- view$1.setValue(value);
183
+ const view$1 = new view.EditorView({
184
+ state: createState('')
185
+ });
186
+ this.setValue = function (newValue) {
187
+ const oldValue = view$1.state.doc.toString();
188
+ const diff = findDiff(oldValue, newValue);
189
+ if (diff) {
190
+ view$1.dispatch({
191
+ changes: {
192
+ from: diff.start,
193
+ to: diff.end,
194
+ insert: diff.text
195
+ },
196
+ selection: {
197
+ anchor: diff.start + diff.text.length
198
+ }
199
+ });
200
+ }
216
201
  };
217
202
  this.getValue = function () {
218
203
  return view$1.state.doc.toString();
219
204
  };
220
-
221
- /**
222
- * @param {Variables} variables
223
- */
224
205
  this.setVariables = function (variables) {
225
- view$1.setVariables(variables);
206
+ view$1.dispatch({
207
+ effects: autocompletionConfCompartment.reconfigure(variablesFacet.of(variables))
208
+ });
209
+ };
210
+ this.getView = function () {
211
+ return view$1;
226
212
  };
227
213
  this.on = emitter.on;
228
214
  this.off = emitter.off;
@@ -230,24 +216,56 @@ function JSONEditor(options = {}) {
230
216
  this.attachTo = function (_container) {
231
217
  container = _container;
232
218
  container.appendChild(view$1.dom);
219
+ minDom.classes(container, document.body).add('fjs-json-editor');
233
220
  };
234
221
  this.destroy = function () {
235
- if (view$1.dom.parentNode) {
236
- view$1.dom.parentNode.removeChild(view$1.dom);
222
+ if (container && view$1.dom) {
223
+ container.removeChild(view$1.dom);
224
+ minDom.classes(container, document.body).remove('fjs-json-editor');
237
225
  }
238
226
  view$1.destroy();
239
227
  };
228
+ function createPlaceholderLinterExtension() {
229
+ return lint.linter(view => {
230
+ const placeholders = view.dom.querySelectorAll('.cm-placeholder');
231
+ if (placeholders.length > 0) {
232
+ minDom.classes(container, document.body).add(NO_LINT_CLS);
233
+ } else {
234
+ minDom.classes(container, document.body).remove(NO_LINT_CLS);
235
+ }
236
+ return [];
237
+ });
238
+ }
240
239
  }
241
-
242
- // helpers //////////////////////
243
-
244
- function set(node, cls) {
245
- const classes = minDom.classes(node, document.body);
246
- classes.add(cls);
247
- }
248
- function unset(node, cls) {
249
- const classes = minDom.classes(node, document.body);
250
- classes.remove(cls);
240
+ function findDiff(oldStr, newStr) {
241
+ if (oldStr === newStr) {
242
+ return null;
243
+ }
244
+ oldStr = oldStr || '';
245
+ newStr = newStr || '';
246
+ let minLength = Math.min(oldStr.length, newStr.length);
247
+ let start = 0;
248
+ while (start < minLength && oldStr[start] === newStr[start]) {
249
+ start++;
250
+ }
251
+ if (start === minLength) {
252
+ return {
253
+ start: start,
254
+ text: newStr.slice(start),
255
+ end: oldStr.length
256
+ };
257
+ }
258
+ let endOld = oldStr.length;
259
+ let endNew = newStr.length;
260
+ while (endOld > start && endNew > start && oldStr[endOld - 1] === newStr[endNew - 1]) {
261
+ endOld--;
262
+ endNew--;
263
+ }
264
+ return {
265
+ start: start,
266
+ text: newStr.slice(start, endNew),
267
+ end: endOld
268
+ };
251
269
  }
252
270
 
253
271
  function Section(props) {
@@ -281,90 +299,70 @@ Section.HeaderItem = function (props) {
281
299
  return props.children;
282
300
  };
283
301
 
284
- function PlaygroundRoot(props) {
302
+ function PlaygroundRoot(config) {
285
303
  const {
286
- additionalModules = [],
304
+ additionalModules,
287
305
  // goes into both editor + viewer
288
- actions: actionsConfig = {},
306
+ actions: actionsConfig,
289
307
  emit,
290
- exporter: exporterConfig = {},
291
- viewerProperties = {},
292
- editorProperties = {},
293
- viewerAdditionalModules = [],
294
- editorAdditionalModules = [],
295
- propertiesPanel: propertiesPanelConfig = {},
296
- onInit: onPlaygroundInit,
297
- onStateChanged
298
- } = props;
308
+ exporter: exporterConfig,
309
+ viewerProperties,
310
+ editorProperties,
311
+ viewerAdditionalModules,
312
+ editorAdditionalModules,
313
+ propertiesPanel: propertiesPanelConfig,
314
+ apiLinkTarget,
315
+ onInit
316
+ } = config;
299
317
  const {
300
318
  display: displayActions = true
301
- } = actionsConfig;
302
- const paletteContainerRef = hooks.useRef();
319
+ } = actionsConfig || {};
303
320
  const editorContainerRef = hooks.useRef();
304
- const formContainerRef = hooks.useRef();
305
- const dataContainerRef = hooks.useRef();
306
- const resultContainerRef = hooks.useRef();
321
+ const paletteContainerRef = hooks.useRef();
307
322
  const propertiesPanelContainerRef = hooks.useRef();
308
- const paletteRef = hooks.useRef();
323
+ const viewerContainerRef = hooks.useRef();
324
+ const inputDataContainerRef = hooks.useRef();
325
+ const outputDataContainerRef = hooks.useRef();
309
326
  const formEditorRef = hooks.useRef();
310
- const formRef = hooks.useRef();
311
- const dataEditorRef = hooks.useRef();
312
- const resultViewRef = hooks.useRef();
313
- const propertiesPanelRef = hooks.useRef();
327
+ const formViewerRef = hooks.useRef();
328
+ const inputDataRef = hooks.useRef();
329
+ const outputDataRef = hooks.useRef();
314
330
  const [showEmbed, setShowEmbed] = hooks.useState(false);
315
- const [initialData] = hooks.useState(props.data || undefined);
316
- const [initialSchema, setInitialSchema] = hooks.useState(props.schema);
317
- const [data, setData] = hooks.useState(props.data || {});
318
- const [schema, setSchema] = hooks.useState(props.schema);
319
- const [resultData, setResultData] = hooks.useState({});
331
+ const [schema, setSchema] = hooks.useState();
332
+ const [data, setData] = hooks.useState();
333
+ const load = hooks.useCallback((schema, data) => {
334
+ formEditorRef.current.importSchema(schema, data);
335
+ inputDataRef.current.setValue(toString(data));
336
+ setSchema(schema);
337
+ setData(data);
338
+ }, []);
320
339
 
321
- // pipe to playground API
340
+ // initialize and link the editors
322
341
  hooks.useEffect(() => {
323
- onPlaygroundInit({
324
- attachDataContainer: node => dataEditorRef.current.attachTo(node),
325
- attachEditorContainer: node => formEditorRef.current.attachTo(node),
326
- attachFormContainer: node => formRef.current.attachTo(node),
327
- attachPaletteContainer: node => paletteRef.current.attachTo(node),
328
- attachPropertiesPanelContainer: node => propertiesPanelRef.current.attachTo(node),
329
- attachResultContainer: node => resultViewRef.current.attachTo(node),
330
- get: (name, strict) => formEditorRef.current.get(name, strict),
331
- getDataEditor: () => dataEditorRef.current,
332
- getEditor: () => formEditorRef.current,
333
- getForm: () => formRef.current,
334
- getResultView: () => resultViewRef.current,
335
- getSchema: () => formEditorRef.current.getSchema(),
336
- setSchema: setInitialSchema,
337
- saveSchema: () => formEditorRef.current.saveSchema()
338
- });
339
- }, [onPlaygroundInit]);
340
- hooks.useEffect(() => {
341
- setInitialSchema(props.schema || {});
342
- }, [props.schema]);
343
- hooks.useEffect(() => {
344
- const dataEditor = dataEditorRef.current = new JSONEditor({
345
- value: toString(data),
342
+ const inputDataEditor = inputDataRef.current = new JSONEditor({
346
343
  contentAttributes: {
347
344
  'aria-label': 'Form Input',
348
345
  tabIndex: 0
349
346
  },
350
347
  placeholder: createDataEditorPlaceholder()
351
348
  });
352
- const resultView = resultViewRef.current = new JSONEditor({
349
+ const outputDataEditor = outputDataRef.current = new JSONEditor({
353
350
  readonly: true,
354
- value: toString(resultData),
355
351
  contentAttributes: {
356
352
  'aria-label': 'Form Output',
357
353
  tabIndex: 0
358
354
  }
359
355
  });
360
- const form = formRef.current = new formJsViewer.Form({
361
- additionalModules: [...additionalModules, ...viewerAdditionalModules],
356
+ const formViewer = formViewerRef.current = new formJsViewer.Form({
357
+ container: viewerContainerRef.current,
358
+ additionalModules: [...(additionalModules || []), ...(viewerAdditionalModules || [])],
362
359
  properties: {
363
- ...viewerProperties,
360
+ ...(viewerProperties || {}),
364
361
  'ariaLabel': 'Form Preview'
365
362
  }
366
363
  });
367
364
  const formEditor = formEditorRef.current = new formJsEditor.FormEditor({
365
+ container: editorContainerRef.current,
368
366
  renderer: {
369
367
  compact: true
370
368
  },
@@ -373,17 +371,15 @@ function PlaygroundRoot(props) {
373
371
  },
374
372
  propertiesPanel: {
375
373
  parent: propertiesPanelContainerRef.current,
376
- ...propertiesPanelConfig
374
+ ...(propertiesPanelConfig || {})
377
375
  },
378
376
  exporter: exporterConfig,
379
377
  properties: {
380
- ...editorProperties,
378
+ ...(editorProperties || {}),
381
379
  'ariaLabel': 'Form Definition'
382
380
  },
383
- additionalModules: [...additionalModules, ...editorAdditionalModules]
381
+ additionalModules: [...(additionalModules || []), ...(editorAdditionalModules || [])]
384
382
  });
385
- paletteRef.current = formEditor.get('palette');
386
- propertiesPanelRef.current = formEditor.get('propertiesPanel');
387
383
  formEditor.on('formField.add', ({
388
384
  formField
389
385
  }) => {
@@ -409,7 +405,7 @@ function PlaygroundRoot(props) {
409
405
  ...currentData,
410
406
  [id]: initialDemoData
411
407
  };
412
- dataEditorRef.current.setValue(toString(newData));
408
+ inputDataRef.current.setValue(toString(newData));
413
409
  return newData;
414
410
  });
415
411
  });
@@ -420,10 +416,13 @@ function PlaygroundRoot(props) {
420
416
  // notify interested parties after render
421
417
  emit('formPlayground.rendered');
422
418
  });
423
- form.on('changed', () => {
424
- setResultData(form._getSubmitData());
419
+
420
+ // pipe viewer changes to output data editor
421
+ formViewer.on('changed', () => {
422
+ const submitData = formViewer._getSubmitData();
423
+ outputDataEditor.setValue(toString(submitData));
425
424
  });
426
- dataEditor.on('changed', event => {
425
+ inputDataEditor.on('changed', event => {
427
426
  try {
428
427
  setData(JSON.parse(event.value));
429
428
  } catch (error) {
@@ -431,48 +430,69 @@ function PlaygroundRoot(props) {
431
430
  emit('formPlayground.inputDataError', error);
432
431
  }
433
432
  });
434
- const formContainer = formContainerRef.current;
435
- const editorContainer = editorContainerRef.current;
436
- const dataContainer = dataContainerRef.current;
437
- const resultContainer = resultContainerRef.current;
438
- dataEditor.attachTo(dataContainer);
439
- resultView.attachTo(resultContainer);
440
- form.attachTo(formContainer);
441
- formEditor.attachTo(editorContainer);
433
+ inputDataEditor.attachTo(inputDataContainerRef.current);
434
+ outputDataEditor.attachTo(outputDataContainerRef.current);
442
435
  return () => {
443
- dataEditor.destroy();
444
- resultView.destroy();
445
- form.destroy();
436
+ inputDataEditor.destroy();
437
+ outputDataEditor.destroy();
438
+ formViewer.destroy();
446
439
  formEditor.destroy();
447
440
  };
448
- }, []);
449
- hooks.useEffect(() => {
450
- dataEditorRef.current.setValue(toString(initialData));
451
- }, [initialData]);
441
+ }, [additionalModules, editorAdditionalModules, editorProperties, emit, exporterConfig, propertiesPanelConfig, viewerAdditionalModules, viewerProperties]);
442
+
443
+ // initialize data through props
452
444
  hooks.useEffect(() => {
453
- if (initialSchema) {
454
- formEditorRef.current.importSchema(initialSchema);
455
- dataEditorRef.current.setVariables(formJsViewer.getSchemaVariables(initialSchema));
445
+ if (!config.initialSchema) {
446
+ return;
456
447
  }
457
- }, [initialSchema]);
448
+ load(config.initialSchema, config.initialData || {});
449
+ }, [config.initialSchema, config.initialData, load]);
450
+ hooks.useEffect(() => {
451
+ schema && formViewerRef.current.importSchema(schema, data);
452
+ }, [schema, data]);
458
453
  hooks.useEffect(() => {
459
- if (schema && dataContainerRef.current) {
454
+ if (schema && inputDataContainerRef.current) {
460
455
  const variables = formJsViewer.getSchemaVariables(schema);
461
- dataEditorRef.current.setVariables(variables);
456
+ inputDataRef.current.setVariables(variables);
462
457
  }
463
458
  }, [schema]);
459
+
460
+ // exposes api to parent
464
461
  hooks.useEffect(() => {
465
- schema && formRef.current.importSchema(schema, data);
466
- }, [schema, data]);
467
- hooks.useEffect(() => {
468
- resultViewRef.current.setValue(toString(resultData));
469
- }, [resultData]);
462
+ if (!apiLinkTarget) {
463
+ return;
464
+ }
465
+ apiLinkTarget.api = {
466
+ attachDataContainer: node => inputDataRef.current.attachTo(node),
467
+ attachResultContainer: node => outputDataRef.current.attachTo(node),
468
+ attachFormContainer: node => formViewerRef.current.attachTo(node),
469
+ attachEditorContainer: node => formEditorRef.current.attachTo(node),
470
+ attachPaletteContainer: node => formEditorRef.current.get('palette').attachTo(node),
471
+ attachPropertiesPanelContainer: node => formEditorRef.current.get('propertiesPanel').attachTo(node),
472
+ get: (name, strict) => formEditorRef.current.get(name, strict),
473
+ getDataEditor: () => inputDataRef.current,
474
+ getEditor: () => formEditorRef.current,
475
+ getForm: () => formViewerRef.current,
476
+ getResultView: () => outputDataRef.current,
477
+ getSchema: () => formEditorRef.current.getSchema(),
478
+ saveSchema: () => formEditorRef.current.saveSchema(),
479
+ setSchema: setSchema,
480
+ setData: setData
481
+ };
482
+ onInit();
483
+ }, [apiLinkTarget, onInit]);
484
+
485
+ // separate effect for state to avoid re-creating the api object every time
470
486
  hooks.useEffect(() => {
471
- onStateChanged && onStateChanged({
487
+ if (!apiLinkTarget) {
488
+ return;
489
+ }
490
+ apiLinkTarget.api.getState = () => ({
472
491
  schema,
473
492
  data
474
493
  });
475
- }, [onStateChanged, schema, data]);
494
+ apiLinkTarget.api.load = load;
495
+ }, [apiLinkTarget, schema, data, load]);
476
496
  const handleDownload = hooks.useCallback(() => {
477
497
  download(JSON.stringify(schema, null, ' '), 'form.json', 'text/json');
478
498
  }, [schema]);
@@ -520,19 +540,19 @@ function PlaygroundRoot(props) {
520
540
  }), jsxRuntime.jsx(Section, {
521
541
  name: "Form Preview",
522
542
  children: jsxRuntime.jsx("div", {
523
- ref: formContainerRef,
543
+ ref: viewerContainerRef,
524
544
  class: "fjs-pgl-form-container"
525
545
  })
526
546
  }), jsxRuntime.jsx(Section, {
527
547
  name: "Form Input",
528
548
  children: jsxRuntime.jsx("div", {
529
- ref: dataContainerRef,
549
+ ref: inputDataContainerRef,
530
550
  class: "fjs-pgl-text-container"
531
551
  })
532
552
  }), jsxRuntime.jsx(Section, {
533
553
  name: "Form Output",
534
554
  children: jsxRuntime.jsx("div", {
535
- ref: resultContainerRef,
555
+ ref: outputDataContainerRef,
536
556
  class: "fjs-pgl-text-container"
537
557
  })
538
558
  })]
@@ -557,16 +577,11 @@ function createDataEditorPlaceholder() {
557
577
  function Playground(options) {
558
578
  const {
559
579
  container: parent,
560
- schema,
561
- data,
580
+ schema: initialSchema,
581
+ data: initialData,
562
582
  ...rest
563
583
  } = options;
564
584
  const emitter = mitt();
565
- let state = {
566
- data,
567
- schema
568
- };
569
- let ref;
570
585
  const container = document.createElement('div');
571
586
  container.classList.add('fjs-pgl-parent');
572
587
  if (parent) {
@@ -576,63 +591,56 @@ function Playground(options) {
576
591
  const file = files[0];
577
592
  if (file) {
578
593
  try {
579
- ref.setSchema(JSON.parse(file.contents));
594
+ this.api.setSchema(JSON.parse(file.contents));
580
595
  } catch (err) {
581
596
 
582
597
  // TODO(nikku): indicate JSON parse error
583
598
  }
584
599
  }
585
600
  });
586
- const withRef = function (fn) {
601
+ const safe = function (fn) {
587
602
  return function (...args) {
588
- if (!ref) {
603
+ if (!this.api) {
589
604
  throw new Error('Playground is not initialized.');
590
605
  }
591
606
  return fn(...args);
592
607
  };
593
608
  };
594
- const onInit = function (_ref) {
595
- ref = _ref;
609
+ const onInit = function () {
596
610
  emitter.emit('formPlayground.init');
597
611
  };
598
612
  container.addEventListener('dragover', handleDrop);
599
613
  preact.render(jsxRuntime.jsx(PlaygroundRoot, {
600
- data: data,
614
+ initialSchema: initialSchema,
615
+ initialData: initialData,
601
616
  emit: emitter.emit,
617
+ apiLinkTarget: this,
602
618
  onInit: onInit,
603
- onStateChanged: _state => state = _state,
604
- schema: schema,
605
619
  ...rest
606
620
  }), container);
607
621
  this.on = emitter.on;
608
622
  this.off = emitter.off;
609
623
  this.emit = emitter.emit;
610
- this.on('destroy', function () {
624
+ this.on('destroy', () => {
611
625
  preact.render(null, container);
612
- });
613
- this.on('destroy', function () {
614
626
  parent.removeChild(container);
615
627
  });
616
- this.getState = function () {
617
- return state;
618
- };
619
- this.getSchema = withRef(() => ref.getSchema());
620
- this.setSchema = withRef(schema => ref.setSchema(schema));
621
- this.saveSchema = withRef(() => ref.saveSchema());
622
- this.get = withRef((name, strict) => ref.get(name, strict));
623
- this.getDataEditor = withRef(() => ref.getDataEditor());
624
- this.getEditor = withRef(() => ref.getEditor());
625
- this.getForm = withRef((name, strict) => ref.getForm(name, strict));
626
- this.getResultView = withRef(() => ref.getResultView());
627
- this.destroy = function () {
628
- this.emit('destroy');
629
- };
630
- this.attachEditorContainer = withRef(node => ref.attachEditorContainer(node));
631
- this.attachPreviewContainer = withRef(node => ref.attachFormContainer(node));
632
- this.attachDataContainer = withRef(node => ref.attachDataContainer(node));
633
- this.attachResultContainer = withRef(node => ref.attachResultContainer(node));
634
- this.attachPaletteContainer = withRef(node => ref.attachPaletteContainer(node));
635
- this.attachPropertiesPanelContainer = withRef(node => ref.attachPropertiesPanelContainer(node));
628
+ this.destroy = () => this.emit('destroy');
629
+ this.getState = safe(() => this.api.getState());
630
+ this.getSchema = safe(() => this.api.getSchema());
631
+ this.setSchema = safe(schema => this.api.setSchema(schema));
632
+ this.saveSchema = safe(() => this.api.saveSchema());
633
+ this.get = safe((name, strict) => this.api.get(name, strict));
634
+ this.getDataEditor = safe(() => this.api.getDataEditor());
635
+ this.getEditor = safe(() => this.api.getEditor());
636
+ this.getForm = safe((name, strict) => this.api.getForm(name, strict));
637
+ this.getResultView = safe(() => this.api.getResultView());
638
+ this.attachEditorContainer = safe(node => this.api.attachEditorContainer(node));
639
+ this.attachPreviewContainer = safe(node => this.api.attachFormContainer(node));
640
+ this.attachDataContainer = safe(node => this.api.attachDataContainer(node));
641
+ this.attachResultContainer = safe(node => this.api.attachResultContainer(node));
642
+ this.attachPaletteContainer = safe(node => this.api.attachPaletteContainer(node));
643
+ this.attachPropertiesPanelContainer = safe(node => this.api.attachPropertiesPanelContainer(node));
636
644
  }
637
645
 
638
646
  exports.Playground = Playground;