@streamline-pulse/formkrafter-core 0.26.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) {
@@ -7248,7 +7266,39 @@ var en = {
7248
7266
  "panel.zoom": "Initial zoom",
7249
7267
  "panel.height": "Height (px)",
7250
7268
  "panel.tileUrl": "Tile URL",
7251
- "panel.shape": "Shape (polygon or line)"
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"
7252
7302
  };
7253
7303
  var frFkTranslations = {
7254
7304
  "palette.search": "Rechercher une brique…",
@@ -7405,7 +7455,39 @@ var frFkTranslations = {
7405
7455
  "panel.zoom": "Zoom initial",
7406
7456
  "panel.height": "Hauteur (px)",
7407
7457
  "panel.tileUrl": "URL des tuiles",
7408
- "panel.shape": "Forme (polygon ou line)"
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"
7409
7491
  };
7410
7492
  var GLOBAL_KEY = Symbol.for("formkrafter.i18n");
7411
7493
  var globalStore = globalThis;
@@ -8209,6 +8291,198 @@ var collectFields = (spec, locale, options = {}) => {
8209
8291
  walk(spec, "0", out);
8210
8292
  return out;
8211
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
+ }
8212
8486
  // lib/review.ts
8213
8487
  var flaggedKeys = (annotations) => Object.entries(annotations ?? {}).filter(([, annotation]) => annotation?.incorrect === true).map(([key]) => key);
8214
8488
  // lib/utils/lint-spec.ts
@@ -8368,6 +8642,14 @@ var import_ajv_formats = __toESM(require_dist2(), 1);
8368
8642
  var ajv = new import_ajv.Ajv({ allErrors: true });
8369
8643
  import_ajv_formats.default(ajv, ["email", "uri"]);
8370
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
+ });
8371
8653
  var validatorCache = new WeakMap;
8372
8654
  var compileFailures = new WeakMap;
8373
8655
  var recordCompileFailure = (brickSpec, error) => {
@@ -9303,6 +9585,7 @@ export {
9303
9585
  FetchDataSourceService,
9304
9586
  FetchOptionSourceService,
9305
9587
  FetchSpecSourceService,
9588
+ IDENTITY_FORMATS,
9306
9589
  SandboxJsRunnerService,
9307
9590
  SpecHistory,
9308
9591
  UnsafeEvalJsRunnerService,
@@ -9310,6 +9593,15 @@ export {
9310
9593
  addBrick,
9311
9594
  appendSearchParam,
9312
9595
  buildValidationSchema,
9596
+ checkBjIfu,
9597
+ checkBjNpi,
9598
+ checkDigits,
9599
+ checkFrNir,
9600
+ checkFrSiren,
9601
+ checkFrSiret,
9602
+ checkIban,
9603
+ checkIdentity,
9604
+ checkLuhn,
9313
9605
  collectFields,
9314
9606
  collectRecapItems,
9315
9607
  convertFormioForm,
@@ -9322,6 +9614,7 @@ export {
9322
9614
  fkT,
9323
9615
  fkTOr,
9324
9616
  flaggedKeys,
9617
+ formatIban,
9325
9618
  frFkTranslations,
9326
9619
  getAffectedProperties,
9327
9620
  getBrickAt,
@@ -9333,8 +9626,11 @@ export {
9333
9626
  iterateSchemaBricks,
9334
9627
  lintSpec,
9335
9628
  moveBrick,
9629
+ mrzCheckDigit,
9630
+ mrzLines,
9336
9631
  normalizeOptions,
9337
9632
  parseHeaderLines,
9633
+ parseMrz,
9338
9634
  pointerFromPath,
9339
9635
  registerValidationMessages,
9340
9636
  removeBrick,
@@ -9355,5 +9651,5 @@ export {
9355
9651
  wrapBrickData
9356
9652
  };
9357
9653
 
9358
- //# debugId=24BB3E067E2F0D9064756E2164756E21
9654
+ //# debugId=9E13D1BABBC3D27264756E2164756E21
9359
9655
  //# sourceMappingURL=index.js.map