@valtimo/components 13.50.1 → 13.51.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.
@@ -53,7 +53,7 @@ import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
53
53
  import { trigger, transition, style, animate } from '@angular/animations';
54
54
  import * as i1$6 from '@angular/cdk/overlay';
55
55
  import { OverlayModule, Overlay } from '@angular/cdk/overlay';
56
- import Currency from '@tadashi/currency';
56
+ import AutoNumeric from 'autonumeric';
57
57
  import flatpickr from 'flatpickr';
58
58
  import { Dutch } from 'flatpickr/dist/l10n/nl';
59
59
  import createDOMPurify from 'dompurify';
@@ -12878,108 +12878,134 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImpo
12878
12878
  * See the License for the specific language governing permissions and
12879
12879
  * limitations under the License.
12880
12880
  */
12881
+ const DEFAULT_LOCALE = 'nl-NL';
12882
+ const DEFAULT_CURRENCY = 'EUR';
12883
+ const RAW_VALUE_MODIFIED_EVENT = 'autoNumeric:rawValueModified';
12881
12884
  /**
12882
12885
  * Custom formio component for currency number.
12883
12886
  */
12884
12887
  class FormIoCurrencyComponent {
12885
12888
  constructor() {
12886
- this.currencyForm = new FormGroup({
12887
- currencyValue: new FormControl(''),
12888
- });
12889
12889
  this._value = null;
12890
12890
  this.valueChange = new EventEmitter();
12891
- this._subscriptions = new Subscription();
12891
+ this._disabled = false;
12892
+ this._autoNumeric = null;
12893
+ this._rendering = false;
12894
+ this._rawValueModifiedListener = () => this.onRawValueModified();
12892
12895
  }
12893
12896
  get value() {
12894
12897
  return this._value;
12895
12898
  }
12896
12899
  set value(value) {
12897
- this._value = value;
12898
- // Rendering only — emitting here would write the rendered value back over the one form.io
12899
- // is about to hand a freshly redrawn component.
12900
- this.currencyForm.setValue({
12901
- currencyValue: Currency.masking(value, this.maskOpts),
12902
- }, { emitEvent: false });
12900
+ this._value = typeof value === 'number' ? value : null;
12901
+ this.renderValue();
12902
+ }
12903
+ get disabled() {
12904
+ return this._disabled;
12903
12905
  }
12904
12906
  set disabled(value) {
12905
- if (value) {
12906
- this.currencyForm.disable({ emitEvent: false });
12907
+ this._disabled = !!value;
12908
+ }
12909
+ ngAfterViewInit() {
12910
+ const element = this.currencyElement.nativeElement;
12911
+ this._autoNumeric = new AutoNumeric(element, this.autoNumericOptions);
12912
+ element.addEventListener(RAW_VALUE_MODIFIED_EVENT, this._rawValueModifiedListener);
12913
+ this.renderValue();
12914
+ }
12915
+ ngOnChanges(changes) {
12916
+ if (this._autoNumeric &&
12917
+ (changes.currencyLocale || changes.currencyCurrency || changes.allowEmptyValue)) {
12918
+ this.withoutEmitting(() => this._autoNumeric.update(this.autoNumericOptions));
12919
+ this.renderValue();
12907
12920
  }
12908
- else {
12909
- this.currencyForm.enable({ emitEvent: false });
12921
+ }
12922
+ ngOnDestroy() {
12923
+ if (!this._autoNumeric) {
12924
+ return;
12910
12925
  }
12926
+ this.currencyElement.nativeElement.removeEventListener(RAW_VALUE_MODIFIED_EVENT, this._rawValueModifiedListener);
12927
+ this._autoNumeric.remove();
12928
+ this._autoNumeric = null;
12911
12929
  }
12912
- // A fresh object every call: Currency's constructor sets viaInput on the options it is handed.
12913
- get maskOpts() {
12930
+ /**
12931
+ * Derives the AutoNumeric formatting from the Intl formatting of the configured locale and
12932
+ * currency, so the input renders amounts exactly as that locale writes them.
12933
+ */
12934
+ get autoNumericOptions() {
12935
+ const formatter = new Intl.NumberFormat(this.currencyLocale || DEFAULT_LOCALE, {
12936
+ style: 'currency',
12937
+ currency: this.currencyCurrency || DEFAULT_CURRENCY,
12938
+ });
12939
+ const parts = formatter.formatToParts(-1234567.89);
12940
+ const partValue = (type) => parts.find(part => part.type === type)?.value ?? '';
12941
+ const indexOf = (type) => parts.findIndex(part => part.type === type);
12942
+ const currencyIndex = indexOf('currency');
12943
+ const firstIntegerIndex = indexOf('integer');
12944
+ const minusSignIndex = indexOf('minusSign');
12945
+ const currencyIsPrefix = currencyIndex < firstIntegerIndex;
12946
+ const spacing = parts[currencyIsPrefix ? currencyIndex + 1 : currencyIndex - 1];
12947
+ const literal = spacing?.type === 'literal' ? spacing.value : '';
12948
+ const currencySymbol = currencyIsPrefix
12949
+ ? `${partValue('currency')}${literal}`
12950
+ : `${literal}${partValue('currency')}`;
12951
+ const negativePositiveSignPlacement = currencyIsPrefix
12952
+ ? minusSignIndex < currencyIndex
12953
+ ? 'l'
12954
+ : 'r'
12955
+ : minusSignIndex < firstIntegerIndex
12956
+ ? 'p'
12957
+ : minusSignIndex < currencyIndex
12958
+ ? 'l'
12959
+ : 's';
12914
12960
  return {
12915
- empty: this.allowEmptyValue || false,
12916
- locales: this.currencyLocale || 'nl-NL',
12917
- digits: 2,
12918
- options: {
12919
- style: 'currency',
12920
- currency: this.currencyCurrency || 'EUR',
12921
- },
12961
+ currencySymbol,
12962
+ currencySymbolPlacement: currencyIsPrefix ? 'p' : 's',
12963
+ negativePositiveSignPlacement,
12964
+ digitGroupSeparator: partValue('group'),
12965
+ decimalCharacter: partValue('decimal') || '.',
12966
+ decimalPlaces: formatter.resolvedOptions().maximumFractionDigits,
12967
+ emptyInputBehavior: 'always',
12968
+ modifyValueOnWheel: false,
12922
12969
  };
12923
12970
  }
12924
- ngOnInit() {
12925
- this._subscriptions.add(this.currencyForm.valueChanges.subscribe(() => {
12926
- // No mask yet: the value is being rendered, not typed, so there is nothing to write back.
12927
- if (!this._currencyInstance) {
12928
- return;
12929
- }
12930
- const unmasked = this._currencyInstance.getUnmasked(this.currencyForm.value.currencyValue);
12931
- if (unmasked === 0 && this.allowEmptyValue) {
12932
- this._value = null;
12933
- this.valueChange.emit(null);
12971
+ onRawValueModified() {
12972
+ if (this._rendering || !this._autoNumeric) {
12973
+ return;
12974
+ }
12975
+ const numericString = this._autoNumeric.getNumericString();
12976
+ const isEmpty = numericString === null || numericString === '';
12977
+ const value = isEmpty ? (this.allowEmptyValue ? null : 0) : Number(numericString);
12978
+ this._value = value;
12979
+ this.valueChange.emit(value);
12980
+ }
12981
+ renderValue() {
12982
+ if (!this._autoNumeric) {
12983
+ return;
12984
+ }
12985
+ this.withoutEmitting(() => {
12986
+ if (typeof this._value === 'number') {
12987
+ this._autoNumeric.set(this._value);
12934
12988
  }
12935
12989
  else {
12936
- this._value = unmasked;
12937
- this.valueChange.emit(unmasked);
12990
+ this._autoNumeric.clear();
12938
12991
  }
12939
- }));
12940
- }
12941
- ngOnDestroy() {
12942
- this._subscriptions.unsubscribe();
12943
- }
12944
- ngAfterViewInit() {
12945
- this._currencyInstance = new Currency(this.currencyElement.nativeElement, {
12946
- maskOpts: this.maskOpts,
12947
12992
  });
12948
- // A value can arrive before the view exists, so its render is redone here with the mask in
12949
- // place.
12950
- if (typeof this._value === 'number') {
12951
- this.renderValue();
12952
- }
12953
12993
  }
12954
- ngOnChanges(changes) {
12955
- if (changes.currencyLocale || changes.currencyCurrency || changes.allowEmptyValue) {
12956
- // ngOnChanges runs before ngAfterViewInit, which is where the mask is created from the
12957
- // current inputs, so there is nothing to update yet on the first run.
12958
- if (this._currencyInstance) {
12959
- if (typeof this.currencyLocale === 'string') {
12960
- this._currencyInstance.opts.maskOpts.locales = this.currencyLocale;
12961
- }
12962
- if (typeof this.currencyCurrency === 'string') {
12963
- this._currencyInstance.opts.maskOpts.options.currency = this.currencyCurrency;
12964
- }
12965
- if (typeof this.allowEmptyValue === 'boolean') {
12966
- this._currencyInstance.opts.maskOpts.empty = this.allowEmptyValue;
12967
- }
12968
- }
12969
- this.renderValue();
12994
+ withoutEmitting(action) {
12995
+ this._rendering = true;
12996
+ try {
12997
+ action();
12998
+ }
12999
+ finally {
13000
+ this._rendering = false;
12970
13001
  }
12971
- }
12972
- renderValue() {
12973
- this.currencyForm.setValue({
12974
- currencyValue: typeof this._value === 'number' ? Currency.masking(this._value, this.maskOpts) : '',
12975
- }, { emitEvent: false });
12976
13002
  }
12977
13003
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: FormIoCurrencyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12978
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.25", type: FormIoCurrencyComponent, isStandalone: false, selector: "valtimo-currency", inputs: { value: "value", disabled: "disabled", currencyLocale: "currencyLocale", currencyCurrency: "currencyCurrency", allowEmptyValue: "allowEmptyValue" }, outputs: { valueChange: "valueChange" }, viewQueries: [{ propertyName: "currencyElement", first: true, predicate: ["currencyElement"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div class=\"currencyForm-container\" [formGroup]=\"currencyForm\">\n <div class=\"input-group\">\n <input #currencyElement id=\"currency\" formControlName=\"currencyValue\" class=\"form-control\" />\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] }); }
13004
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.25", type: FormIoCurrencyComponent, isStandalone: false, selector: "valtimo-currency", inputs: { value: "value", disabled: "disabled", currencyLocale: "currencyLocale", currencyCurrency: "currencyCurrency", allowEmptyValue: "allowEmptyValue" }, outputs: { valueChange: "valueChange" }, viewQueries: [{ propertyName: "currencyElement", first: true, predicate: ["currencyElement"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div class=\"currencyForm-container\">\n <div class=\"input-group\">\n <input\n #currencyElement\n id=\"currency\"\n class=\"form-control\"\n inputmode=\"decimal\"\n [disabled]=\"disabled\"\n />\n </div>\n</div>\n" }); }
12979
13005
  }
12980
13006
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: FormIoCurrencyComponent, decorators: [{
12981
13007
  type: Component,
12982
- args: [{ selector: 'valtimo-currency', standalone: false, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div class=\"currencyForm-container\" [formGroup]=\"currencyForm\">\n <div class=\"input-group\">\n <input #currencyElement id=\"currency\" formControlName=\"currencyValue\" class=\"form-control\" />\n </div>\n</div>\n" }]
13008
+ args: [{ selector: 'valtimo-currency', standalone: false, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div class=\"currencyForm-container\">\n <div class=\"input-group\">\n <input\n #currencyElement\n id=\"currency\"\n class=\"form-control\"\n inputmode=\"decimal\"\n [disabled]=\"disabled\"\n />\n </div>\n</div>\n" }]
12983
13009
  }], propDecorators: { currencyElement: [{
12984
13010
  type: ViewChild,
12985
13011
  args: ['currencyElement']