@projectcaluma/ember-form 14.8.2 → 14.9.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.
Files changed (59) hide show
  1. package/addon/components/cf-content.hbs +44 -39
  2. package/addon/components/cf-content.js +50 -14
  3. package/addon/components/cf-field/info.hbs +1 -5
  4. package/addon/components/cf-field/input/checkbox.hbs +3 -1
  5. package/addon/components/cf-field/input/checkbox.js +15 -0
  6. package/addon/components/cf-field/input/date.hbs +38 -31
  7. package/addon/components/cf-field/input/float.hbs +22 -15
  8. package/addon/components/cf-field/input/integer.hbs +22 -15
  9. package/addon/components/cf-field/input/number-separator.hbs +19 -12
  10. package/addon/components/cf-field/input/number-separator.js +10 -2
  11. package/addon/components/cf-field/input/powerselect.hbs +2 -2
  12. package/addon/components/cf-field/input/powerselect.js +12 -0
  13. package/addon/components/cf-field/input/radio.hbs +3 -1
  14. package/addon/components/cf-field/input/radio.js +15 -0
  15. package/addon/components/cf-field/input/static.hbs +0 -1
  16. package/addon/components/cf-field/input/table.hbs +32 -1
  17. package/addon/components/cf-field/input/table.js +10 -0
  18. package/addon/components/cf-field/input/text.hbs +21 -14
  19. package/addon/components/cf-field/input/textarea.hbs +21 -14
  20. package/addon/components/cf-field/input-compare/changes-note.hbs +9 -0
  21. package/addon/components/cf-field/input-compare/text-diff.hbs +6 -0
  22. package/addon/components/cf-field/input-compare/textarea-diff.hbs +6 -0
  23. package/addon/components/cf-field/input-compare.hbs +50 -0
  24. package/addon/components/cf-field/input-compare.js +60 -0
  25. package/addon/components/cf-field/input.hbs +1 -0
  26. package/addon/components/cf-field.hbs +17 -8
  27. package/addon/components/cf-field.js +15 -3
  28. package/addon/components/cf-form-wrapper.hbs +1 -0
  29. package/addon/components/cf-form.hbs +1 -0
  30. package/addon/components/cf-navigation-item.hbs +14 -4
  31. package/addon/components/cf-navigation.hbs +1 -0
  32. package/addon/components/document-validity.js +34 -28
  33. package/addon/components/timeline-select.hbs +20 -0
  34. package/addon/gql/queries/document-answers-compare.graphql +157 -0
  35. package/addon/helpers/get-widget.js +17 -31
  36. package/addon/instance-initializers/form-widget-overrides.js +6 -0
  37. package/addon/lib/answer.js +38 -3
  38. package/addon/lib/compare.js +161 -0
  39. package/addon/lib/document.js +10 -2
  40. package/addon/lib/field.js +81 -1
  41. package/addon/lib/fieldset.js +11 -0
  42. package/addon/lib/navigation.js +51 -1
  43. package/addon/lib/parsers.js +57 -5
  44. package/addon/services/caluma-store.js +12 -0
  45. package/app/components/cf-field/input-compare/changes-note.js +1 -0
  46. package/app/components/cf-field/input-compare/text-diff.js +1 -0
  47. package/app/components/cf-field/input-compare/textarea-diff.js +1 -0
  48. package/app/components/cf-field/input-compare.js +1 -0
  49. package/app/components/timeline-select.js +1 -0
  50. package/app/styles/@projectcaluma/ember-form.scss +1 -0
  51. package/app/styles/_cf-content-compare.scss +214 -0
  52. package/app/styles/_cf-navigation.scss +1 -0
  53. package/package.json +5 -6
  54. package/translations/de.yaml +9 -0
  55. package/translations/en.yaml +9 -0
  56. package/translations/fr.yaml +9 -0
  57. package/translations/it.yaml +9 -0
  58. package/addon/initializers/register-showdown-extensions.js +0 -20
  59. package/app/initializers/register-showdown-extensions.js +0 -4
