@bpmn-io/form-js-playground 1.7.2 → 1.8.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/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,69 @@ 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
+ } = config;
297
314
  const {
298
315
  display: displayActions = true
299
- } = actionsConfig;
300
- const paletteContainerRef = useRef();
316
+ } = actionsConfig || {};
301
317
  const editorContainerRef = useRef();
302
- const formContainerRef = useRef();
303
- const dataContainerRef = useRef();
304
- const resultContainerRef = useRef();
318
+ const paletteContainerRef = useRef();
305
319
  const propertiesPanelContainerRef = useRef();
306
- const paletteRef = useRef();
320
+ const viewerContainerRef = useRef();
321
+ const inputDataContainerRef = useRef();
322
+ const outputDataContainerRef = useRef();
307
323
  const formEditorRef = useRef();
308
- const formRef = useRef();
309
- const dataEditorRef = useRef();
310
- const resultViewRef = useRef();
311
- const propertiesPanelRef = useRef();
324
+ const formViewerRef = useRef();
325
+ const inputDataRef = useRef();
326
+ const outputDataRef = useRef();
312
327
  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({});
328
+ const [schema, setSchema] = useState();
329
+ const [data, setData] = useState();
330
+ const load = useCallback((schema, data) => {
331
+ formEditorRef.current.importSchema(schema, data);
332
+ inputDataRef.current.setValue(toString(data));
333
+ setSchema(schema);
334
+ setData(data);
335
+ }, []);
318
336
 
319
- // pipe to playground API
337
+ // initialize and link the editors
320
338
  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),
339
+ const inputDataEditor = inputDataRef.current = new JSONEditor({
344
340
  contentAttributes: {
345
341
  'aria-label': 'Form Input',
346
342
  tabIndex: 0
347
343
  },
348
344
  placeholder: createDataEditorPlaceholder()
349
345
  });
