@ng-formworks/core
Version:
Angular ng-formworks - JSON Schema Form builder core
1,216 lines (1,206 loc) • 619 kB
JavaScript
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { Injectable, inject, NgZone, input, viewChild, ViewContainerRef, Component, Input, Directive, ChangeDetectionStrategy, ViewChild, ChangeDetectorRef, signal, Pipe, Inject, ElementRef, NgModule, forwardRef, output } from '@angular/core';
import * as i1 from '@angular/forms';
import { UntypedFormControl, UntypedFormArray, UntypedFormGroup, FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
import addFormats from 'ajv-formats';
import Ajv2019 from 'ajv/dist/2019';
import jsonDraft6 from 'ajv/lib/refs/json-schema-draft-06.json';
import jsonDraft7 from 'ajv/lib/refs/json-schema-draft-07.json';
import cloneDeep from 'lodash/cloneDeep';
import _isArray from 'lodash/isArray';
import _template from 'lodash/template';
import { from, Observable, forkJoin, Subject, BehaviorSubject, debounceTime, distinctUntilChanged, of, lastValueFrom } from 'rxjs';
import { some, isNil, isEmpty as isEmpty$1, pick, isObject as isObject$1, isEqual as isEqual$2, memoize } from 'lodash';
import isEqual$1 from 'lodash/isEqual';
import { map, takeUntil } from 'rxjs/operators';
import omit from 'lodash/omit';
import filter from 'lodash/filter';
import map$1 from 'lodash/map';
import _isPlainObject from 'lodash/isPlainObject';
import uniqueId from 'lodash/uniqueId';
import { Eta } from 'eta/core';
import * as i1$1 from '@angular/cdk/drag-drop';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { HttpClient } from '@angular/common/http';
class Framework {
constructor() {
this.widgets = {};
this.stylesheets = [];
this.scripts = [];
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: Framework, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: Framework }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.2", ngImport: i0, type: Framework, decorators: [{
type: Injectable
}] });
const deValidationMessages = {
required: 'Darf nicht leer sein',
minLength: 'Mindestens {{minimumLength}} Zeichen benötigt (aktuell: {{currentLength}})',
maxLength: 'Maximal {{maximumLength}} Zeichen erlaubt (aktuell: {{currentLength}})',
pattern: 'Entspricht nicht diesem regulären Ausdruck: {{requiredPattern}}',
format: function (error) {
switch (error.requiredFormat) {
case 'date':
return 'Muss ein Datum sein, z. B. "2000-12-31"';
case 'time':
return 'Muss eine Zeitangabe sein, z. B. "16:20" oder "03:14:15.9265"';
case 'date-time':
return 'Muss Datum mit Zeit beinhalten, z. B. "2000-03-14T01:59" oder "2000-03-14T01:59:26.535Z"';
case 'email':
return 'Keine gültige E-Mail-Adresse (z. B. "name@example.com")';
case 'hostname':
return 'Kein gültiger Hostname (z. B. "example.com")';
case 'ipv4':
return 'Keine gültige IPv4-Adresse (z. B. "127.0.0.1")';
case 'ipv6':
return 'Keine gültige IPv6-Adresse (z. B. "1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0")';
// TODO: add examples for 'uri', 'uri-reference', and 'uri-template'
// case 'uri': case 'uri-reference': case 'uri-template':
case 'url':
return 'Keine gültige URL (z. B. "http://www.example.com/page.html")';
case 'uuid':
return 'Keine gültige UUID (z. B. "12345678-9ABC-DEF0-1234-56789ABCDEF0")';
case 'color':
return 'Kein gültiger Farbwert (z. B. "#FFFFFF")';
case 'json-pointer':
return 'Kein gültiger JSON-Pointer (z. B. "/pointer/to/something")';
case 'relative-json-pointer':
return 'Kein gültiger relativer JSON-Pointer (z. B. "2/pointer/to/something")';
case 'regex':
return 'Kein gültiger regulärer Ausdruck (z. B. "(1-)?\\d{3}-\\d{3}-\\d{4}")';
case 'duration':
return "Muss eine gültige ISO 8601-Dauer sein (z. B. 'PT1H30M')";
default:
return 'Muss diesem Format entsprechen: ' + error.requiredFormat;
}
},
minimum: 'Muss mindestens {{minimumValue}} sein',
exclusiveMinimum: 'Muss größer als {{exclusiveMinimumValue}} sein',
maximum: 'Darf maximal {{maximumValue}} sein',
exclusiveMaximum: 'Muss kleiner als {{exclusiveMaximumValue}} sein',
multipleOf: function (error) {
if ((1 / error.multipleOfValue) % 10 === 0) {
const decimals = Math.log10(1 / error.multipleOfValue);
return `Maximal ${decimals} Dezimalstellen erlaubt`;
}
else {
return `Muss ein Vielfaches von ${error.multipleOfValue} sein`;
}
},
minProperties: 'Mindestens {{minimumProperties}} Attribute erforderlich (aktuell: {{currentProperties}})',
maxProperties: 'Maximal {{maximumProperties}} Attribute erlaubt (aktuell: {{currentProperties}})',
minItems: 'Mindestens {{minimumItems}} Werte erforderlich (aktuell: {{currentItems}})',
maxItems: 'Maximal {{maximumItems}} Werte erlaubt (aktuell: {{currentItems}})',
uniqueItems: 'Alle Werte müssen eindeutig sein',
// Note: No default error messages for 'type', 'const', 'enum', or 'dependencies'
};
const enValidationMessages = {
required: 'This field is required.',
minLength: 'Must be {{minimumLength}} characters or longer (current length: {{currentLength}})',
maxLength: 'Must be {{maximumLength}} characters or shorter (current length: {{currentLength}})',
pattern: 'Must match pattern: {{requiredPattern}}',
format: function (error) {
switch (error.requiredFormat) {
case 'date':
return 'Must be a date, like "2000-12-31"';
case 'time':
return 'Must be a time, like "16:20" or "03:14:15.9265"';
case 'date-time':
return 'Must be a date-time, like "2000-03-14T01:59" or "2000-03-14T01:59:26.535Z"';
case 'email':
return 'Must be an email address, like "name@example.com"';
case 'hostname':
return 'Must be a hostname, like "example.com"';
case 'ipv4':
return 'Must be an IPv4 address, like "127.0.0.1"';
case 'ipv6':
return 'Must be an IPv6 address, like "1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0"';
// TODO: add examples for 'uri', 'uri-reference', and 'uri-template'
// case 'uri': case 'uri-reference': case 'uri-template':
case 'url':
return 'Must be a url, like "http://www.example.com/page.html"';
case 'uuid':
return 'Must be a uuid, like "12345678-9ABC-DEF0-1234-56789ABCDEF0"';
case 'color':
return 'Must be a color, like "#FFFFFF"';
case 'json-pointer':
return 'Must be a JSON Pointer, like "/pointer/to/something"';
case 'relative-json-pointer':
return 'Must be a relative JSON Pointer, like "2/pointer/to/something"';
case 'regex':
return 'Must be a regular expression, like "(1-)?\\d{3}-\\d{3}-\\d{4}"';
case 'duration':
return "Must be a valid ISO 8601 duration (e.g., 'PT1H30M')";
default:
return 'Must be a correctly formatted ' + error.requiredFormat;
}
},
minimum: 'Must be {{minimumValue}} or more',
exclusiveMinimum: 'Must be more than {{exclusiveMinimumValue}}',
maximum: 'Must be {{maximumValue}} or less',
exclusiveMaximum: 'Must be less than {{exclusiveMaximumValue}}',
multipleOf: function (error) {
if ((1 / error.multipleOfValue) % 10 === 0) {
const decimals = Math.log10(1 / error.multipleOfValue);
return `Must have ${decimals} or fewer decimal places.`;
}
else {
return `Must be a multiple of ${error.multipleOfValue}.`;
}
},
minProperties: 'Must have {{minimumProperties}} or more items (current items: {{currentProperties}})',
maxProperties: 'Must have {{maximumProperties}} or fewer items (current items: {{currentProperties}})',
minItems: 'Must have {{minimumItems}} or more items (current items: {{currentItems}})',
maxItems: 'Must have {{maximumItems}} or fewer items (current items: {{currentItems}})',
uniqueItems: 'All items must be unique',
// Note: No default error messages for 'type', 'const', 'enum', or 'dependencies'
};
const esValidationMessages = {
required: 'Este campo está requerido.',
minLength: 'Debe tener {{minimumLength}} caracteres o más longitud (longitud actual: {{currentLength}})',
maxLength: 'Debe tener {{maximumLength}} caracteres o menos longitud (longitud actual: {{currentLength}})',
pattern: 'Must match pattern: {{requiredPattern}}',
format: function (error) {
switch (error.requiredFormat) {
case 'date':
return 'Debe tener una fecha, ej "2000-12-31"';
case 'time':
return 'Debe tener una hora, ej "16:20" o "03:14:15.9265"';
case 'date-time':
return 'Debe tener fecha y hora, ej "2000-03-14T01:59" o "2000-03-14T01:59:26.535Z"';
case 'email':
return 'No hay dirección de correo electrónico válida, ej "name@example.com"';
case 'hostname':
return 'Debe ser un nombre de host válido, ej "example.com"';
case 'ipv4':
return 'Debe ser una dirección de IPv4, ej "127.0.0.1"';
case 'ipv6':
return 'Debe ser una dirección de IPv6, ej "1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0"';
case 'url':
return 'Debe ser una URL, ej "http://www.example.com/page.html"';
case 'uuid':
return 'Debe ser un UUID, ej "12345678-9ABC-DEF0-1234-56789ABCDEF0"';
case 'color':
return 'Debe ser un color, ej "#FFFFFF"';
case 'json-pointer':
return 'Debe ser un JSON Pointer, ej "/pointer/to/something"';
case 'relative-json-pointer':
return 'Debe ser un JSON Pointer relativo, ej "2/pointer/to/something"';
case 'regex':
return 'Debe ser una expresión regular, ej "(1-)?\\d{3}-\\d{3}-\\d{4}"';
case 'duration':
return "Debe ser una duración válida en formato ISO 8601 (p. ej., 'PT1H30M')";
default:
return 'Debe tener el formato correcto ' + error.requiredFormat;
}
},
minimum: 'Debe ser {{minimumValue}} o más',
exclusiveMinimum: 'Debe ser superior a {{exclusiveMinimumValue}}',
maximum: 'Debe ser {{maximumValue}} o menos',
exclusiveMaximum: 'Debe ser menor que {{exclusiveMaximumValue}}',
multipleOf: function (error) {
if ((1 / error.multipleOfValue) % 10 === 0) {
const decimals = Math.log10(1 / error.multipleOfValue);
return `Se permite un máximo de ${decimals} decimales`;
}
else {
return `Debe ser múltiplo de ${error.multipleOfValue}.`;
}
},
minProperties: 'Debe tener {{minimumProperties}} o más elementos (elementos actuales: {{currentProperties}})',
maxProperties: 'Debe tener {{maximumProperties}} o menos elementos (elementos actuales: {{currentProperties}})',
minItems: 'Debe tener {{minimumItems}} o más elementos (elementos actuales: {{currentItems}})',
maxItems: 'Debe tener {{maximumItems}} o menos elementos (elementos actuales: {{currentItems}})',
uniqueItems: 'Todos los elementos deben ser únicos',
};
const frValidationMessages = {
required: 'Est obligatoire.',
minLength: 'Doit avoir minimum {{minimumLength}} caractères (actuellement: {{currentLength}})',
maxLength: 'Doit avoir maximum {{maximumLength}} caractères (actuellement: {{currentLength}})',
pattern: 'Doit respecter: {{requiredPattern}}',
format: function (error) {
switch (error.requiredFormat) {
case 'date':
return 'Doit être une date, tel que "2000-12-31"';
case 'time':
return 'Doit être une heure, tel que "16:20" ou "03:14:15.9265"';
case 'date-time':
return 'Doit être une date et une heure, tel que "2000-03-14T01:59" ou "2000-03-14T01:59:26.535Z"';
case 'email':
return 'Doit être une adresse e-mail, tel que "name@example.com"';
case 'hostname':
return 'Doit être un nom de domaine, tel que "example.com"';
case 'ipv4':
return 'Doit être une adresse IPv4, tel que "127.0.0.1"';
case 'ipv6':
return 'Doit être une adresse IPv6, tel que "1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0"';
// TODO: add examples for 'uri', 'uri-reference', and 'uri-template'
// case 'uri': case 'uri-reference': case 'uri-template':
case 'url':
return 'Doit être une URL, tel que "http://www.example.com/page.html"';
case 'uuid':
return 'Doit être un UUID, tel que "12345678-9ABC-DEF0-1234-56789ABCDEF0"';
case 'color':
return 'Doit être une couleur, tel que "#FFFFFF"';
case 'json-pointer':
return 'Doit être un JSON Pointer, tel que "/pointer/to/something"';
case 'relative-json-pointer':
return 'Doit être un relative JSON Pointer, tel que "2/pointer/to/something"';
case 'regex':
return 'Doit être une expression régulière, tel que "(1-)?\\d{3}-\\d{3}-\\d{4}"';
case 'duration':
return "Doit être une durée valide au format ISO 8601 (par ex., 'PT1H30M')";
default:
return 'Doit être avoir le format correct: ' + error.requiredFormat;
}
},
minimum: 'Doit être supérieur à {{minimumValue}}',
exclusiveMinimum: 'Doit avoir minimum {{exclusiveMinimumValue}} charactères',
maximum: 'Doit être inférieur à {{maximumValue}}',
exclusiveMaximum: 'Doit avoir maximum {{exclusiveMaximumValue}} charactères',
multipleOf: function (error) {
if ((1 / error.multipleOfValue) % 10 === 0) {
const decimals = Math.log10(1 / error.multipleOfValue);
return `Doit comporter ${decimals} ou moins de decimales.`;
}
else {
return `Doit être un multiple de ${error.multipleOfValue}.`;
}
},
minProperties: 'Doit comporter au minimum {{minimumProperties}} éléments',
maxProperties: 'Doit comporter au maximum {{maximumProperties}} éléments',
minItems: 'Doit comporter au minimum {{minimumItems}} éléments',
maxItems: 'Doit comporter au maximum {{minimumItems}} éléments',
uniqueItems: 'Tous les éléments doivent être uniques',
// Note: No default error messages for 'type', 'const', 'enum', or 'dependencies'
};
const itValidationMessages = {
required: 'Il campo è obbligatorio',
minLength: 'Deve inserire almeno {{minimumLength}} caratteri (lunghezza corrente: {{currentLength}})',
maxLength: 'Il numero massimo di caratteri consentito è {{maximumLength}} (lunghezza corrente: {{currentLength}})',
pattern: 'Devi rispettare il pattern : {{requiredPattern}}',
format: function (error) {
switch (error.requiredFormat) {
case 'date':
return 'Deve essere una data, come "31-12-2000"';
case 'time':
return 'Deve essere un orario, come "16:20" o "03:14:15.9265"';
case 'date-time':
return 'Deve essere data-orario, come "14-03-2000T01:59" or "14-03-2000T01:59:26.535Z"';
case 'email':
return 'Deve essere un indirzzo email, come "name@example.com"';
case 'hostname':
return 'Deve essere un hostname, come "example.com"';
case 'ipv4':
return 'Deve essere un indirizzo IPv4, come "127.0.0.1"';
case 'ipv6':
return 'Deve essere un indirizzo IPv6, come "1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0"';
// TODO: add examples for 'uri', 'uri-reference', and 'uri-template'
// case 'uri': case 'uri-reference': case 'uri-template':
case 'url':
return 'Deve essere un url, come "http://www.example.com/page.html"';
case 'uuid':
return 'Deve essere un uuid, come "12345678-9ABC-DEF0-1234-56789ABCDEF0"';
case 'color':
return 'Deve essere un colore, come "#FFFFFF"';
case 'json-pointer':
return 'Deve essere un JSON Pointer, come "/pointer/to/something"';
case 'relative-json-pointer':
return 'Deve essere un JSON Pointer relativo, come "2/pointer/to/something"';
case 'regex':
return 'Deve essere una regular expression, come "(1-)?\\d{3}-\\d{3}-\\d{4}"';
case 'duration':
return "Deve essere una durata valida nel formato ISO 8601 (es. 'PT1H30M')";
default:
return 'Deve essere formattato correttamente ' + error.requiredFormat;
}
},
minimum: 'Deve essere {{minimumValue}} o più',
exclusiveMinimum: 'Deve essere più di {{exclusiveMinimumValue}}',
maximum: 'Deve essere {{maximumValue}} o meno',
exclusiveMaximum: 'Deve essere minore di {{exclusiveMaximumValue}}',
multipleOf: function (error) {
if ((1 / error.multipleOfValue) % 10 === 0) {
const decimals = Math.log10(1 / error.multipleOfValue);
return `Deve avere ${decimals} o meno decimali.`;
}
else {
return `Deve essere multiplo di ${error.multipleOfValue}.`;
}
},
minProperties: 'Deve avere {{minimumProperties}} o più elementi (elementi correnti: {{currentProperties}})',
maxProperties: 'Deve avere {{maximumProperties}} o meno elementi (elementi correnti: {{currentProperties}})',
minItems: 'Deve avere {{minimumItems}} o più elementi (elementi correnti: {{currentItems}})',
maxItems: 'Deve avere {{maximumItems}} o meno elementi (elementi correnti: {{currentItems}})',
uniqueItems: 'Tutti gli elementi devono essere unici',
// Note: No default error messages for 'type', 'const', 'enum', or 'dependencies'
};
const ptValidationMessages = {
required: 'Este campo é obrigatório.',
minLength: 'É preciso no mínimo {{minimumLength}} caracteres ou mais (tamanho atual: {{currentLength}})',
maxLength: 'É preciso no máximo {{maximumLength}} caracteres ou menos (tamanho atual: {{currentLength}})',
pattern: 'Tem que ajustar ao formato: {{requiredPattern}}',
format: function (error) {
switch (error.requiredFormat) {
case 'date':
return 'Tem que ser uma data, por exemplo "2000-12-31"';
case 'time':
return 'Tem que ser horário, por exemplo "16:20" ou "03:14:15.9265"';
case 'date-time':
return 'Tem que ser data e hora, por exemplo "2000-03-14T01:59" ou "2000-03-14T01:59:26.535Z"';
case 'email':
return 'Tem que ser um email, por exemplo "fulano@exemplo.com.br"';
case 'hostname':
return 'Tem que ser uma nome de domínio, por exemplo "exemplo.com.br"';
case 'ipv4':
return 'Tem que ser um endereço IPv4, por exemplo "127.0.0.1"';
case 'ipv6':
return 'Tem que ser um endereço IPv6, por exemplo "1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0"';
// TODO: add examples for 'uri', 'uri-reference', and 'uri-template'
// case 'uri': case 'uri-reference': case 'uri-template':
case 'url':
return 'Tem que ser uma URL, por exemplo "http://www.exemplo.com.br/pagina.html"';
case 'uuid':
return 'Tem que ser um uuid, por exemplo "12345678-9ABC-DEF0-1234-56789ABCDEF0"';
case 'color':
return 'Tem que ser uma cor, por exemplo "#FFFFFF"';
case 'json-pointer':
return 'Tem que ser um JSON Pointer, por exemplo "/referencia/para/algo"';
case 'relative-json-pointer':
return 'Tem que ser um JSON Pointer relativo, por exemplo "2/referencia/para/algo"';
case 'regex':
return 'Tem que ser uma expressão regular, por exemplo "(1-)?\\d{3}-\\d{3}-\\d{4}"';
case 'duration':
return "Deve ser uma duração válida no formato ISO 8601 (ex.: 'PT1H30M')";
default:
return 'Tem que ser no formato: ' + error.requiredFormat;
}
},
minimum: 'Tem que ser {{minimumValue}} ou mais',
exclusiveMinimum: 'Tem que ser mais que {{exclusiveMinimumValue}}',
maximum: 'Tem que ser {{maximumValue}} ou menos',
exclusiveMaximum: 'Tem que ser menor que {{exclusiveMaximumValue}}',
multipleOf: function (error) {
if ((1 / error.multipleOfValue) % 10 === 0) {
const decimals = Math.log10(1 / error.multipleOfValue);
return `Tem que ter ${decimals} ou menos casas decimais.`;
}
else {
return `Tem que ser um múltiplo de ${error.multipleOfValue}.`;
}
},
minProperties: 'Deve ter {{minimumProperties}} ou mais itens (itens até o momento: {{currentProperties}})',
maxProperties: 'Deve ter {{maximumProperties}} ou menos intens (itens até o momento: {{currentProperties}})',
minItems: 'Deve ter {{minimumItems}} ou mais itens (itens até o momento: {{currentItems}})',
maxItems: 'Deve ter {{maximumItems}} ou menos itens (itens até o momento: {{currentItems}})',
uniqueItems: 'Todos os itens devem ser únicos',
// Note: No default error messages for 'type', 'const', 'enum', or 'dependencies'
};
const zhValidationMessages = {
required: '必填字段.',
minLength: '字符长度必须大于或者等于 {{minimumLength}} (当前长度: {{currentLength}})',
maxLength: '字符长度必须小于或者等于 {{maximumLength}} (当前长度: {{currentLength}})',
pattern: '必须匹配正则表达式: {{requiredPattern}}',
format: function (error) {
switch (error.requiredFormat) {
case 'date':
return '必须为日期格式, 比如 "2000-12-31"';
case 'time':
return '必须为时间格式, 比如 "16:20" 或者 "03:14:15.9265"';
case 'date-time':
return '必须为日期时间格式, 比如 "2000-03-14T01:59" 或者 "2000-03-14T01:59:26.535Z"';
case 'email':
return '必须为邮箱地址, 比如 "name@example.com"';
case 'hostname':
return '必须为主机名, 比如 "example.com"';
case 'ipv4':
return '必须为 IPv4 地址, 比如 "127.0.0.1"';
case 'ipv6':
return '必须为 IPv6 地址, 比如 "1234:5678:9ABC:DEF0:1234:5678:9ABC:DEF0"';
// TODO: add examples for 'uri', 'uri-reference', and 'uri-template'
// case 'uri': case 'uri-reference': case 'uri-template':
case 'url':
return '必须为 url, 比如 "http://www.example.com/page.html"';
case 'uuid':
return '必须为 uuid, 比如 "12345678-9ABC-DEF0-1234-56789ABCDEF0"';
case 'color':
return '必须为颜色值, 比如 "#FFFFFF"';
case 'json-pointer':
return '必须为 JSON Pointer, 比如 "/pointer/to/something"';
case 'relative-json-pointer':
return '必须为相对的 JSON Pointer, 比如 "2/pointer/to/something"';
case 'regex':
return '必须为正则表达式, 比如 "(1-)?\\d{3}-\\d{3}-\\d{4}"';
case 'duration':
return "必须是有效的 ISO 8601 持续时间(例如:'PT1H30M')";
default:
return '必须为格式正确的 ' + error.requiredFormat;
}
},
minimum: '必须大于或者等于最小值: {{minimumValue}}',
exclusiveMinimum: '必须大于最小值: {{exclusiveMinimumValue}}',
maximum: '必须小于或者等于最大值: {{maximumValue}}',
exclusiveMaximum: '必须小于最大值: {{exclusiveMaximumValue}}',
multipleOf: function (error) {
if ((1 / error.multipleOfValue) % 10 === 0) {
const decimals = Math.log10(1 / error.multipleOfValue);
return `必须有 ${decimals} 位或更少的小数位`;
}
else {
return `必须为 ${error.multipleOfValue} 的倍数`;
}
},
minProperties: '项目数必须大于或者等于 {{minimumProperties}} (当前项目数: {{currentProperties}})',
maxProperties: '项目数必须小于或者等于 {{maximumProperties}} (当前项目数: {{currentProperties}})',
minItems: '项目数必须大于或者等于 {{minimumItems}} (当前项目数: {{currentItems}})',
maxItems: '项目数必须小于或者等于 {{maximumItems}} (当前项目数: {{currentItems}})',
uniqueItems: '所有项目必须是唯一的',
// Note: No default error messages for 'type', 'const', 'enum', or 'dependencies'
};
/**
* '_executeValidators' utility function
*
* Validates a control against an array of validators, and returns
* an array of the same length containing a combination of error messages
* (from invalid validators) and null values (from valid validators)
*
* // { AbstractControl } control - control to validate
* // { IValidatorFn[] } validators - array of validators
* // { boolean } invert - invert?
* // { PlainObject[] } - array of nulls and error message
*/
function _executeValidators(control, validators, invert = false) {
return validators.map(validator => validator(control, invert));
}
/**
* '_executeAsyncValidators' utility function
*
* Validates a control against an array of async validators, and returns
* an array of observabe results of the same length containing a combination of
* error messages (from invalid validators) and null values (from valid ones)
*
* // { AbstractControl } control - control to validate
* // { AsyncIValidatorFn[] } validators - array of async validators
* // { boolean } invert - invert?
* // - array of observable nulls and error message
*/
function _executeAsyncValidators(control, validators, invert = false) {
return validators.map(validator => validator(control, invert));
}
/**
* '_mergeObjects' utility function
*
* Recursively Merges one or more objects into a single object with combined keys.
* Automatically detects and ignores null and undefined inputs.
* Also detects duplicated boolean 'not' keys and XORs their values.
*
* // { PlainObject[] } objects - one or more objects to merge
* // { PlainObject } - merged object
*/
function _mergeObjects(...objects) {
const mergedObject = {};
for (const currentObject of objects) {
if (isObject(currentObject)) {
for (const key of Object.keys(currentObject)) {
const currentValue = currentObject[key];
const mergedValue = mergedObject[key];
mergedObject[key] = !isDefined(mergedValue) ? currentValue :
key === 'not' && isBoolean(mergedValue, 'strict') &&
isBoolean(currentValue, 'strict') ? xor(mergedValue, currentValue) :
getType(mergedValue) === 'object' && getType(currentValue) === 'object' ?
_mergeObjects(mergedValue, currentValue) :
currentValue;
}
}
}
return mergedObject;
}
/**
* '_mergeErrors' utility function
*
* Merges an array of objects.
* Used for combining the validator errors returned from 'executeValidators'
*
* // { PlainObject[] } arrayOfErrors - array of objects
* // { PlainObject } - merged object, or null if no usable input objectcs
*/
function _mergeErrors(arrayOfErrors) {
const mergedErrors = _mergeObjects(...arrayOfErrors);
return isEmpty(mergedErrors) ? null : mergedErrors;
}
/**
* 'isDefined' utility function
*
* Checks if a variable contains a value of any type.
* Returns true even for otherwise 'falsey' values of 0, '', and false.
*
* // value - the value to check
* // { boolean } - false if undefined or null, otherwise true
*/
function isDefined(value) {
return value !== undefined && value !== null;
}
/**
* 'hasValue' utility function
*
* Checks if a variable contains a value.
* Returs false for null, undefined, or a zero-length strng, '',
* otherwise returns true.
* (Stricter than 'isDefined' because it also returns false for '',
* though it stil returns true for otherwise 'falsey' values 0 and false.)
*
* // value - the value to check
* // { boolean } - false if undefined, null, or '', otherwise true
*/
function hasValue(value) {
return value !== undefined && value !== null && value !== '';
}
/**
* 'isEmpty' utility function
*
* Similar to !hasValue, but also returns true for empty arrays and objects.
*
* // value - the value to check
* // { boolean } - false if undefined, null, or '', otherwise true
*/
function isEmpty(value) {
if (isArray(value)) {
return !value.length;
}
if (isObject(value)) {
return !Object.keys(value).length;
}
return value === undefined || value === null || value === '';
}
/**
* 'isString' utility function
*
* Checks if a value is a string.
*
* // value - the value to check
* // { boolean } - true if string, false if not
*/
function isString(value) {
return typeof value === 'string';
}
/**
* 'isNumber' utility function
*
* Checks if a value is a regular number, numeric string, or JavaScript Date.
*
* // value - the value to check
* // { any = false } strict - if truthy, also checks JavaScript tyoe
* // { boolean } - true if number, false if not
*/
function isNumber(value, strict = false) {
if (strict && typeof value !== 'number') {
return false;
}
return !isNaN(value) && value !== value / 0;
}
/**
* 'isInteger' utility function
*
* Checks if a value is an integer.
*
* // value - the value to check
* // { any = false } strict - if truthy, also checks JavaScript tyoe
* // {boolean } - true if number, false if not
*/
function isInteger(value, strict = false) {
if (strict && typeof value !== 'number') {
return false;
}
return !isNaN(value) && value !== value / 0 && value % 1 === 0;
}
/**
* 'isBoolean' utility function
*
* Checks if a value is a boolean.
*
* // value - the value to check
* // { any = null } option - if 'strict', also checks JavaScript type
* if TRUE or FALSE, checks only for that value
* // { boolean } - true if boolean, false if not
*/
function isBoolean(value, option = null) {
if (option === 'strict') {
return value === true || value === false;
}
if (option === true) {
return value === true || value === 1 || value === 'true' || value === '1';
}
if (option === false) {
return value === false || value === 0 || value === 'false' || value === '0';
}
return value === true || value === 1 || value === 'true' || value === '1' ||
value === false || value === 0 || value === 'false' || value === '0';
}
function isFunction(item) {
return typeof item === 'function';
}
function isObject(item) {
return item !== null && typeof item === 'object';
}
function isArray(item) {
return Array.isArray(item);
}
function isDate(item) {
return !!item && Object.prototype.toString.call(item) === '[object Date]';
}
function isMap(item) {
return !!item && Object.prototype.toString.call(item) === '[object Map]';
}
function isSet(item) {
return !!item && Object.prototype.toString.call(item) === '[object Set]';
}
function isSymbol(item) {
return typeof item === 'symbol';
}
/**
* 'getType' function
*
* Detects the JSON Schema Type of a value.
* By default, detects numbers and integers even if formatted as strings.
* (So all integers are also numbers, and any number may also be a string.)
* However, it only detects true boolean values (to detect boolean values
* in non-boolean formats, use isBoolean() instead).
*
* If passed a second optional parameter of 'strict', it will only detect
* numbers and integers if they are formatted as JavaScript numbers.
*
* Examples:
* getType('10.5') = 'number'
* getType(10.5) = 'number'
* getType('10') = 'integer'
* getType(10) = 'integer'
* getType('true') = 'string'
* getType(true) = 'boolean'
* getType(null) = 'null'
* getType({ }) = 'object'
* getType([]) = 'array'
*
* getType('10.5', 'strict') = 'string'
* getType(10.5, 'strict') = 'number'
* getType('10', 'strict') = 'string'
* getType(10, 'strict') = 'integer'
* getType('true', 'strict') = 'string'
* getType(true, 'strict') = 'boolean'
*
* // value - value to check
* // { any = false } strict - if truthy, also checks JavaScript tyoe
* // { SchemaType }
*/
function getType(value, strict = false) {
if (!isDefined(value)) {
return 'null';
}
if (isArray(value)) {
return 'array';
}
if (isObject(value)) {
return 'object';
}
if (isBoolean(value, 'strict')) {
return 'boolean';
}
if (isInteger(value, strict)) {
return 'integer';
}
if (isNumber(value, strict)) {
return 'number';
}
if (isString(value) || (!strict && isDate(value))) {
return 'string';
}
return null;
}
/**
* 'isType' function
*
* Checks wether an input (probably string) value contains data of
* a specified JSON Schema type
*
* // { PrimitiveValue } value - value to check
* // { SchemaPrimitiveType } type - type to check
* // { boolean }
*/
function isType(value, type) {
switch (type) {
case 'string':
return isString(value) || isDate(value);
case 'number':
return isNumber(value);
case 'integer':
return isInteger(value);
case 'boolean':
return isBoolean(value);
case 'null':
return !hasValue(value);
default:
console.error(`isType error: "${type}" is not a recognized type.`);
return null;
}
}
/**
* 'isPrimitive' function
*
* Checks wether an input value is a JavaScript primitive type:
* string, number, boolean, or null.
*
* // value - value to check
* // { boolean }
*/
function isPrimitive(value) {
return (isString(value) || isNumber(value) ||
isBoolean(value, 'strict') || value === null);
}
/**
*
* @param date
* @returns {string}
* exmaple:
* toDateString('2018-01-01') = '2018-01-01'
* toDateString('2018-01-30T00:00:00.000Z') = '2018-01-30'
*/
const toIsoString = (date) => {
const day = date.getDate();
const month = date.getMonth() + 1;
const year = date.getFullYear();
return `${year}-${month < 10 ? '0' + month : month}-${day < 10 ? '0' + day : day}`;
};
/**
* 'toJavaScriptType' function
*
* Converts an input (probably string) value to a JavaScript primitive type -
* 'string', 'number', 'boolean', or 'null' - before storing in a JSON object.
*
* Does not coerce values (other than null), and only converts the types
* of values that would otherwise be valid.
*
* If the optional third parameter 'strictIntegers' is TRUE, and the
* JSON Schema type 'integer' is specified, it also verifies the input value
* is an integer and, if it is, returns it as a JaveScript number.
* If 'strictIntegers' is FALSE (or not set) the type 'integer' is treated
* exactly the same as 'number', and allows decimals.
*
* Valid Examples:
* toJavaScriptType('10', 'number' ) = 10 // '10' is a number
* toJavaScriptType('10', 'integer') = 10 // '10' is also an integer
* toJavaScriptType( 10, 'integer') = 10 // 10 is still an integer
* toJavaScriptType( 10, 'string' ) = '10' // 10 can be made into a string
* toJavaScriptType('10.5', 'number' ) = 10.5 // '10.5' is a number
*
* Invalid Examples:
* toJavaScriptType('10.5', 'integer') = null // '10.5' is not an integer
* toJavaScriptType( 10.5, 'integer') = null // 10.5 is still not an integer
*
* // { PrimitiveValue } value - value to convert
* // { SchemaPrimitiveType | SchemaPrimitiveType[] } types - types to convert to
* // { boolean = false } strictIntegers - if FALSE, treat integers as numbers
* // { PrimitiveValue }
*/
function toJavaScriptType(value, types, strictIntegers = true) {
if (!isDefined(value)) {
return null;
}
if (isString(types)) {
types = [types];
}
if (strictIntegers && inArray('integer', types)) {
if (isInteger(value, 'strict')) {
return value;
}
if (isInteger(value)) {
return parseInt(value, 10);
}
}
if (inArray('number', types) || (!strictIntegers && inArray('integer', types))) {
if (isNumber(value, 'strict')) {
return value;
}
if (isNumber(value)) {
return parseFloat(value);
}
}
if (inArray('string', types)) {
if (isString(value)) {
return value;
}
// If value is a date, and types includes 'string',
// convert the date to a string
if (isDate(value)) {
return toIsoString(value);
}
if (isNumber(value)) {
return value.toString();
}
}
// If value is a date, and types includes 'integer' or 'number',
// but not 'string', convert the date to a number
if (isDate(value) && (inArray('integer', types) || inArray('number', types))) {
return value.getTime();
}
if (inArray('boolean', types)) {
if (isBoolean(value, true)) {
return true;
}
if (isBoolean(value, false)) {
return false;
}
}
return null;
}
/**
* 'toSchemaType' function
*
* Converts an input (probably string) value to the "best" JavaScript
* equivalent available from an allowed list of JSON Schema types, which may
* contain 'string', 'number', 'integer', 'boolean', and/or 'null'.
* If necssary, it does progressively agressive type coersion.
* It will not return null unless null is in the list of allowed types.
*
* Number conversion examples:
* toSchemaType('10', ['number','integer','string']) = 10 // integer
* toSchemaType('10', ['number','string']) = 10 // number
* toSchemaType('10', ['string']) = '10' // string
* toSchemaType('10.5', ['number','integer','string']) = 10.5 // number
* toSchemaType('10.5', ['integer','string']) = '10.5' // string
* toSchemaType('10.5', ['integer']) = 10 // integer
* toSchemaType(10.5, ['null','boolean','string']) = '10.5' // string
* toSchemaType(10.5, ['null','boolean']) = true // boolean
*
* String conversion examples:
* toSchemaType('1.5x', ['boolean','number','integer','string']) = '1.5x' // string
* toSchemaType('1.5x', ['boolean','number','integer']) = '1.5' // number
* toSchemaType('1.5x', ['boolean','integer']) = '1' // integer
* toSchemaType('1.5x', ['boolean']) = true // boolean
* toSchemaType('xyz', ['number','integer','boolean','null']) = true // boolean
* toSchemaType('xyz', ['number','integer','null']) = null // null
* toSchemaType('xyz', ['number','integer']) = 0 // number
*
* Boolean conversion examples:
* toSchemaType('1', ['integer','number','string','boolean']) = 1 // integer
* toSchemaType('1', ['number','string','boolean']) = 1 // number
* toSchemaType('1', ['string','boolean']) = '1' // string
* toSchemaType('1', ['boolean']) = true // boolean
* toSchemaType('true', ['number','string','boolean']) = 'true' // string
* toSchemaType('true', ['boolean']) = true // boolean
* toSchemaType('true', ['number']) = 0 // number
* toSchemaType(true, ['number','string','boolean']) = true // boolean
* toSchemaType(true, ['number','string']) = 'true' // string
* toSchemaType(true, ['number']) = 1 // number
*
* // { PrimitiveValue } value - value to convert
* // { SchemaPrimitiveType | SchemaPrimitiveType[] } types - allowed types to convert to
* // { PrimitiveValue }
*/
function toSchemaType(value, types) {
if (!isArray(types)) {
types = [types];
}
if (types.includes('null') && !hasValue(value)) {
return null;
}
if (types.includes('boolean') && !isBoolean(value, 'strict')) {
return value;
}
if (types.includes('integer')) {
const testValue = toJavaScriptType(value, 'integer');
if (testValue !== null) {
return +testValue;
}
}
if (types.includes('number')) {
const testValue = toJavaScriptType(value, 'number');
if (testValue !== null) {
return +testValue;
}
}
if ((isString(value) || isNumber(value, 'strict')) &&
types.includes('string')) { // Convert number to string
return toJavaScriptType(value, 'string');
}
if (types.includes('boolean') && isBoolean(value)) {
return toJavaScriptType(value, 'boolean');
}
if (types.includes('string')) { // Convert null & boolean to string
if (value === null) {
return '';
}
const testValue = toJavaScriptType(value, 'string');
if (testValue !== null) {
return testValue;
}
}
if ((types.includes('number') ||
types.includes('integer'))) {
if (value === true) {
return 1;
} // Convert boolean & null to number
if (value === false || value === null || value === '') {
return 0;
}
}
if (types.includes('number')) { // Convert mixed string to number
const testValue = parseFloat(value);
if (!!testValue) {
return testValue;
}
}
if (types.includes('integer')) { // Convert string or number to integer
const testValue = parseInt(value, 10);
if (!!testValue) {
return testValue;
}
}
if (types.includes('boolean')) { // Convert anything to boolean
return !!value;
}
if ((types.includes('number') ||
types.includes('integer')) && !types.includes('null')) {
return 0; // If null not allowed, return 0 for non-convertable values
}
}
/**
* 'isPromise' function
*
* // object
* // { boolean }
*/
function isPromise(object) {
return !!object && typeof object.then === 'function';
}
/**
* 'isObservable' function
*
* // object
* // { boolean }
*/
function isObservable(object) {
return !!object && typeof object.subscribe === 'function';
}
/**
* '_toPromise' function
*
* // { object } object
* // { Promise<any> }
*/
function _toPromise(object) {
return isPromise(object) ? object : object.toPromise();
}
/**
* 'toObservable' function
*
* // { object } object
* // { Observable<any> }
*/
function toObservable(object) {
const observable = isPromise(object) ? from(object) : object;
if (isObservable(observable)) {
return observable;
}
console.error('toObservable error: Expected validator to return Promise or Observable.');
return new Observable();
}
/**
* 'inArray' function
*
* Searches an array for an item, or one of a list of items, and returns true
* as soon as a match is found, or false if no match.
*
* If the optional third parameter allIn is set to TRUE, and the item to find
* is an array, then the function returns true only if all elements from item
* are found in the array list, and false if any element is not found. If the
* item to find is not an array, setting allIn to TRUE has no effect.
*
* // { any|any[] } item - the item to search for
* // array - the array to search
* // { boolean = false } allIn - if TRUE, all items must be in array
* // { boolean } - true if item(s) in array, false otherwise
*/
function inArray(item, array, allIn = false) {
if (!isDefined(item) || !isArray(array)) {
return false;
}
return isArray(item) ?
item[allIn ? 'every' : 'some'](subItem => array.includes(subItem)) :
array.includes(item);
}
/**
* 'xor' utility function - exclusive or
*
* Returns true if exactly one of two values is truthy.
*
* // value1 - first value to check
* // value2 - second value to check
* // { boolean } - true if exactly one input value is truthy, false if not
*/
function xor(value1, value2) {
return (!!value1 && !value2) || (!value1 && !!value2);
}
/**
* Utility function library:
*
* addClasses, copy, forEach, forEachCopy, hasOwn, mergeFilteredObject,
* uniqueItems, commonItems, fixTitle, toTitleCase
*/
/**
* 'addClasses' function
*
* Merges two space-delimited lists of CSS classes and removes duplicates.
*
* // {string | string[] | Set<string>} oldClasses
* // {string | string[] | Set<string>} newClasses
* // {string | string[] | Set<string>} - Combined classes
*/
function addClasses(oldClasses, newClasses) {
const badType = i => !isSet(i) && !isArray(i) && !isString(i);
if (badType(newClasses)) {
return oldClasses;
}
if (badType(oldClasses)) {
oldClasses = '';
}
const toSet = i => isSet(i) ? i : isArray(i) ? new Set(i) : new Set(i.split(' '));
const combinedSet = toSet(oldClasses);
const newSet = toSet(newClasses);
newSet.forEach(c => combinedSet.add(c));
if (isSet(oldClasses)) {
return combinedSet;
}
if (isArray(oldClasses)) {
return Array.from(combinedSet);
}
return Array.from(combinedSet).join(' ');
}
/**
* 'copy' function
*
* Makes a shallow copy of a JavaScript object, array, Map, or Set.
* If passed a JavaScript primitive value (string, number, boolean, or null),
* it returns the value.
*
* // {Object|Array|string|number|boolean|null} object - The object to copy
* // {boolean = false} errors - Show errors?
* // {Object|Array|string|number|boolean|null} - The copied object
*/
function copy(object, errors = false) {
if (typeof object !== 'object' || object === null) {
return object;
}
if (isMap(object)) {
return new Map(object);
}
if (isSet(object)) {
return new Set(object);
}
if (isArray(object)) {
return [...object];
}
if (isObject(object)) {
return { ...object };
}
if (errors) {
console.error('copy error: Object to copy must be a JavaScript object or value.');
}
return object;
}
/**
* 'forEach' function
*
* Iterates over all items in the first level of an object or array
* and calls an iterator funciton on each item.
*
* The iterator function is called with four values:
* 1. The current item's value
* 2. The current item's key
* 3. The parent object, which contains the current item
* 4. The root object
*
* Setting the optional third parameter to 'top-down' or 'bottom-up' will cause
* it to also recursively iterate over items in sub-objects or sub-arrays in the
* specified direction.
*
* // {Object|Array} object - The object or array to iterate over
* // {function} fn - the iterator funciton to call on each item
* // {boolean = false} errors - Show errors?
* // {void}
*/
function forEach(object, fn, recurse = false, rootObject = object, errors = false) {
if (isEmpty(object)) {
return;
}
if ((isObject(object) || isArray(object)) && typeof fn === 'function') {
for (const key of Object.keys(object)) {
const value = object[key];
if (recurse === 'bottom-up' && (isObject(value) || isArray(value))) {
forEach(value, fn, recurse, rootObject);
}
fn(value, key, object, rootObject);
if (recurse === 'top-down' && (isObject(value) || isArray(value))) {
forEach(value, fn, recurse, rootObject);
}
}
}
if (errors) {
if (typeof fn !== 'function') {
console.error('forEach error: Iterator must be a function.');
console.error('function', fn);
}
if (!isObject(object) && !isArray(object)) {
console.error('forEach error: Input object must be an object or array.');
console.error('object', object);
}
}
}
/**
* 'forEachCopy' function
*
* Iterates over all items in the first level of an object or array
* and calls an iterator function on each item. Returns a new object or array
* with the same keys or indexes as the original, and values set to the results
* of the iterator function.
*
* Does NOT recursively iterate over items in sub-objects or sub-arrays.
*
* // {Object | Array} object - The object or array to iterate over
* // {function} fn - The iterator funciton to call on each item
* // {boolean = false} errors - Show errors?
* // {Object | Array} - The resulting object or array
*/
function forEachCopy(object, fn, errors = false) {
if (!hasValue(object)) {
return;
}
if ((isObject(object) || isArray(object)) && typeof object !== 'function') {
const newObject = isArray(object) ? [] : {};
for (const key of Object.keys(object)) {
newObject[key] = fn(object[key], key, object);
}
return newObject;
}
if (errors) {
if (typeof fn !== 'function') {
console.error('forEachCopy error: Iterator must be a function.');
console.error('function', fn);
}
if (!isObject(object) && !isArray(object)) {
console.error('forEachCopy error: Input object must be an object or array.');
console.error('object', object);
}
}
}
/**
* 'hasOwn' utility function
*
* Checks whether an object or array has a particular property.
*
* // {any} object - the object to check
* // {string} property - the property to look for
* // {boolean} - true if object has property, false if not
*/
function hasOwn(object, property) {
if (!