@@ -0,0 +1,9 @@
1
+ <div
2
+ class="cf-compare-note uk-text-muted uk-text-small uk-flex uk-flex-middle uk-padding-small uk-padding-remove-left uk-padding-remove-bottom"
3
+ >
4
+ {{t
5
+ "caluma.form.compare.note"
6
+ username=(user-name (or @historyUserId @field.answer.raw.historyUserId))
7
+ date=(format-date (or @historyDate @field.answer.raw.historyDate))
8
+ }}
9
+ </div>
@@ -0,0 +1,6 @@
1
+ <div class="uk-form-controls">
2
+ <div class="uk-input uk-disabled">
3
+ <s class="diff-from-version">{{@historicalValue}}</s>
4
+ <span class="diff-to-version">{{@currentValue}}</span>
5
+ </div>
6
+ </div>
@@ -0,0 +1,6 @@
1
+ <div class="uk-form-controls">
2
+ <div class="uk-textarea uk-disabled">
3
+ <div class="diff-from-version"><s>{{@historicalValue}}</s></div>
4
+ <div class="diff-to-version">{{@currentValue}}</div>
5
+ </div>
6
+ </div>
@@ -0,0 +1,50 @@
1
+ {{#let
2
+ (component
3
+ (ensure-safe-component (get-widget @field.question))
4
+ field=@field
5
+ disabled=true
6
+ context=@context
7
+ onSave=(perform this.save)
8
+ )
9
+ as |FieldComponent|
10
+ }}
11
+ {{#if @field.isModified}}
12
+ <div
13
+ class="cf-compare cf-compare-{{@field.question.raw.__typename}}
14
+ {{if (eq @field.answer.raw.historyType '-') 'cf-compare-removed' ''}}
15
+ {{if (eq @field.answer.raw.historyType '+') 'cf-compare-added' ''}}
16
+ {{if (eq @field.answer.raw.historyType '~') 'cf-compare-modified' ''}}
17
+ "
18
+ >
19
+ {{#if this.compareOptions.combined}}
20
+ <div class="combined-diff">
21
+ <div hidden>
22
+ <FieldComponent @compare={{false}} />
23
+ </div>
24
+ <div class="diff-version">
25
+ <FieldComponent @compare={{@compare}} />
26
+ </div>
27
+ </div>
28
+ {{#unless this.compareOptions.disableChangesNote}}
29
+ <div class="changes-note">
30
+ <CfField::InputCompare::ChangesNote @field={{@field}} />
31
+ </div>
32
+ {{/unless}}
33
+ {{else}}
34
+ <div class="from-version">
35
+ <FieldComponent @compare={{@compare}} />
36
+ </div>
37
+ <div class="to-version">
38
+ <FieldComponent @compare={{false}} />
39
+ </div>
40
+ {{#unless this.compareOptions.disableChangesNote}}
41
+ <div class="changes-note">
42
+ <CfField::InputCompare::ChangesNote @field={{@field}} />
43
+ </div>
44
+ {{/unless}}
45
+ {{/if}}
46
+ </div>
47
+ {{else}}
48
+ <FieldComponent @compare={{false}} />
49
+ {{/if}}
50
+ {{/let}}
@@ -0,0 +1,60 @@
1
+ import { service } from "@ember/service";
2
+ import Component from "@glimmer/component";
3
+ import { task } from "ember-concurrency";
4
+
5
+ import { parseWidgetType } from "@projectcaluma/ember-form/lib/parsers";
6
+
7
+ /**
8
+ * Component for wrapping the compare input components
9
+ *
10
+ * @class CfFieldInputCompareComponent
11
+ */
12
+ export default class CfFieldInputCompareComponent extends Component {
13
+ @service calumaOptions;
14
+
15
+ /**
16
+ * The compare input options.
17
+ *
18
+ * @property {} compareOptions
19
+ * @accessor
20
+ */
21
+ get compareOptions() {
22
+ const { widget } = parseWidgetType(this.calumaOptions, [
23
+ this.args.field.question,
24
+ ]);
25
+
26
+ const override = this.calumaOptions
27
+ .getComponentOverrides()
28
+ .find(({ component }) => component === widget);
29
+
30
+ if (widget === "cf-field/input") {
31
+ const typeName = this.args.field?.question?.raw?.__typename;
32
+ const compareOptions = {
33
+ TextQuestion: { combined: true },
34
+ IntegerQuestion: { combined: true },
35
+ MultipleChoiceQuestion: { combined: true },
36
+ ChoiceQuestion: { combined: true },
37
+ DateQuestion: { combined: true },
38
+ TextareaQuestion: { combined: true },
39
+ TableQuestion: { combined: true, disableChangesNote: true },
40
+ };
41
+
42
+ if (typeName in compareOptions) {
43
+ return compareOptions[typeName];
44
+ }
45
+ }
46
+
47
+ return override?.compareOptions;
48
+ }
49
+
50
+ /**
51
+ * In comparison mode, never perform a save, but keep a method
52
+ * to bind to the components.
53
+ *
54
+ * @method save
55
+ * @param {String|Number|String[]} value The new value to save to the field
56
+ */
57
+ save = task({ restartable: true }, async (value) => {
58
+ return value;
59
+ });
60
+ }
@@ -4,5 +4,6 @@
4
4
  @disabled={{@disabled}}
5
5
  @onSave={{@onSave}}
6
6
  @context={{@context}}
7
+ @compare={{@compare}}
7
8
  />
8
9
  </div>
@@ -18,17 +18,26 @@
18
18
 
19
19
  <div class="uk-flex">
20
20
  <div class="uk-width-expand">
21
- {{#let
22
- (component (ensure-safe-component (get-widget @field.question)))
23
- as |FieldComponent|
24
- }}
25
- <FieldComponent
21
+ {{#if @compare}}
22
+ <CfField::InputCompare
26
23
  @field={{@field}}
27
- @disabled={{or @disabled @field.refreshAnswer.isRunning}}
24
+ @disabled={{true}}
28
25
  @context={{@context}}
29
- @onSave={{perform this.save}}
26
+ @compare={{@compare}}
30
27
  />
31
- {{/let}}
28
+ {{else}}
29
+ {{#let
30
+ (component (ensure-safe-component (get-widget @field.question)))
31
+ as |FieldComponent|
32
+ }}
33
+ <FieldComponent
34
+ @field={{@field}}
35
+ @disabled={{or @disabled @field.refreshAnswer.isRunning}}
36
+ @context={{@context}}
37
+ @onSave={{perform this.save}}
38
+ />
39
+ {{/let}}
40
+ {{/if}}
32
41
  </div>
33
42
 
34
43
  {{#if (and @field.question.raw.infoText this.infoTextVisible)}}
@@ -1,8 +1,8 @@
1
1
  import { action } from "@ember/object";
2
2
  import { service } from "@ember/service";
3
- import { macroCondition, isTesting } from "@embroider/macros";
3
+ import { isTesting, macroCondition } from "@embroider/macros";
4
4
  import Component from "@glimmer/component";
5
- import { timeout, task } from "ember-concurrency";
5
+ import { task, timeout } from "ember-concurrency";
6
6
 
7
7
  import { hasQuestionType } from "@projectcaluma/ember-core/helpers/has-question-type";
8
8
 
@@ -31,6 +31,10 @@ export default class CfFieldComponent extends Component {
31
31
  this.args.field._components.delete(this);
32
32
  }
33
33
 
34
+ get compare() {
35
+ return this.args.compare;
36
+ }
37
+
34
38
  get hasHiddenWidget() {
35
39
  return (
36
40
  this.args.field?.question.raw.meta.widgetOverride ===
@@ -77,7 +81,10 @@ export default class CfFieldComponent extends Component {
77
81
  }
78
82
 
79
83
  get saveIndicatorVisible() {
80
- return !hasQuestionType(this.args.field?.question, "action-button");
84
+ return (
85
+ !this.compare &&
86
+ !hasQuestionType(this.args.field?.question, "action-button")
87
+ );
81
88
  }
82
89
 
83
90
  /**
@@ -89,6 +96,11 @@ export default class CfFieldComponent extends Component {
89
96
  * @param {String|Number|String[]} value The new value to save to the field
90
97
  */
91
98
  save = task({ restartable: true }, async (value) => {
99
+ // Do not save when in comparison mode.
100
+ if (this.compare) {
101
+ return;
102
+ }
103
+
92
104
  if (typeof this.args.onSave === "function") {
93
105
  return await this.args.onSave(this.args.field, value);
94
106
  }
@@ -10,6 +10,7 @@
10
10
  @document={{@document}}
11
11
  @fieldset={{@fieldset}}
12
12
  @context={{@context}}
13
+ @compare={{@compare}}
13
14
  @disabled={{@disabled}}
14
15
  @onSave={{@onSave}}
15
16
  />
@@ -7,6 +7,7 @@
7
7
  @field={{field}}
8
8
  @disabled={{@disabled}}
9
9
  @context={{@context}}
10
+ @compare={{@compare}}
10
11
  @onSave={{@onSave}}
11
12
  />
12
13
  {{/if}}
@@ -17,10 +17,19 @@
17
17
  {{@item.label}}
18
18
  </span>
19
19
  {{/if}}
20
- <span
21
- class="cf-navigation__item__icon cf-navigation__item__icon--{{@item.state}}
22
- {{if @item.dirty 'cf-navigation__item__icon--dirty'}}"
23
- ></span>
20
+ {{#if @compare}}
21
+ {{#if (eq @item.compareState "modified")}}
22
+ <UkIcon
23
+ @icon="pencil"
24
+ class="cf-navigation__item__icon cf-navigation__item__icon--compare-{{@item.compareState}}"
25
+ />
26
+ {{/if}}
27
+ {{else}}
28
+ <span
29
+ class="cf-navigation__item__icon cf-navigation__item__icon--{{@item.state}}
30
+ {{if @item.dirty 'cf-navigation__item__icon--dirty'}}"
31
+ ></span>
32
+ {{/if}}
24
33
  </LinkTo>
25
34
 
26
35
  {{#if @item.visibleChildren}}
@@ -29,6 +38,7 @@
29
38
  <CfNavigationItem
30
39
  @item={{child}}
31
40
  @headingLevel={{add @headingLevel 1}}
41
+ @compare={{@compare}}
32
42
  />
33
43
  {{/each}}
34
44
  </ul>
@@ -7,6 +7,7 @@
7
7
  @item={{item}}
8
8
  @useAsHeading={{@useAsHeading}}
9
9
  @headingLevel={{@headingBaseLevel}}
10
+ @compare={{@compare}}
10
11
  />
11
12
  {{#unless item.fieldset.field}}
12
13
  <hr class="uk-divider-small uk-margin-small" />
@@ -70,34 +70,7 @@ export default class DocumentValidity extends Component {
70
70
  ),
71
71
  );
72
72
 
73
- const { isValid, errors } = await this.apollo.query(
74
- {
75
- query: documentValidityQuery,
76
- fetchPolicy: "network-only",
77
- variables: { id: this.args.document.uuid },
78
- },
79
- "documentValidity.edges.0.node",
80
- );
81
-
82
- if (!isValid) {
83
- errors
84
- .filter(({ errorCode }) => errorCode === "format_validation_failed")
85
- .forEach(({ slug, errorMsg, documentId }) => {
86
- const pk = `Document:${documentId}:Question:${slug}`;
87
- const field = this.calumaStore.findByPk(pk);
88
- const parentField = field.document.parentField;
89
-
90
- // Add the error manually as the frontend does not validate format
91
- // validators - only the backend.
92
- field.addManualError("format", { errorMsg }, field.value);
93
-
94
- if (parentField) {
95
- // If the affected field is in a table row, we need to mark the
96
- // table answer as invalid as well.
97
- parentField.addManualError("table", {}, null);
98
- }
99
- });
100
- }
73
+ await this.#runBackendValidation();
101
74
 
102
75
  if (this.isValid) {
103
76
  this.args.onValid?.();
@@ -111,6 +84,39 @@ export default class DocumentValidity extends Component {
111
84
  }
112
85
  });
113
86
 
87
+ async #runBackendValidation() {
88
+ if (this.args.skipBackendValidation) return;
89
+
90
+ const { isValid, errors } = await this.apollo.query(
91
+ {
92
+ query: documentValidityQuery,
93
+ fetchPolicy: "network-only",
94
+ variables: { id: this.args.document.uuid },
95
+ },
96
+ "documentValidity.edges.0.node",
97
+ );
98
+
99
+ if (!isValid) {
100
+ errors
101
+ .filter(({ errorCode }) => errorCode === "format_validation_failed")
102
+ .forEach(({ slug, errorMsg, documentId }) => {
103
+ const pk = `Document:${documentId}:Question:${slug}`;
104
+ const field = this.calumaStore.findByPk(pk);
105
+ const parentField = field.document.parentField;
106
+
107
+ // Add the error manually as the frontend does not validate format
108
+ // validators - only the backend.
109
+ field.addManualError("format", { errorMsg }, field.value);
110
+
111
+ if (parentField) {
112
+ // If the affected field is in a table row, we need to mark the
113
+ // table answer as invalid as well.
114
+ parentField.addManualError("table", {}, null);
115
+ }
116
+ });
117
+ }
118
+ }
119
+
114
120
  @action
115
121
  validate() {
116
122
  return this._validate.perform();
@@ -0,0 +1,20 @@
1
+ <PowerSelect
2
+ class="uk-margin-bottom"
3
+ @selected={{@selected}}
4
+ @options={{@options}}
5
+ @searchEnabled={{false}}
6
+ @searchField="label"
7
+ @allowClear={{true}}
8
+ @onChange={{@onChange}}
9
+ as |option|
10
+ >
11
+ {{#if (eq option.id "current")}}
12
+ {{t "caluma.form.compare.timeline.current"}}
13
+ {{else}}
14
+ {{option.label}}:
15
+ {{format-date option.startDate}}
16
+ {{#if option.endDate}}
17
+ -
18
+ {{format-date option.endDate}}{{/if}}
19
+ {{/if}}
20
+ </PowerSelect>
@@ -0,0 +1,157 @@
1
+ #import FieldQuestion, SimpleQuestion, FieldTableQuestion from '../fragments/field.graphql'
2
+
3
+ fragment SimpleRevisionAnswer on HistoricalAnswer {
4
+ id
5
+ historyDate
6
+ historyUserId
7
+ historyType
8
+ question {
9
+ slug
10
+ label
11
+ ... on ChoiceQuestion {
12
+ choiceOptions: options {
13
+ edges {
14
+ node {
15
+ slug
16
+ label
17
+ }
18
+ }
19
+ }
20
+ }
21
+ ... on DynamicChoiceQuestion {
22
+ dynamicChoiceOptions: options(context: $context) {
23
+ edges {
24
+ node {
25
+ slug
26
+ label
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ... on MultipleChoiceQuestion {
32
+ multipleChoiceOptions: options {
33
+ edges {
34
+ node {
35
+ slug
36
+ label
37
+ }
38
+ }
39
+ }
40
+ }
41
+ ... on DynamicMultipleChoiceQuestion {
42
+ dynamicMultipleChoiceOptions: options(context: $context) {
43
+ edges {
44
+ node {
45
+ slug
46
+ label
47
+ }
48
+ }
49
+ }
50
+ }
51
+ __typename
52
+ }
53
+ ... on HistoricalStringAnswer {
54
+ historyId
55
+ stringValue: value
56
+ }
57
+ ... on HistoricalListAnswer {
58
+ historyId
59
+ listValue: value
60
+ }
61
+ ... on HistoricalIntegerAnswer {
62
+ historyId
63
+ integerValue: value
64
+ }
65
+ ... on HistoricalFloatAnswer {
66
+ historyId
67
+ floatValue: value
68
+ }
69
+ ... on HistoricalDateAnswer {
70
+ historyId
71
+ dateValue: value
72
+ }
73
+ __typename
74
+ }
75
+
76
+ fragment HistoricalTableValue on HistoricalDocument {
77
+ id: documentId
78
+ documentId: id
79
+ historyDate
80
+ historyType
81
+ historyUserId
82
+ form {
83
+ id
84
+ slug
85
+ questions {
86
+ edges {
87
+ node {
88
+ ...FieldQuestion
89
+ }
90
+ }
91
+ }
92
+ }
93
+ __typename
94
+ }
95
+
96
+ fragment RevisionDocument on HistoricalDocument {
97
+ historicalDocumentId: id
98
+ id: documentId
99
+ form {
100
+ id
101
+ slug
102
+ }
103
+ }
104
+
105
+ query (
106
+ $documentId: ID!
107
+ $from: DateTime!
108
+ $to: DateTime!
109
+ $context: JSONString
110
+ ) {
111
+ fromRevision: documentAsOf(id: $documentId, asOf: $from) {
112
+ ...RevisionDocument
113
+ answers: historicalAnswers(asOf: $from, excludeDeleted: true) {
114
+ edges {
115
+ node {
116
+ ...SimpleRevisionAnswer
117
+ ... on HistoricalTableAnswer {
118
+ tableValue: value(asOf: $from, excludeDeleted: true) {
119
+ ...HistoricalTableValue
120
+
121
+ answers: historicalAnswers(asOf: $from, excludeDeleted: true) {
122
+ edges {
123
+ node {
124
+ ...SimpleRevisionAnswer
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ toRevision: documentAsOf(id: $documentId, asOf: $to) {
135
+ ...RevisionDocument
136
+ answers: historicalAnswers(asOf: $to) {
137
+ edges {
138
+ node {
139
+ ...SimpleRevisionAnswer
140
+ ... on HistoricalTableAnswer {
141
+ tableValue: value(asOf: $to) {
142
+ ...HistoricalTableValue
143
+
144
+ answers: historicalAnswers(asOf: $to) {
145
+ edges {
146
+ node {
147
+ ...SimpleRevisionAnswer
148
+ }
149
+ }
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
@@ -1,10 +1,10 @@
1
1
  import Helper from "@ember/component/helper";
2
- import { warn } from "@ember/debug";
3
2
  import { inject as service } from "@ember/service";
4
3
  import { ensureSafeComponent } from "@embroider/util";
5
4
 
6
5
  import InputComponent from "@projectcaluma/ember-form/components/cf-field/input";
7
6
  import FormComponent from "@projectcaluma/ember-form/components/cf-form";
7
+ import { parseWidgetType } from "@projectcaluma/ember-form/lib/parsers";
8
8
 
9
9
  const DEFAULT_WIDGETS = {
10
10
  "cf-field/input": InputComponent,
@@ -33,38 +33,24 @@ const DEFAULT_WIDGETS = {
33
33
  export default class GetWidgetHelper extends Helper {
34
34
  @service calumaOptions;
35
35
 
36
- compute(params, { default: defaultWidget = "cf-field/input" }) {
37
- for (const obj of params) {
38
- let widget = obj?.raw?.meta?.widgetOverride;
39
-
40
- if (obj?.useNumberSeparatorWidget) {
41
- widget = "cf-field/input/number-separator";
42
- }
43
-
44
- if (!widget) {
45
- continue;
46
- }
47
-
48
- const override =
49
- widget &&
50
- this.calumaOptions
51
- .getComponentOverrides()
52
- .find(({ component }) => component === widget);
53
-
54
- warn(
55
- `Widget override "${widget}" is not registered. Please register it by calling \`calumaOptions.registerComponentOverride\``,
56
- override,
57
- { id: "ember-caluma.unregistered-override" },
36
+ compute(params, options = {}) {
37
+ const { widget, override } = parseWidgetType(
38
+ this.calumaOptions,
39
+ params,
40
+ options,
41
+ );
42
+
43
+ if (override) {
44
+ const overrideWidget = this.calumaOptions
45
+ .getComponentOverrides()
46
+ .find(({ component }) => component === widget);
47
+
48
+ return ensureSafeComponent(
49
+ overrideWidget.componentClass ?? overrideWidget.component,
50
+ this,
58
51
  );
59
-
60
- if (override) {
61
- return ensureSafeComponent(
62
- override.componentClass ?? override.component,
63
- this,
64
- );
65
- }
66
52
  }
67
53
 
68
- return ensureSafeComponent(DEFAULT_WIDGETS[defaultWidget], this);
54
+ return ensureSafeComponent(DEFAULT_WIDGETS[widget], this);
69
55
  }
70
56
  }
@@ -33,6 +33,9 @@ class PowerSelectOverride {
33
33
  "DynamicChoiceQuestion",
34
34
  "DynamicMultipleChoiceQuestion",
35
35
  ];
36
+ compareOptions = {
37
+ combined: false,
38
+ };
36
39
  }
37
40
 
38
41
  class NumberSeparatorOverride {
@@ -47,6 +50,9 @@ class NumberSeparatorOverride {
47
50
  component = "cf-field/input/number-separator";
48
51
  componentClass = NumberSeparatorComponent;
49
52
  types = ["IntegerQuestion", "FloatQuestion", "CalculatedFloatQuestion"];
53
+ compareOptions = {
54
+ combined: true,
55
+ };
50
56
  }
51
57
 
52
58
  export function initialize(appInstance) {