350
- const resultView = resultViewRef.current = new JSONEditor({
346
+ const outputDataEditor = outputDataRef.current = new JSONEditor({
351
347
  readonly: true,
352
- value: toString(resultData),
353
348
  contentAttributes: {
354
349
  'aria-label': 'Form Output',
355
350
  tabIndex: 0
356
351
  }
357
352
  });
358
- const form = formRef.current = new Form({
359
- additionalModules: [...additionalModules, ...viewerAdditionalModules],
353
+ const formViewer = formViewerRef.current = new Form({
354
+ container: viewerContainerRef.current,
355
+ additionalModules: [...(additionalModules || []), ...(viewerAdditionalModules || [])],
360
356
  properties: {
361
- ...viewerProperties,
357
+ ...(viewerProperties || {}),
362
358
  'ariaLabel': 'Form Preview'
363
359
  }
364
360
  });
365
361
  const formEditor = formEditorRef.current = new FormEditor({
362
+ container: editorContainerRef.current,
366
363
  renderer: {
367
364
  compact: true
368
365
  },
@@ -371,17 +368,15 @@ function PlaygroundRoot(props) {
371
368
  },
372
369
  propertiesPanel: {
373
370
  parent: propertiesPanelContainerRef.current,
374
- ...propertiesPanelConfig
371
+ ...(propertiesPanelConfig || {})
375
372
  },
376
373
  exporter: exporterConfig,
377
374
  properties: {
378
- ...editorProperties,
375
+ ...(editorProperties || {}),
379
376
  'ariaLabel': 'Form Definition'
380
377
  },
381
- additionalModules: [...additionalModules, ...editorAdditionalModules]
378
+ additionalModules: [...(additionalModules || []), ...(editorAdditionalModules || [])]
382
379
  });
383
- paletteRef.current = formEditor.get('palette');
384
- propertiesPanelRef.current = formEditor.get('propertiesPanel');
385
380
  formEditor.on('formField.add', ({
386
381
  formField
387
382
  }) => {
@@ -407,7 +402,7 @@ function PlaygroundRoot(props) {
407
402
  ...currentData,
408
403
  [id]: initialDemoData
409
404
  };
410
- dataEditorRef.current.setValue(toString(newData));
405
+ inputDataRef.current.setValue(toString(newData));
411
406
  return newData;
412
407
  });
413
408
  });
@@ -418,10 +413,13 @@ function PlaygroundRoot(props) {
418
413
  // notify interested parties after render
419
414
  emit('formPlayground.rendered');
420
415
  });
421
- form.on('changed', () => {
422
- setResultData(form._getSubmitData());
416
+
417
+ // pipe viewer changes to output data editor
418
+ formViewer.on('changed', () => {
419
+ const submitData = formViewer._getSubmitData();
420
+ outputDataEditor.setValue(toString(submitData));
423
421
  });
424
- dataEditor.on('changed', event => {
422
+ inputDataEditor.on('changed', event => {
425
423
  try {
426
424
  setData(JSON.parse(event.value));
427
425
  } catch (error) {
@@ -429,48 +427,68 @@ function PlaygroundRoot(props) {
429
427
  emit('formPlayground.inputDataError', error);
430
428
  }
431
429
  });
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);
430
+ inputDataEditor.attachTo(inputDataContainerRef.current);
431
+ outputDataEditor.attachTo(outputDataContainerRef.current);
440
432
  return () => {
441
- dataEditor.destroy();
442
- resultView.destroy();
443
- form.destroy();
433
+ inputDataEditor.destroy();
434
+ outputDataEditor.destroy();
435
+ formViewer.destroy();
444
436
  formEditor.destroy();
445
437
  };
446
- }, []);
447
- useEffect(() => {
448
- dataEditorRef.current.setValue(toString(initialData));
449
- }, [initialData]);
438
+ }, [additionalModules, editorAdditionalModules, editorProperties, emit, exporterConfig, propertiesPanelConfig, viewerAdditionalModules, viewerProperties]);
439
+
440
+ // initialize data through props
450
441
  useEffect(() => {
451
- if (initialSchema) {
452
- formEditorRef.current.importSchema(initialSchema);
453
- dataEditorRef.current.setVariables(getSchemaVariables(initialSchema));
442
+ if (!config.initialSchema) {
443
+ return;
454
444
  }
455
- }, [initialSchema]);
445
+ load(config.initialSchema, config.initialData || {});
446
+ }, [config.initialSchema, config.initialData, load]);
447
+ useEffect(() => {
448
+ schema && formViewerRef.current.importSchema(schema, data);
449
+ }, [schema, data]);
456
450
  useEffect(() => {
457
- if (schema && dataContainerRef.current) {
451
+ if (schema && inputDataContainerRef.current) {
458
452
  const variables = getSchemaVariables(schema);
459
- dataEditorRef.current.setVariables(variables);
453
+ inputDataRef.current.setVariables(variables);
460
454
  }
461
455
  }, [schema]);
456
+
457
+ // exposes api to parent
462
458
  useEffect(() => {
463
- schema && formRef.current.importSchema(schema, data);
464
- }, [schema, data]);
465
- useEffect(() => {
466
- resultViewRef.current.setValue(toString(resultData));
467
- }, [resultData]);
459
+ if (!apiLinkTarget) {
460
+ return;
461
+ }
462
+ apiLinkTarget.api = {
463
+ attachDataContainer: node => inputDataRef.current.attachTo(node),
464
+ attachResultContainer: node => outputDataRef.current.attachTo(node),
465
+ attachFormContainer: node => formViewerRef.current.attachTo(node),
466
+ attachEditorContainer: node => formEditorRef.current.attachTo(node),
467
+ attachPaletteContainer: node => formEditorRef.current.get('palette').attachTo(node),
468
+ attachPropertiesPanelContainer: node => formEditorRef.current.get('propertiesPanel').attachTo(node),
469
+ get: (name, strict) => formEditorRef.current.get(name, strict),
470
+ getDataEditor: () => inputDataRef.current,
471
+ getEditor: () => formEditorRef.current,
472
+ getForm: () => formViewerRef.current,
473
+ getResultView: () => outputDataRef.current,
474
+ getSchema: () => formEditorRef.current.getSchema(),
475
+ saveSchema: () => formEditorRef.current.saveSchema(),
476
+ setSchema: setSchema,
477
+ setData: setData
478
+ };
479
+ }, [apiLinkTarget]);
480
+
481
+ // separate effect for state to avoid re-creating the api object every time
468
482
  useEffect(() => {
469
- onStateChanged && onStateChanged({
483
+ if (!apiLinkTarget) {
484
+ return;
485
+ }
486
+ apiLinkTarget.api.getState = () => ({
470
487
  schema,
471
488
  data
472
489
  });
473
- }, [onStateChanged, schema, data]);
490
+ apiLinkTarget.api.load = load;
491
+ }, [apiLinkTarget, schema, data, load]);
474
492
  const handleDownload = useCallback(() => {
475
493
  download(JSON.stringify(schema, null, ' '), 'form.json', 'text/json');
476
494
  }, [schema]);
