@streamline-pulse/formkrafter-core 0.25.0 → 0.27.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.d.ts CHANGED
@@ -8,6 +8,8 @@ export { getBrickData, wrapBrickData } from "./utils/brick-data.js";
8
8
  export { SelectOption, normalizeOptions } from "./utils/options.js";
9
9
  export { RecapItem, collectRecapItems } from "./utils/recap.js";
10
10
  export { FieldDescriptor, CollectFieldsOptions, collectFields } from "./utils/fields.js";
11
+ export { IdentityFormat, IDENTITY_FORMATS, checkIdentity, checkFrNir, checkBjNpi, checkDigits, checkLuhn, checkFrSiren, checkFrSiret, checkBjIfu, checkIban, formatIban } from "./utils/identity.js";
12
+ export { MrzResult, parseMrz, mrzLines, mrzCheckDigit } from "./utils/mrz.js";
11
13
  export { FieldAnnotation, FieldAnnotations, flaggedKeys } from "./review.js";
12
14
  export { SpecIssue, SpecIssueCode, lintSpec } from "./utils/lint-spec.js";
13
15
  export { ensureBrickUids, stripBrickUids, stripUidsFromPatches } from "./ops/uids.js";
package/dist/index.js CHANGED
@@ -6920,7 +6920,10 @@ var DEFAULT_MESSAGES = {
6920
6920
  email: "Invalid email address",
6921
6921
  url: "Invalid URL",
6922
6922
  type: "Invalid value",
6923
- custom: "Invalid value"
6923
+ custom: "Invalid value",
6924
+ identity: "Invalid identifier for this format",
6925
+ iban: "Invalid IBAN",
6926
+ mrz: "Machine-readable zone unreadable or inconsistent"
6924
6927
  },
6925
6928
  fr: {
6926
6929
  required: "Ce champ est obligatoire",
@@ -6934,7 +6937,10 @@ var DEFAULT_MESSAGES = {
6934
6937
  email: "Adresse email invalide",
6935
6938
  url: "URL invalide",
6936
6939
  type: "Valeur invalide",
6937
- custom: "Valeur invalide"
6940
+ custom: "Valeur invalide",
6941
+ identity: "Identifiant invalide pour ce format",
6942
+ iban: "IBAN invalide",
6943
+ mrz: "Zone de lecture illisible ou incohérente"
6938
6944
  }
6939
6945
  };
6940
6946
  function registerValidationMessages(locale, messages) {
@@ -7031,6 +7037,18 @@ function buildValidationSchema(brickSpecs, locale) {
7031
7037
  messages.format = message;
7032
7038
  }
7033
7039
  }
7040
+ if (brick.id === "iban") {
7041
+ property.format = "identity-iban";
7042
+ messages.format = defaultValidationMessage("iban", locale);
7043
+ }
7044
+ if ((brick.id === "national-id" || brick.id === "tax-id") && typeof brick.configs?.idFormat === "string" && brick.configs.idFormat) {
7045
+ property.format = `identity-${brick.configs.idFormat}`;
7046
+ messages.format = defaultValidationMessage("identity", locale);
7047
+ }
7048
+ if (brick.id === "mrz") {
7049
+ property.mrzValid = true;
7050
+ messages.mrzValid = defaultValidationMessage("mrz", locale);
7051
+ }
7034
7052
  property.errorMessage = messages;
7035
7053
  properties[key] = property;
