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