@@ -518,19 +536,19 @@ function PlaygroundRoot(props) {
518
536
  }), jsx(Section, {
519
537
  name: "Form Preview",
520
538
  children: jsx("div", {
521
- ref: formContainerRef,
539
+ ref: viewerContainerRef,
522
540
  class: "fjs-pgl-form-container"
523
541
  })
524
542
  }), jsx(Section, {
525
543
  name: "Form Input",
526
544
  children: jsx("div", {
527
- ref: dataContainerRef,
545
+ ref: inputDataContainerRef,
528
546
  class: "fjs-pgl-text-container"
529
547
  })
530
548
  }), jsx(Section, {
531
549
  name: "Form Output",
532
550
  children: jsx("div", {
533
- ref: resultContainerRef,
551
+ ref: outputDataContainerRef,
534
552
  class: "fjs-pgl-text-container"
535
553
  })
536
554
  })]
@@ -555,16 +573,11 @@ function createDataEditorPlaceholder() {
555
573
  function Playground(options) {
556
574
  const {
557
575
  container: parent,
558
- schema,
559
- data,
576
+ schema: initialSchema,
577
+ data: initialData,
560
578
  ...rest
561
579
  } = options;
562
580
  const emitter = mitt();
563
- let state = {
564
- data,
565
- schema
566
- };
567
- let ref;
568
581
  const container = document.createElement('div');
569
582
  container.classList.add('fjs-pgl-parent');
570
583
  if (parent) {
@@ -574,63 +587,52 @@ function Playground(options) {
574
587
  const file = files[0];
575
588
  if (file) {
576
589
  try {
577
- ref.setSchema(JSON.parse(file.contents));
590
+ this.api.setSchema(JSON.parse(file.contents));
578
591
  } catch (err) {
579
592
 
580
593
  // TODO(nikku): indicate JSON parse error
581
594
  }
582
595
  }
583
596
  });
584
- const withRef = function (fn) {
597
+ const safe = function (fn) {
585
598
  return function (...args) {
586
- if (!ref) {
599
+ if (!this.api) {
587
600
  throw new Error('Playground is not initialized.');
588
601
  }
589
602
  return fn(...args);
590
603
  };
591
604
  };
592
- const onInit = function (_ref) {
593
- ref = _ref;
594
- emitter.emit('formPlayground.init');
595
- };
596
605
  container.addEventListener('dragover', handleDrop);
597
606
  render(jsx(PlaygroundRoot, {
598
- data: data,
607
+ initialSchema: initialSchema,
608
+ initialData: initialData,
599
609
  emit: emitter.emit,
600
- onInit: onInit,
601
- onStateChanged: _state => state = _state,
602
- schema: schema,
610
+ apiLinkTarget: this,
603
611
  ...rest
604
612
  }), container);
605
613
  this.on = emitter.on;
606
614
  this.off = emitter.off;
607
615
  this.emit = emitter.emit;
608
- this.on('destroy', function () {
616
+ this.on('destroy', () => {
609
617
  render(null, container);
610
- });
611
- this.on('destroy', function () {
612
618
  parent.removeChild(container);
613
619
  });
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));
620
+ this.destroy = () => this.emit('destroy');
621
+ this.getState = safe(() => this.api.getState());
622
+ this.getSchema = safe(() => this.api.getSchema());
623
+ this.setSchema = safe(schema => this.api.setSchema(schema));
624
+ this.saveSchema = safe(() => this.api.saveSchema());
625
+ this.get = safe((name, strict) => this.api.get(name, strict));
626
+ this.getDataEditor = safe(() => this.api.getDataEditor());
627
+ this.getEditor = safe(() => this.api.getEditor());
628
+ this.getForm = safe((name, strict) => this.api.getForm(name, strict));
629
+ this.getResultView = safe(() => this.api.getResultView());
630
+ this.attachEditorContainer = safe(node => this.api.attachEditorContainer(node));
631
+ this.attachPreviewContainer = safe(node => this.api.attachFormContainer(node));
632
+ this.attachDataContainer = safe(node => this.api.attachDataContainer(node));
633
+ this.attachResultContainer = safe(node => this.api.attachResultContainer(node));
634
+ this.attachPaletteContainer = safe(node => this.api.attachPaletteContainer(node));
635
+ this.attachPropertiesPanelContainer = safe(node => this.api.attachPropertiesPanelContainer(node));
634
636
  }
635
637
 
636
638
  export { Playground };