7036
7054
  if (isRequired) {
@@ -7190,6 +7208,7 @@ var en = {
7190
7208
  "category.Inputs": "Inputs",
7191
7209
  "category.Layout": "Layout",
7192
7210
  "category.Data": "Data",
7211
+ "category.Geo": "Map",
7193
7212
  "brick.text.name": "Text",
7194
7213
  "brick.email.name": "Email",
7195
7214
  "brick.password.name": "Password",
@@ -7234,7 +7253,52 @@ var en = {
7234
7253
  "review.comment": "Comment for the person who corrects it",
7235
7254
  "review.flagged": "To correct",
7236
7255
  "review.previous": "Flagged in the previous review",
7237
- "variables.empty": "No matching variable"
7256
+ "variables.empty": "No matching variable",
7257
+ "geo.latitude": "Latitude",
7258
+ "geo.longitude": "Longitude",
7259
+ "geo.points": "points",
7260
+ "geo.clear": "Clear",
7261
+ "geo.hintPoint": "Click the map to place the point, drag it to move it.",
7262
+ "geo.hintArea": "Click the map to add points, drag one to move it, right-click to remove it.",
7263
+ "geo.unavailable": "Map unavailable",
7264
+ "geo.nativeReadOnly": "Drawn on the web version",
7265
+ "panel.center": "Initial center (lat, lng)",
7266
+ "panel.zoom": "Initial zoom",
7267
+ "panel.height": "Height (px)",
7268
+ "panel.tileUrl": "Tile URL",
7269
+ "panel.shape": "Shape (polygon or line)",
7270
+ "panel.searchUrl": "Geocoding URL ({q} = query)",
7271
+ "panel.countryCodes": "Country codes (bj, tg)",
7272
+ "address.search": "Search an address",
7273
+ "address.searching": "Searching…",
7274
+ "address.located": "Address located",
7275
+ "address.notLocated": "Address not located: pick a match to place it",
7276
+ "address.noResult": "No matching address",
7277
+ "address.unavailable": "Address search unavailable",
7278
+ "layer.pick": "Choose a zone…",
7279
+ "layer.pickMany": "Click the zones on the map",
7280
+ "layer.hint": "Click a zone on the map to select it",
7281
+ "layer.unavailable": "Reference layer unavailable",
7282
+ "panel.layerUrl": "Reference layer URL (GeoJSON)",
7283
+ "panel.codeProperty": "Code property",
7284
+ "panel.nameProperty": "Name property",
7285
+ "panel.idFormat": "Identifier format (fr-nir, bj-npi, digits-10…)",
7286
+ "mrz.placeholder": "Paste or type the two or three lines of the machine-readable zone",
7287
+ "mrz.unreadable": "Zone not recognised: 2 lines of 44 (passport) or 3 lines of 30 (identity card)",
7288
+ "mrz.invalid": "Check digits do not match for:",
7289
+ "mrz.documentNumber": "Document number",
7290
+ "mrz.nationality": "Nationality",
7291
+ "mrz.surname": "Surname",
7292
+ "mrz.givenNames": "Given names",
7293
+ "mrz.birthDate": "Date of birth",
7294
+ "mrz.sex": "Sex",
7295
+ "mrz.expiryDate": "Expiry date",
7296
+ "category.Identity": "Identity",
7297
+ "category.Finance": "Finance",
7298
+ "money.amount": "Amount",
7299
+ "money.currency": "Currency",
7300
+ "panel.currencies": "Currencies offered (XOF, EUR, USD)",
7301
+ "panel.currency": "Default currency"
7238
7302
  };
7239
7303
  var frFkTranslations = {
7240
7304
  "palette.search": "Rechercher une brique…",
@@ -7333,6 +7397,7 @@ var frFkTranslations = {
7333
7397
  "category.Inputs": "Champs",
7334
7398
  "category.Layout": "Mise en page",
7335
7399
  "category.Data": "Données",
7400
+ "category.Geo": "Carte",
7336
7401
  "brick.text.name": "Texte",
7337
7402
  "brick.email.name": "Email",
7338
7403
  "brick.password.name": "Mot de passe",
@@ -7377,7 +7442,52 @@ var frFkTranslations = {
7377
7442
  "review.comment": "Commentaire pour la personne qui corrige",
7378
7443
  "review.flagged": "À corriger",
7379
7444
  "review.previous": "Signalé lors de la revue précédente",
7380
- "variables.empty": "Aucune variable ne correspond"
7445
+ "variables.empty": "Aucune variable ne correspond",
7446
+ "geo.latitude": "Latitude",
7447
+ "geo.longitude": "Longitude",
7448
+ "geo.points": "points",
7449
+ "geo.clear": "Effacer",
7450
+ "geo.hintPoint": "Cliquez sur la carte pour placer le point, glissez-le pour le déplacer.",
7451
+ "geo.hintArea": "Cliquez sur la carte pour ajouter des points, glissez-en un pour le déplacer, clic droit pour le retirer.",
7452
+ "geo.unavailable": "Carte indisponible",
7453
+ "geo.nativeReadOnly": "Tracé sur la version web",
7454
+ "panel.center": "Centre initial (lat, lng)",
7455
+ "panel.zoom": "Zoom initial",
7456
+ "panel.height": "Hauteur (px)",
7457
+ "panel.tileUrl": "URL des tuiles",
7458
+ "panel.shape": "Forme (polygon ou line)",
7459
+ "panel.searchUrl": "URL de géocodage ({q} = recherche)",
7460
+ "panel.countryCodes": "Codes pays (bj, tg)",
7461
+ "address.search": "Rechercher une adresse",
7462
+ "address.searching": "Recherche…",
7463
+ "address.located": "Adresse localisée",
7464
+ "address.notLocated": "Adresse non localisée : choisissez une proposition pour la placer",
7465
+ "address.noResult": "Aucune adresse ne correspond",
7466
+ "address.unavailable": "Recherche d’adresse indisponible",
7467
+ "layer.pick": "Choisir une zone…",
7468
+ "layer.pickMany": "Cliquez les zones sur la carte",
7469
+ "layer.hint": "Cliquez une zone sur la carte pour la choisir",
7470
+ "layer.unavailable": "Couche de référence indisponible",
7471
+ "panel.layerUrl": "URL de la couche de référence (GeoJSON)",
7472
+ "panel.codeProperty": "Propriété du code",
7473
+ "panel.nameProperty": "Propriété du nom",
7474
+ "panel.idFormat": "Format de l’identifiant (fr-nir, bj-npi, digits-10…)",
7475
+ "mrz.placeholder": "Collez ou saisissez les deux ou trois lignes de la zone de lecture",
7476
+ "mrz.unreadable": "Zone non reconnue : 2 lignes de 44 (passeport) ou 3 lignes de 30 (carte d’identité)",
7477
+ "mrz.invalid": "Chiffres de contrôle incohérents pour :",
7478
+ "mrz.documentNumber": "Numéro du document",
7479
+ "mrz.nationality": "Nationalité",
7480
+ "mrz.surname": "Nom",
7481
+ "mrz.givenNames": "Prénoms",
7482
+ "mrz.birthDate": "Date de naissance",
7483
+ "mrz.sex": "Sexe",
7484
+ "mrz.expiryDate": "Date d’expiration",
7485
+ "category.Identity": "Identité",
7486
+ "category.Finance": "Finance",
7487
+ "money.amount": "Montant",
7488
+ "money.currency": "Devise",
7489
+ "panel.currencies": "Devises proposées (XOF, EUR, USD)",
7490
+ "panel.currency": "Devise par défaut"
7381
7491
  };
7382
7492
  var GLOBAL_KEY = Symbol.for("formkrafter.i18n");
7383
7493
  var globalStore = globalThis;
@@ -8181,6 +8291,198 @@ var collectFields = (spec, locale, options = {}) => {
8181
8291
  walk(spec, "0", out);
8182
8292
  return out;
8183
8293
  };
8294
+ // lib/utils/identity.ts
8295
+ var compact = (value) => value.replace(/[\s.-]/g, "").toUpperCase();
8296
+ function checkFrNir(value) {
8297
+ const raw = compact(value);
8298
+ if (!/^[12][0-9]{2}(0[1-9]|1[0-2]|[2-9][0-9])(2A|2B|[0-9]{2})[0-9]{6}[0-9]{2}$/.test(raw))
8299
+ return false;
8300
+ const number = raw.slice(0, 13).replace("2A", "19").replace("2B", "18");
8301
+ const key = Number(raw.slice(13));
8302
+ return key === 97 - Number(BigInt(number) % 97n);
8303
+ }
8304
+ function checkBjNpi(value) {
8305
+ return /^[0-9]{10}$/.test(compact(value));
8306
+ }
8307
+ function checkDigits(value, length) {
8308
+ return new RegExp(`^[0-9]{${length}}$`).test(compact(value));
8309
+ }
8310
+ function checkLuhn(digits) {
8311
+ if (!/^[0-9]+$/.test(digits))
8312
+ return false;
8313
+ let sum = 0;
8314
+ for (let i = 0;i < digits.length; i++) {
8315
+ let n = Number(digits[digits.length - 1 - i]);
8316
+ if (i % 2 === 1) {
8317
+ n *= 2;
8318
+ if (n > 9)
8319
+ n -= 9;
8320
+ }
8321
+ sum += n;
8322
+ }
8323
+ return sum % 10 === 0;
8324
+ }
8325
+ function checkFrSiren(value) {
8326
+ const raw = compact(value);
8327
+ return /^[0-9]{9}$/.test(raw) && checkLuhn(raw);
8328
+ }
8329
+ function checkFrSiret(value) {
8330
+ const raw = compact(value);
8331
+ return /^[0-9]{14}$/.test(raw) && checkLuhn(raw);
8332
+ }
8333
+ function checkBjIfu(value) {
8334
+ return /^[0-9]{13}$/.test(compact(value));
8335
+ }
8336
+ function checkIban(value) {
8337
+ const raw = compact(value);
8338
+ if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/.test(raw))
8339
+ return false;
8340
+ const rearranged = raw.slice(4) + raw.slice(0, 4);
8341
+ const numeric = rearranged.replace(/[A-Z]/g, (char) => String(char.charCodeAt(0) - 55));
8342
+ return Number(BigInt(numeric) % 97n) === 1;
8343
+ }
8344
+ function checkIdentity(format, value) {
8345
+ if (format === "fr-nir")
8346
+ return checkFrNir(value);
8347
+ if (format === "bj-npi")
8348
+ return checkBjNpi(value);
8349
+ if (format === "fr-siren")
8350
+ return checkFrSiren(value);
8351
+ if (format === "fr-siret")
8352
+ return checkFrSiret(value);
8353
+ if (format === "bj-ifu")
8354
+ return checkBjIfu(value);
8355
+ if (format === "iban")
8356
+ return checkIban(value);
8357
+ const digits = /^digits-([0-9]{1,3})$/.exec(format);
8358
+ if (digits)
8359
+ return checkDigits(value, Number(digits[1]));
8360
+ return value.trim().length > 0;
8361
+ }
8362
+ var IDENTITY_FORMATS = [
8363
+ { id: "fr-nir", label: "France · NIR (sécurité sociale)" },
8364
+ { id: "bj-npi", label: "Bénin · NPI" },
8365
+ { id: "fr-siren", label: "France · SIREN" },
8366
+ { id: "fr-siret", label: "France · SIRET" },
8367
+ { id: "bj-ifu", label: "Bénin · IFU" },
8368
+ { id: "iban", label: "IBAN" },
8369
+ { id: "digits-10", label: "10 chiffres" },
8370
+ { id: "digits-13", label: "13 chiffres" }
8371
+ ];
8372
+ var formatIban = (value) => compact(value).replace(/(.{4})(?=.)/g, "$1 ");
8373
+ // lib/utils/mrz.ts
8374
+ var WEIGHTS = [7, 3, 1];
8375
+ var charValue = (char) => {
8376
+ if (char === "<")
8377
+ return 0;
8378
+ if (/[0-9]/.test(char))
8379
+ return Number(char);
8380
+ if (/[A-Z]/.test(char))
8381
+ return char.charCodeAt(0) - 55;
8382
+ return 0;
8383
+ };
8384
+ function mrzCheckDigit(field) {
8385
+ let sum = 0;
8386
+ for (let i = 0;i < field.length; i++)
8387
+ sum += charValue(field[i]) * WEIGHTS[i % 3];
8388
+ return sum % 10;
8389
+ }
8390
+ var checks = (field, digit) => /[0-9]/.test(digit) && mrzCheckDigit(field) === Number(digit);
8391
+ var clean = (field) => field.replace(/</g, " ").trim().replace(/\s+/g, " ");
8392
+ var names = (field) => {
8393
+ const [surname = "", given = ""] = field.split("<<");
8394
+ return { surname: clean(surname), givenNames: clean(given) };
8395
+ };
8396
+ var date = (field, kind) => {
8397
+ if (!/^[0-9]{6}$/.test(field))
8398
+ return null;
8399
+ const yy = Number(field.slice(0, 2));
8400
+ const month = field.slice(2, 4);
8401
+ const day = field.slice(4, 6);
8402
+ const now = new Date().getFullYear();
8403
+ const century = Math.floor(now / 100) * 100;
8404
+ let year = century + yy;
8405
+ if (kind === "birth" && year > now)
8406
+ year -= 100;
8407
+ if (kind === "expiry" && year < now - 20)
8408
+ year += 100;
8409
+ return `${year}-${month}-${day}`;
8410
+ };
8411
+ var sex = (char) => char === "M" ? "M" : char === "F" ? "F" : "X";
8412
+ function mrzLines(text) {
8413
+ return text.split(/\r?\n/).map((line) => line.replace(/\s+/g, "").toUpperCase()).filter((line) => line.length > 0);
8414
+ }
8415
+ function parseMrz(text) {
8416
+ const lines = mrzLines(text);
8417
+ if (lines.length === 2 && lines.every((line) => line.length === 44))
8418
+ return parseTd3(lines[0], lines[1], text);
8419
+ if (lines.length === 3 && lines.every((line) => line.length === 30))
8420
+ return parseTd1(lines[0], lines[1], lines[2], text);
8421
+ return null;
8422
+ }
8423
+ function parseTd3(l1, l2, raw) {
8424
+ const errors = [];
8425
+ const documentNumber = l2.slice(0, 9);
8426
+ if (!checks(documentNumber, l2[9]))
8427
+ errors.push("documentNumber");
8428
+ const birth = l2.slice(13, 19);
8429
+ if (!checks(birth, l2[19]))
8430
+ errors.push("birthDate");
8431
+ const expiry = l2.slice(21, 27);
8432
+ if (!checks(expiry, l2[27]))
8433
+ errors.push("expiryDate");
8434
+ const optional = l2.slice(28, 42);
8435
+ if (optional.replace(/</g, "") && !checks(optional, l2[42]))
8436
+ errors.push("optional");
8437
+ const composite = l2.slice(0, 10) + l2.slice(13, 20) + l2.slice(21, 43);
8438
+ if (!checks(composite, l2[43]))
8439
+ errors.push("composite");
8440
+ return {
8441
+ raw,
8442
+ format: "TD3",
8443
+ documentType: clean(l1.slice(0, 2)),
8444
+ issuingCountry: clean(l1.slice(2, 5)),
8445
+ documentNumber: clean(documentNumber),
8446
+ nationality: clean(l2.slice(10, 13)),
8447
+ ...names(l1.slice(5)),
8448
+ birthDate: date(birth, "birth"),
8449
+ sex: sex(l2[20]),
8450
+ expiryDate: date(expiry, "expiry"),
8451
+ optional: clean(optional),
8452
+ valid: errors.length === 0,
8453
+ errors
8454
+ };
8455
+ }
8456
+ function parseTd1(l1, l2, l3, raw) {
8457
+ const errors = [];
8458
+ const documentNumber = l1.slice(5, 14);
8459
+ if (!checks(documentNumber, l1[14]))
8460
+ errors.push("documentNumber");
8461
+ const birth = l2.slice(0, 6);
8462
+ if (!checks(birth, l2[6]))
8463
+ errors.push("birthDate");
8464
+ const expiry = l2.slice(8, 14);
8465
+ if (!checks(expiry, l2[14]))
8466
+ errors.push("expiryDate");
8467
+ const composite = l1.slice(5, 30) + l2.slice(0, 7) + l2.slice(8, 15) + l2.slice(18, 29);
8468
+ if (!checks(composite, l2[29]))
8469
+ errors.push("composite");
8470
+ return {
8471
+ raw,
8472
+ format: "TD1",
8473
+ documentType: clean(l1.slice(0, 2)),
8474
+ issuingCountry: clean(l1.slice(2, 5)),
8475
+ documentNumber: clean(documentNumber),
8476
+ nationality: clean(l2.slice(15, 18)),
8477
+ ...names(l3),
8478
+ birthDate: date(birth, "birth"),
8479
+ sex: sex(l2[7]),
8480
+ expiryDate: date(expiry, "expiry"),
8481
+ optional: clean(l1.slice(15, 30) + l2.slice(18, 29)),
8482
+ valid: errors.length === 0,
8483
+ errors
8484
+ };
8485
+ }
8184
8486
  // lib/review.ts
8185
8487
  var flaggedKeys = (annotations) => Object.entries(annotations ?? {}).filter(([, annotation]) => annotation?.incorrect === true).map(([key]) => key);
8186
8488
  // lib/utils/lint-spec.ts
@@ -8340,6 +8642,14 @@ var import_ajv_formats = __toESM(require_dist2(), 1);
8340
8642
  var ajv = new import_ajv.Ajv({ allErrors: true });
8341
8643
  import_ajv_formats.default(ajv, ["email", "uri"]);
8342
8644
  import_ajv_errors.default(ajv);
8645
+ for (const format of ["fr-nir", "bj-npi", "fr-siren", "fr-siret", "bj-ifu", "iban", ...Array.from({ length: 20 }, (_, i) => `digits-${i + 1}`)])
8646
+ ajv.addFormat(`identity-${format}`, (value) => checkIdentity(format, value));
8647
+ ajv.addKeyword({
8648
+ keyword: "mrzValid",
8649
+ type: "object",
8650
+ schemaType: "boolean",
8651
+ validate: (expected, value) => !expected || value?.valid === true
8652
+ });
8343
8653
  var validatorCache = new WeakMap;
8344
8654
  var compileFailures = new WeakMap;
8345
8655
  var recordCompileFailure = (brickSpec, error) => {
@@ -9275,6 +9585,7 @@ export {
9275
9585
  FetchDataSourceService,
9276
9586
  FetchOptionSourceService,
9277
9587
  FetchSpecSourceService,
9588
+ IDENTITY_FORMATS,
9278
9589
  SandboxJsRunnerService,
9279
9590
  SpecHistory,
9280
9591
  UnsafeEvalJsRunnerService,
@@ -9282,6 +9593,15 @@ export {
9282
9593
  addBrick,
9283
9594
  appendSearchParam,
9284
9595
  buildValidationSchema,
9596
+ checkBjIfu,
9597
+ checkBjNpi,
9598
+ checkDigits,
9599
+ checkFrNir,
9600
+ checkFrSiren,
9601
+ checkFrSiret,
9602
+ checkIban,
9603
+ checkIdentity,
9604
+ checkLuhn,
9285
9605
  collectFields,
9286
9606
  collectRecapItems,
9287
9607
  convertFormioForm,
@@ -9294,6 +9614,7 @@ export {
9294
9614
  fkT,
9295
9615
  fkTOr,
9296
9616
  flaggedKeys,
9617
+ formatIban,
9297
9618
  frFkTranslations,
9298
9619
  getAffectedProperties,
9299
9620
  getBrickAt,
@@ -9305,8 +9626,11 @@ export {
9305
9626
  iterateSchemaBricks,
9306
9627
  lintSpec,
9307
9628
  moveBrick,
9629
+ mrzCheckDigit,
9630
+ mrzLines,
9308
9631
  normalizeOptions,
9309
9632
  parseHeaderLines,
9633
+ parseMrz,
9310
9634
  pointerFromPath,
9311
9635
  registerValidationMessages,
9312
9636
  removeBrick,
@@ -9327,5 +9651,5 @@ export {
9327
9651
  wrapBrickData
9328
9652
  };
9329
9653
 
9330
- //# debugId=5D02D861AEE5308664756E2164756E21
9654
+ //# debugId=9E13D1BABBC3D27264756E2164756E21
9331
9655
  //# sourceMappingURL=index.js.map