@streamline-pulse/formkrafter-core 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -194,781 +194,624 @@ function normalizeOptions(raw, labelKey = "label", valueKey = "value") {
194
194
  return String(raw ?? "").split(`
195
195
  `).map((line) => line.trim()).filter(Boolean).map((line) => ({ label: line, value: line }));
196
196
  }
197
- // lib/brick/brick.ts
198
- class Brick {
199
- type;
200
- dataType;
201
- id;
202
- name;
203
- configsForm;
204
- category;
205
- styles;
206
- unWrapData;
207
- isPrivate;
208
- validators;
209
- defaultConfigs;
210
- constructor(params) {
211
- this.type = params.type;
212
- this.dataType = params.dataType;
213
- this.id = params.id;
214
- this.name = params.name;
215
- this.configsForm = params.configsForm;
216
- this.category = params.category;
217
- this.styles = params.styles;
218
- this.unWrapData = params.unWrapData ?? false;
219
- this.isPrivate = params.isPrivate ?? false;
220
- this.validators = params.validators ?? [];
221
- this.defaultConfigs = params.defaultConfigs;
222
- }
223
- getBrickEmptySpec() {
224
- return {
225
- type: this.type,
226
- dataType: this.dataType,
227
- id: this.id,
228
- name: this.name,
229
- ...this.category !== undefined ? { category: this.category } : {},
230
- ...this.configsForm !== undefined ? { configsForm: this.configsForm } : {},
231
- ...this.styles !== undefined ? { styles: this.styles } : {},
232
- ...this.defaultConfigs !== undefined ? { configs: { ...this.defaultConfigs } } : {},
233
- validators: this.validators
234
- };
235
- }
236
- }
237
- // lib/brick/utils.ts
238
- import jsonLogic from "json-logic-js";
239
-
240
- // lib/services/js_runner_service.ts
241
- import { parse as parse2 } from "acorn";
242
-
243
- // lib/services/sandbox_interpreter.ts
244
- import { parse } from "acorn";
245
- var FORBIDDEN_PROPS = new Set(["__proto__", "constructor", "prototype"]);
246
- var MAX_STEPS = 1e5;
247
-
248
- class ReturnSignal {
249
- value;
250
- constructor(value) {
251
- this.value = value;
252
- }
253
- }
254
- var lookup = (ctx, name) => {
255
- for (let i = ctx.scopes.length - 1;i >= 0; i--) {
256
- if (ctx.scopes[i].has(name))
257
- return ctx.scopes[i].get(name);
258
- }
259
- throw new Error(`Unknown identifier "${name}"`);
260
- };
261
- var declareVar = (ctx, name, value) => {
262
- ctx.scopes[ctx.scopes.length - 1].set(name, value);
263
- };
264
- var assign = (ctx, name, value) => {
265
- for (let i = ctx.scopes.length - 1;i >= 0; i--) {
266
- if (ctx.scopes[i].has(name)) {
267
- ctx.scopes[i].set(name, value);
268
- return;
269
- }
270
- }
271
- throw new Error(`Cannot assign to unknown identifier "${name}"`);
272
- };
273
- var guardProp = (prop) => {
274
- if (typeof prop === "number")
275
- return prop;
276
- const name = String(prop);
277
- if (FORBIDDEN_PROPS.has(name)) {
278
- throw new Error(`Access to "${name}" is not allowed`);
279
- }
280
- return name;
281
- };
282
- var memberTarget = (ctx, node) => {
283
- const object = evalNode(ctx, node.object);
284
- const prop = node.computed ? guardProp(evalNode(ctx, node.property)) : guardProp(node.property.name);
285
- return { object, prop, optional: node.optional === true };
286
- };
287
- var readMember = (ctx, node) => {
288
- const { object, prop, optional } = memberTarget(ctx, node);
289
- if (object === null || object === undefined) {
290
- if (optional)
291
- return;
292
- throw new Error(`Cannot read "${String(prop)}" of ${String(object)}`);
293
- }
294
- return object[prop];
295
- };
296
- var evalNode = (ctx, node) => {
297
- if (++ctx.steps > MAX_STEPS) {
298
- throw new Error("Sandbox execution budget exceeded");
299
- }
300
- switch (node.type) {
301
- case "Program":
302
- case "BlockStatement": {
303
- for (const statement of node.body) {
304
- const result = evalNode(ctx, statement);
305
- if (result instanceof ReturnSignal)
306
- return result;
307
- }
308
- return;
309
- }
310
- case "ReturnStatement":
311
- return new ReturnSignal(node.argument ? evalNode(ctx, node.argument) : undefined);
312
- case "IfStatement": {
313
- if (evalNode(ctx, node.test)) {
314
- return evalNode(ctx, node.consequent);
315
- }
316
- if (node.alternate)
317
- return evalNode(ctx, node.alternate);
318
- return;
319
- }
320
- case "VariableDeclaration": {
321
- for (const declaration of node.declarations) {
322
- const id = declaration.id;
323
- if (id.type !== "Identifier") {
324
- throw new Error("Only simple variable names are supported");
325
- }
326
- declareVar(ctx, id.name, declaration.init ? evalNode(ctx, declaration.init) : undefined);
327
- }
328
- return;
329
- }
330
- case "ExpressionStatement":
331
- evalNode(ctx, node.expression);
332
- return;
333
- case "Literal":
334
- return node.value;
335
- case "TemplateLiteral": {
336
- const quasis = node.quasis;
337
- const expressions = node.expressions;
338
- let text = "";
339
- quasis.forEach((quasi, index) => {
340
- text += quasi.value.cooked ?? "";
341
- if (index < expressions.length) {
342
- text += String(evalNode(ctx, expressions[index]));
343
- }
344
- });
345
- return text;
346
- }
347
- case "Identifier":
348
- return lookup(ctx, node.name);
349
- case "ThisExpression":
350
- return;
351
- case "ChainExpression":
352
- return evalNode(ctx, node.expression);
353
- case "MemberExpression":
354
- return readMember(ctx, node);
355
- case "CallExpression": {
356
- const callee = node.callee;
357
- const args = [];
358
- for (const argument of node.arguments) {
359
- if (argument.type === "SpreadElement") {
360
- const spread = evalNode(ctx, argument.argument);
361
- if (Array.isArray(spread))
362
- args.push(...spread);
363
- } else {
364
- args.push(evalNode(ctx, argument));
365
- }
366
- }
367
- if (callee.type === "MemberExpression") {
368
- const { object, prop, optional } = memberTarget(ctx, callee);
369
- if (object === null || object === undefined) {
370
- if (optional || node.optional === true)
371
- return;
372
- throw new Error(`Cannot call "${String(prop)}" of ${String(object)}`);
373
- }
374
- const fn3 = object[prop];
375
- if (typeof fn3 !== "function") {
376
- if (node.optional === true && fn3 == null)
377
- return;
378
- throw new Error(`"${String(prop)}" is not a function`);
379
- }
380
- if (fn3 === Function || fn3 === eval) {
381
- throw new Error("This function is not allowed");
382
- }
383
- return fn3.apply(object, args);
384
- }
385
- const fn2 = evalNode(ctx, callee);
386
- if (typeof fn2 !== "function") {
387
- if (node.optional === true && fn2 == null)
388
- return;
389
- throw new Error("Value is not a function");
390
- }
391
- if (fn2 === Function || fn2 === eval) {
392
- throw new Error("This function is not allowed");
393
- }
394
- return fn2(...args);
395
- }
396
- case "ArrowFunctionExpression": {
397
- const params = node.params;
398
- const body = node.body;
399
- const capturedScopes = [...ctx.scopes];
400
- return (...args) => {
401
- const callScope = new Map;
402
- params.forEach((param, index) => {
403
- if (param.type !== "Identifier") {
404
- throw new Error("Only simple parameters are supported");
405
- }
406
- callScope.set(param.name, args[index]);
407
- });
408
- const callCtx = {
409
- steps: ctx.steps,
410
- scopes: [...capturedScopes, callScope]
411
- };
412
- const result = evalNode(callCtx, body);
413
- ctx.steps = callCtx.steps;
414
- if (result instanceof ReturnSignal)
415
- return result.value;
416
- return node.expression === true ? result : undefined;
417
- };
418
- }
419
- case "BinaryExpression": {
420
- const left = evalNode(ctx, node.left);
421
- const right = evalNode(ctx, node.right);
422
- switch (node.operator) {
423
- case "===":
424
- return left === right;
425
- case "!==":
426
- return left !== right;
427
- case "==":
428
- return left == right;
429
- case "!=":
430
- return left != right;
431
- case "<":
432
- return left < right;
433
- case "<=":
434
- return left <= right;
435
- case ">":
436
- return left > right;
437
- case ">=":
438
- return left >= right;
439
- case "+":
440
- return left + right;
441
- case "-":
442
- return left - right;
443
- case "*":
444
- return left * right;
445
- case "/":
446
- return left / right;
447
- case "%":
448
- return left % right;
449
- case "**":
450
- return left ** right;
451
- default:
452
- throw new Error(`Unsupported operator "${String(node.operator)}"`);
453
- }
454
- }
455
- case "LogicalExpression": {
456
- const left = evalNode(ctx, node.left);
457
- switch (node.operator) {
458
- case "&&":
459
- return left && evalNode(ctx, node.right);
460
- case "||":
461
- return left || evalNode(ctx, node.right);
462
- case "??":
463
- return left ?? evalNode(ctx, node.right);
464
- default:
465
- throw new Error(`Unsupported operator "${String(node.operator)}"`);
466
- }
467
- }
468
- case "UnaryExpression": {
469
- const argument = evalNode(ctx, node.argument);
470
- switch (node.operator) {
471
- case "!":
472
- return !argument;
473
- case "-":
474
- return -argument;
475
- case "+":
476
- return +argument;
477
- case "typeof":
478
- return typeof argument;
479
- default:
480
- throw new Error(`Unsupported operator "${String(node.operator)}"`);
481
- }
482
- }
483
- case "ConditionalExpression":
484
- return evalNode(ctx, node.test) ? evalNode(ctx, node.consequent) : evalNode(ctx, node.alternate);
485
- case "AssignmentExpression": {
486
- const target = node.left;
487
- if (target.type !== "Identifier" || node.operator !== "=") {
488
- throw new Error("Only simple assignments are supported");
489
- }
490
- const value = evalNode(ctx, node.right);
491
- assign(ctx, target.name, value);
492
- return value;
493
- }
494
- case "ArrayExpression": {
495
- const values = [];
496
- for (const element of node.elements) {
497
- if (!element)
498
- continue;
499
- if (element.type === "SpreadElement") {
500
- const spread = evalNode(ctx, element.argument);
501
- if (Array.isArray(spread))
502
- values.push(...spread);
503
- } else {
504
- values.push(evalNode(ctx, element));
505
- }
506
- }
507
- return values;
508
- }
509
- case "ObjectExpression": {
510
- const result = {};
511
- for (const property of node.properties) {
512
- if (property.type === "SpreadElement") {
513
- const spread = evalNode(ctx, property.argument);
514
- if (spread && typeof spread === "object") {
515
- for (const [key2, value] of Object.entries(spread)) {
516
- result[String(guardProp(key2))] = value;
517
- }
518
- }
519
- continue;
520
- }
521
- const keyNode = property.key;
522
- const key = property.computed ? guardProp(evalNode(ctx, keyNode)) : guardProp(keyNode.type === "Identifier" ? keyNode.name : keyNode.value);
523
- result[String(key)] = evalNode(ctx, property.value);
524
- }
525
- return result;
526
- }
527
- default:
528
- throw new Error(`Unsupported syntax: ${node.type}`);
529
- }
197
+ // lib/i18n.ts
198
+ var en = {
199
+ "palette.search": "Search bricks…",
200
+ "palette.empty": "No brick matches “{query}”",
201
+ "builder.undo": "Undo",
202
+ "builder.redo": "Redo",
203
+ "builder.editLocale": "Editing language",
204
+ "builder.copyJson": "Copy JSON",
205
+ "builder.importJson": "Import JSON",
206
+ "builder.importInvalid": "Invalid form JSON",
207
+ "builder.pasteHint": "Press Ctrl+V to paste a spec",
208
+ "builder.copied": "Copied!",
209
+ "builder.downloadJson": "Download .json",
210
+ "builder.hint": "Select a brick to edit its properties",
211
+ "drop.hint": "Drop a brick here",
212
+ "empty.title": "Your form is empty",
213
+ "empty.hint": "Drag a brick from the palette to get started",
214
+ "actions.move": "Move",
215
+ "actions.duplicate": "Duplicate",
216
+ "actions.delete": "Delete",
217
+ "stepper.back": "← Back",
218
+ "stepper.next": "Next →",
219
+ "stepper.submit": "Submit",
220
+ "panel.cfg.validateSteps": "Validate each step",
221
+ "panel.cfg.allowStepClick": "Allow clicking steps to jump",
222
+ "panel.cfg.showSubmit": "Submit button on last step",
223
+ "panel.cfg.validateTabs": "Validate tab before leaving it",
224
+ "signature.clear": "Clear",
225
+ "panel.tab.config": "Config",
226
+ "panel.tab.validation": "Validation",
227
+ "panel.tab.styles": "Styles",
228
+ "panel.tab.rules": "Rules",
229
+ "panel.key": "Key",
230
+ "panel.label": "Label",
231
+ "panel.placeholder": "Placeholder",
232
+ "panel.prefix": "Prefix",
233
+ "panel.suffix": "Suffix",
234
+ "panel.accept": "Accepted file types",
235
+ "panel.mask": "Input mask (9=digit, a=letter, *=any)",
236
+ "panel.uploadUrl": "Upload URL",
237
+ "panel.cfg.multiple": "Allow multiple files",
238
+ "panel.optionsSource": "Options source",
239
+ "panel.options": "Options (one per line)",
240
+ "panel.content": "Content",
241
+ "panel.dataKey": "Data key",
242
+ "panel.url": "URL",
243
+ "panel.headers": "Headers ({token} reads form data)",
244
+ "panel.searchParam": "Server search param",
245
+ "panel.labelKey": "Label key",
246
+ "panel.valueKey": "Value key",
247
+ "panel.errorMessage": "Error message",
248
+ "panel.required": "Required",
249
+ "panel.minLength": "Min length",
250
+ "panel.maxLength": "Max length",
251
+ "panel.min": "Min",
252
+ "panel.max": "Max",
253
+ "panel.pattern": "Pattern (regex)",
254
+ "panel.minItems": "Min items",
255
+ "panel.maxItems": "Max items",
256
+ "panel.emailFormat": "Email format",
257
+ "panel.urlFormat": "URL format",
258
+ "panel.custom": "Custom (JavaScript)",
259
+ "panel.fallbackMessage": "Fallback message",
260
+ "panel.styleProperty": "property",
261
+ "panel.styleValue": "value",
262
+ "file.choose": "Choose a file…",
263
+ "file.uploading": "Uploading…",
264
+ "file.remove": "Remove file",
265
+ "file.addAnother": "+ Add a file",
266
+ "address.street": "Street",
267
+ "address.city": "City",
268
+ "address.zip": "Postal code",
269
+ "address.country": "Country",
270
+ "grid.addRow": "+ Add row",
271
+ "grid.moveUp": "Move row up",
272
+ "grid.moveDown": "Move row down",
273
+ "grid.removeRow": "Remove row",
274
+ "grid.empty": "No rows yet",
275
+ "grid.columns": "Row template",
276
+ "select.search": "Search…",
277
+ "select.empty": "No options",
278
+ "select.clear": "Clear",
279
+ "rules.when": "When",
280
+ "rules.then": "Then",
281
+ "rules.addRule": "+ Add rule",
282
+ "rules.addEffect": "+ Effect",
283
+ "rules.removeRule": "Remove rule",
284
+ "rules.removeEffect": "Remove effect",
285
+ "rules.mode.simple": "simple",
286
+ "rules.mode.json": "json",
287
+ "rules.mode.js": "JS",
288
+ "category.Inputs": "Inputs",
289
+ "category.Layout": "Layout",
290
+ "category.Data": "Data",
291
+ "brick.text.name": "Text",
292
+ "brick.email.name": "Email",
293
+ "brick.password.name": "Password",
294
+ "brick.url.name": "URL",
295
+ "brick.phone.name": "Phone",
296
+ "brick.textarea.name": "Text area",
297
+ "brick.number.name": "Number",
298
+ "brick.date.name": "Date",
299
+ "brick.time.name": "Time",
300
+ "brick.datetime.name": "Date / Time",
301
+ "brick.select.name": "Select",
302
+ "brick.multi-select.name": "Multi select",
303
+ "brick.radio.name": "Radio",
304
+ "brick.select-boxes.name": "Select boxes",
305
+ "brick.checkbox.name": "Checkbox",
306
+ "brick.tags.name": "Tags",
307
+ "brick.signature.name": "Signature",
308
+ "brick.data-grid.name": "Data grid",
309
+ "brick.address.name": "Address",
310
+ "brick.file.name": "File",
311
+ "brick.hidden.name": "Hidden",
312
+ "brick.content.name": "Content",
313
+ "brick.recap.name": "Recap",
314
+ "brick.nested-form.name": "Nested form",
315
+ "brick.code.name": "Code",
316
+ "nestedForm.ref": "Referenced form",
317
+ "nestedForm.missing": "Set the specRef config to reference a form.",
318
+ "nestedForm.loading": "Loading nested forms…",
319
+ "panel.specRef": "Referenced form (specRef)",
320
+ "panel.optionsRef": "Options catalog (optionsRef)",
321
+ "recap.empty": "Nothing to summarize yet.",
322
+ "recap.yes": "Yes",
323
+ "recap.no": "No",
324
+ "panel.cfg.showEmpty": "Show empty fields",
325
+ "panel.cfg.groupBySections": "Group by section",
326
+ "brick.group.name": "Group",
327
+ "brick.row.name": "Row",
328
+ "brick.column.name": "Column",
329
+ "brick.stepper.name": "Stepper",
330
+ "brick.tabs.name": "Tabs"
530
331
  };
531
- var SAFE_BUILTINS = {
532
- undefined: undefined,
533
- NaN: NaN,
534
- Infinity: Infinity,
535
- Math,
536
- JSON,
537
- Number,
538
- String,
539
- Boolean,
540
- Array,
541
- Object,
542
- Date,
543
- parseInt,
544
- parseFloat,
545
- isNaN,
546
- isFinite
332
+ var frFkTranslations = {
333
+ "palette.search": "Rechercher une brique…",
334
+ "palette.empty": "Aucune brique ne correspond à « {query} »",
335
+ "builder.undo": "Annuler",
336
+ "builder.editLocale": "Langue d'édition",
337
+ "builder.redo": "Rétablir",
338
+ "builder.copyJson": "Copier le JSON",
339
+ "builder.importJson": "Importer le JSON",
340
+ "builder.importInvalid": "JSON de formulaire invalide",
341
+ "builder.pasteHint": "Collez avec Ctrl+V pour importer une spec",
342
+ "builder.copied": "Copié !",
343
+ "builder.downloadJson": "Télécharger le .json",
344
+ "builder.hint": "Sélectionnez une brique pour éditer ses propriétés",
345
+ "drop.hint": "Déposez une brique ici",
346
+ "empty.title": "Votre formulaire est vide",
347
+ "empty.hint": "Glissez une brique depuis la palette pour commencer",
348
+ "actions.move": "Déplacer",
349
+ "actions.duplicate": "Dupliquer",
350
+ "actions.delete": "Supprimer",
351
+ "stepper.back": "← Précédent",
352
+ "stepper.next": "Suivant →",
353
+ "stepper.submit": "Envoyer",
354
+ "panel.cfg.validateSteps": "Valider chaque étape",
355
+ "panel.cfg.allowStepClick": "Autoriser le saut d’étapes au clic",
356
+ "panel.cfg.showSubmit": "Bouton d’envoi à la dernière étape",
357
+ "panel.cfg.validateTabs": "Valider l’onglet avant de le quitter",
358
+ "signature.clear": "Effacer",
359
+ "panel.tab.config": "Config",
360
+ "panel.tab.validation": "Validation",
361
+ "panel.tab.styles": "Styles",
362
+ "panel.tab.rules": "Règles",
363
+ "panel.key": "Clé",
364
+ "panel.label": "Libellé",
365
+ "panel.placeholder": "Placeholder",
366
+ "panel.prefix": "Préfixe",
367
+ "panel.suffix": "Suffixe",
368
+ "panel.accept": "Types de fichiers acceptés",
369
+ "panel.mask": "Masque de saisie (9=chiffre, a=lettre, *=tout)",
370
+ "panel.uploadUrl": "URL d'upload",
371
+ "panel.cfg.multiple": "Autoriser plusieurs fichiers",
372
+ "panel.optionsSource": "Source des options",
373
+ "panel.options": "Options (une par ligne)",
374
+ "panel.content": "Contenu",
375
+ "panel.dataKey": "Clé de données",
376
+ "panel.url": "URL",
377
+ "panel.headers": "En-têtes ({token} lit les données du formulaire)",
378
+ "panel.searchParam": "Paramètre de recherche serveur",
379
+ "panel.labelKey": "Clé du libellé",
380
+ "panel.valueKey": "Clé de la valeur",
381
+ "panel.errorMessage": "Message d'erreur",
382
+ "panel.required": "Obligatoire",
383
+ "panel.minLength": "Longueur min",
384
+ "panel.maxLength": "Longueur max",
385
+ "panel.min": "Min",
386
+ "panel.max": "Max",
387
+ "panel.pattern": "Motif (regex)",
388
+ "panel.minItems": "Éléments min",
389
+ "panel.maxItems": "Éléments max",
390
+ "panel.emailFormat": "Format email",
391
+ "panel.urlFormat": "Format URL",
392
+ "panel.custom": "Personnalisé (JavaScript)",
393
+ "panel.fallbackMessage": "Message par défaut",
394
+ "panel.styleProperty": "propriété",
395
+ "panel.styleValue": "valeur",
396
+ "file.choose": "Choisir un fichier…",
397
+ "file.uploading": "Envoi en cours…",
398
+ "file.remove": "Retirer le fichier",
399
+ "file.addAnother": "+ Ajouter un fichier",
400
+ "address.street": "Rue",
401
+ "address.city": "Ville",
402
+ "address.zip": "Code postal",
403
+ "address.country": "Pays",
404
+ "grid.addRow": "+ Ajouter une ligne",
405
+ "grid.moveUp": "Monter la ligne",
406
+ "grid.moveDown": "Descendre la ligne",
407
+ "grid.removeRow": "Supprimer la ligne",
408
+ "grid.empty": "Aucune ligne",
409
+ "grid.columns": "Modèle de ligne",
410
+ "select.search": "Rechercher…",
411
+ "select.empty": "Aucune option",
412
+ "select.clear": "Effacer",
413
+ "rules.when": "Quand",
414
+ "rules.then": "Alors",
415
+ "rules.addRule": "+ Ajouter une règle",
416
+ "rules.addEffect": "+ Effet",
417
+ "rules.removeRule": "Supprimer la règle",
418
+ "rules.removeEffect": "Supprimer l'effet",
419
+ "rules.mode.simple": "simple",
420
+ "rules.mode.json": "json",
421
+ "rules.mode.js": "JS",
422
+ "category.Inputs": "Champs",
423
+ "category.Layout": "Mise en page",
424
+ "category.Data": "Données",
425
+ "brick.text.name": "Texte",
426
+ "brick.email.name": "Email",
427
+ "brick.password.name": "Mot de passe",
428
+ "brick.url.name": "URL",
429
+ "brick.phone.name": "Téléphone",
430
+ "brick.textarea.name": "Zone de texte",
431
+ "brick.number.name": "Nombre",
432
+ "brick.date.name": "Date",
433
+ "brick.time.name": "Heure",
434
+ "brick.datetime.name": "Date / Heure",
435
+ "brick.select.name": "Liste déroulante",
436
+ "brick.multi-select.name": "Sélection multiple",
437
+ "brick.radio.name": "Boutons radio",
438
+ "brick.select-boxes.name": "Cases à cocher",
439
+ "brick.checkbox.name": "Case à cocher",
440
+ "brick.tags.name": "Étiquettes",
441
+ "brick.signature.name": "Signature",
442
+ "brick.data-grid.name": "Grille de données",
443
+ "brick.address.name": "Adresse",
444
+ "brick.file.name": "Fichier",
445
+ "brick.hidden.name": "Caché",
446
+ "brick.content.name": "Contenu",
447
+ "brick.recap.name": "Récapitulatif",
448
+ "brick.nested-form.name": "Formulaire imbriqué",
449
+ "brick.code.name": "Code",
450
+ "nestedForm.ref": "Formulaire référencé",
451
+ "nestedForm.missing": "Renseignez la config specRef pour référencer un formulaire.",
452
+ "nestedForm.loading": "Chargement des formulaires imbriqués…",
453
+ "panel.specRef": "Formulaire référencé (specRef)",
454
+ "panel.optionsRef": "Catalogue d'options (optionsRef)",
455
+ "recap.empty": "Rien à récapituler pour le moment.",
456
+ "recap.yes": "Oui",
457
+ "recap.no": "Non",
458
+ "panel.cfg.showEmpty": "Afficher les champs vides",
459
+ "panel.cfg.groupBySections": "Regrouper par section",
460
+ "brick.group.name": "Groupe",
461
+ "brick.row.name": "Ligne",
462
+ "brick.column.name": "Colonne",
463
+ "brick.stepper.name": "Étapes",
464
+ "brick.tabs.name": "Onglets"
547
465
  };
548
- function runSandboxed(code, globals = {}) {
549
- const ast = parse(code, {
550
- ecmaVersion: "latest",
551
- allowReturnOutsideFunction: true
552
- });
553
- const ctx = {
554
- steps: 0,
555
- scopes: [
556
- new Map(Object.entries({ ...SAFE_BUILTINS, ...globals })),
557
- new Map
558
- ]
559
- };
560
- const result = evalNode(ctx, ast);
561
- return result instanceof ReturnSignal ? result.value : undefined;
466
+ var GLOBAL_KEY = Symbol.for("formkrafter.i18n");
467
+ var globalStore = globalThis;
468
+ var state = globalStore[GLOBAL_KEY] ??= { current: { ...en } };
469
+ function setFkTranslations(overrides = {}) {
470
+ state.current = { ...en, ...overrides };
562
471
  }
563
-
564
- // lib/services/js_runner_service.ts
565
- class SandboxJsRunnerService {
566
- validateJs(js2, options = { ecmaVersion: "latest", allowReturnOutsideFunction: true }) {
567
- try {
568
- parse2(js2, options);
569
- return { valid: true };
570
- } catch (error) {
571
- return {
572
- valid: false,
573
- error: error instanceof Error ? error : new Error(String(error))
574
- };
575
- }
576
- }
577
- eval(js2, scope2 = {}) {
578
- return runSandboxed(js2, scope2);
472
+ function fkT(key, params) {
473
+ let text = state.current[key] ?? key;
474
+ for (const [name, value] of Object.entries(params ?? {})) {
475
+ text = text.replace(`{${name}}`, String(value));
579
476
  }
477
+ return text;
580
478
  }
581
-
582
- // lib/services/data_source_service.ts
583
- class FetchDataSourceService {
584
- defaults;
585
- cache = new Map;
586
- constructor(defaults = {}) {
587
- this.defaults = defaults;
588
- }
589
- fetchOptions(url, options) {
590
- const headers = { ...this.defaults.headers, ...options?.headers };
591
- const init = {};
592
- if (Object.keys(headers).length)
593
- init.headers = headers;
594
- if (this.defaults.credentials)
595
- init.credentials = this.defaults.credentials;
596
- const key = `${url}|${JSON.stringify(init)}`;
597
- let pending = this.cache.get(key);
598
- if (!pending) {
599
- pending = fetch(url, Object.keys(init).length ? init : undefined).then((response) => {
600
- if (!response.ok) {
601
- throw new Error(`Data source request failed (${response.status}): ${url}`);
602
- }
603
- return response.json();
604
- }).then((json) => Array.isArray(json) ? json : []).catch((error) => {
605
- this.cache.delete(key);
606
- throw error;
607
- });
608
- this.cache.set(key, pending);
609
- }
610
- return pending;
611
- }
479
+ function fkTOr(key, fallback) {
480
+ return state.current[key] ?? fallback;
612
481
  }
613
482
 
614
- // lib/services/file_upload_service.ts
615
- class Base64FileUploadService {
616
- async upload(file) {
617
- const encode = globalThis.btoa;
618
- if (!encode)
619
- throw new Error("No base64 encoder available");
620
- const bytes = new Uint8Array(await file.arrayBuffer());
621
- let binary = "";
622
- for (const byte of bytes)
623
- binary += String.fromCharCode(byte);
624
- return {
625
- name: file.name,
626
- type: file.type,
627
- size: file.size,
628
- url: `data:${file.type};base64,${encode(binary)}`
629
- };
630
- }
631
- }
483
+ // lib/brick/utils.ts
484
+ import jsonLogic from "json-logic-js";
632
485
 
633
- class UrlFileUploadService {
634
- defaults;
635
- constructor(defaults = {}) {
636
- this.defaults = defaults;
637
- }
638
- get fetchImplementation() {
639
- return this.defaults.fetch ?? globalThis.fetch.bind(globalThis);
640
- }
641
- resolveUrl(options) {
642
- const url = options?.url ?? this.defaults.url;
643
- if (!url) {
644
- throw new Error("No upload URL configured — set it on the brick (uploadUrl) or on UrlFileUploadService");
645
- }
646
- return url;
647
- }
648
- mergedHeaders(options) {
649
- return { ...this.defaults.headers, ...options?.headers };
650
- }
651
- async upload(file, options) {
652
- const url = this.resolveUrl(options);
653
- const blob = typeof Blob !== "undefined" && file instanceof Blob ? file : new Blob([await file.arrayBuffer()], { type: file.type });
654
- const body = new FormData;
655
- body.append(this.defaults.fieldName ?? "file", blob, file.name);
656
- const response = await this.fetchImplementation(url, {
657
- method: "POST",
658
- body,
659
- headers: this.mergedHeaders(options),
660
- credentials: this.defaults.credentials
661
- });
662
- if (!response.ok) {
663
- throw new Error(`Upload failed (${response.status})`);
664
- }
665
- const payload = await response.json().catch(() => ({}));
666
- const uploadedUrl = payload.url ?? payload.path ?? payload.location ?? payload.href;
667
- return {
668
- name: file.name,
669
- type: file.type,
670
- size: file.size,
671
- url: typeof uploadedUrl === "string" ? uploadedUrl : ""
672
- };
673
- }
674
- async remove(file, options) {
675
- if (!file.url)
676
- return;
677
- const target = /^https?:\/\//.test(file.url) ? file.url : this.resolveUrl(options);
678
- const response = await this.fetchImplementation(target, {
679
- method: "DELETE",
680
- headers: {
681
- "content-type": "application/json",
682
- ...this.mergedHeaders(options)
683
- },
684
- credentials: this.defaults.credentials,
685
- body: JSON.stringify({ url: file.url, name: file.name })
686
- });
687
- if (!response.ok) {
688
- throw new Error(`Delete failed (${response.status})`);
689
- }
690
- }
691
- }
486
+ // lib/services/js_runner_service.ts
487
+ import { parse as parse2 } from "acorn";
692
488
 
693
- // lib/services/spec_source_service.ts
694
- class FetchSpecSourceService {
695
- defaults;
696
- cache = new Map;
697
- constructor(defaults = {}) {
698
- this.defaults = defaults;
699
- }
700
- resolveUrl(ref) {
701
- if (/^https?:\/\//.test(ref) || !this.defaults.baseUrl)
702
- return ref;
703
- return `${this.defaults.baseUrl.replace(/\/$/, "")}/${ref.replace(/^\//, "")}`;
704
- }
705
- fetchSpec(ref) {
706
- const url = this.resolveUrl(ref);
707
- let pending = this.cache.get(url);
708
- if (!pending) {
709
- const fetchImplementation = this.defaults.fetch ?? globalThis.fetch.bind(globalThis);
710
- pending = fetchImplementation(url, {
711
- headers: this.defaults.headers,
712
- credentials: this.defaults.credentials
713
- }).then(async (response) => {
714
- if (!response.ok) {
715
- throw new Error(`Failed to load spec "${ref}" (${response.status})`);
716
- }
717
- return await response.json();
718
- });
719
- pending.catch(() => this.cache.delete(url));
720
- this.cache.set(url, pending);
721
- }
722
- return pending;
723
- }
724
- }
489
+ // lib/services/sandbox_interpreter.ts
490
+ import { parse } from "acorn";
491
+ var FORBIDDEN_PROPS = new Set(["__proto__", "constructor", "prototype"]);
492
+ var MAX_STEPS = 1e5;
725
493
 
726
- // lib/services/option_source_service.ts
727
- class FetchOptionSourceService {
728
- defaults;
729
- cache = new Map;
730
- constructor(defaults = {}) {
731
- this.defaults = defaults;
494
+ class ReturnSignal {
495
+ value;
496
+ constructor(value) {
497
+ this.value = value;
732
498
  }
733
- resolveUrl(ref) {
734
- if (/^https?:\/\//.test(ref) || !this.defaults.baseUrl)
735
- return ref;
736
- return `${this.defaults.baseUrl.replace(/\/$/, "")}/${ref.replace(/^\//, "")}`;
499
+ }
500
+ var lookup = (ctx, name) => {
501
+ for (let i = ctx.scopes.length - 1;i >= 0; i--) {
502
+ if (ctx.scopes[i].has(name))
503
+ return ctx.scopes[i].get(name);
737
504
  }
738
- fetchOptions(ref) {
739
- const url = this.resolveUrl(ref);
740
- let pending = this.cache.get(url);
741
- if (!pending) {
742
- const fetchImplementation = this.defaults.fetch ?? globalThis.fetch.bind(globalThis);
743
- pending = fetchImplementation(url, {
744
- headers: this.defaults.headers,
745
- credentials: this.defaults.credentials
746
- }).then(async (response) => {
747
- if (!response.ok) {
748
- throw new Error(`Failed to load options "${ref}" (${response.status})`);
749
- }
750
- return await response.json();
751
- });
752
- pending.catch(() => this.cache.delete(url));
753
- this.cache.set(url, pending);
505
+ throw new Error(`Unknown identifier "${name}"`);
506
+ };
507
+ var declareVar = (ctx, name, value) => {
508
+ ctx.scopes[ctx.scopes.length - 1].set(name, value);
509
+ };
510
+ var assign = (ctx, name, value) => {
511
+ for (let i = ctx.scopes.length - 1;i >= 0; i--) {
512
+ if (ctx.scopes[i].has(name)) {
513
+ ctx.scopes[i].set(name, value);
514
+ return;
754
515
  }
755
- return pending;
756
516
  }
757
- }
758
-
759
- // lib/services/index.ts
760
- var GLOBAL_KEY = Symbol.for("formkrafter.core.services");
761
- var globalStore = globalThis;
762
- var services = globalStore[GLOBAL_KEY] ??= {
763
- jsRunnerService: new SandboxJsRunnerService,
764
- dataSourceService: new FetchDataSourceService,
765
- fileUploadService: new Base64FileUploadService,
766
- specSourceService: new FetchSpecSourceService,
767
- optionSourceService: new FetchOptionSourceService
517
+ throw new Error(`Cannot assign to unknown identifier "${name}"`);
768
518
  };
769
- services.specSourceService ??= new FetchSpecSourceService;
770
- services.optionSourceService ??= new FetchOptionSourceService;
771
-
772
- // lib/brick/utils.ts
773
- var evalBrickCode = (code, data) => {
774
- try {
775
- return services.jsRunnerService.eval(code, { dataMap: data ?? {} });
776
- } catch (error) {
777
- return error instanceof Error ? error : new Error(String(error));
519
+ var guardProp = (prop) => {
520
+ if (typeof prop === "number")
521
+ return prop;
522
+ const name = String(prop);
523
+ if (FORBIDDEN_PROPS.has(name)) {
524
+ throw new Error(`Access to "${name}" is not allowed`);
778
525
  }
526
+ return name;
779
527
  };
780
- var evalRule = (rule, dataMap) => {
781
- if (rule.type === "jsonLogic") {
782
- if (rule.logic == null)
783
- return false;
784
- return jsonLogic.apply(rule.logic, dataMap) === true;
785
- }
786
- if (rule.type === "javaScript") {
787
- if (!rule.code)
788
- return false;
789
- return evalBrickCode(rule.code, dataMap) === true;
528
+ var memberTarget = (ctx, node) => {
529
+ const object = evalNode(ctx, node.object);
530
+ const prop = node.computed ? guardProp(evalNode(ctx, node.property)) : guardProp(node.property.name);
531
+ return { object, prop, optional: node.optional === true };
532
+ };
533
+ var readMember = (ctx, node) => {
534
+ const { object, prop, optional } = memberTarget(ctx, node);
535
+ if (object === null || object === undefined) {
536
+ if (optional)
537
+ return;
538
+ throw new Error(`Cannot read "${String(prop)}" of ${String(object)}`);
790
539
  }
791
- return false;
540
+ return object[prop];
792
541
  };
793
- var getActiveRules = (rules, dataMap) => rules?.filter((rule) => evalRule(rule, dataMap));
794
- var getActiveEffect = (rules, dataMap) => getActiveRules(rules, dataMap)?.map((rule) => rule.effects).flat().filter((effect) => effect != null);
795
- var getAffectedProperties = (rules, dataMap) => {
796
- return getActiveEffect(rules, dataMap)?.map((effect) => {
797
- let value;
798
- if (effect.property) {
799
- if (effect.property.type === "boolean")
800
- value = effect.boolean;
801
- else if (effect.property.type === "logic" && effect.logic != null)
802
- value = jsonLogic.apply(effect.logic, dataMap);
803
- else if (effect.property.type === "code" && effect.code)
804
- value = evalBrickCode(effect.code, dataMap);
805
- else if (effect.property.type === "string")
806
- value = effect.string;
807
- return {
808
- [effect.property.target]: value
542
+ var evalNode = (ctx, node) => {
543
+ if (++ctx.steps > MAX_STEPS) {
544
+ throw new Error("Sandbox execution budget exceeded");
545
+ }
546
+ switch (node.type) {
547
+ case "Program":
548
+ case "BlockStatement": {
549
+ for (const statement of node.body) {
550
+ const result = evalNode(ctx, statement);
551
+ if (result instanceof ReturnSignal)
552
+ return result;
553
+ }
554
+ return;
555
+ }
556
+ case "ReturnStatement":
557
+ return new ReturnSignal(node.argument ? evalNode(ctx, node.argument) : undefined);
558
+ case "IfStatement": {
559
+ if (evalNode(ctx, node.test)) {
560
+ return evalNode(ctx, node.consequent);
561
+ }
562
+ if (node.alternate)
563
+ return evalNode(ctx, node.alternate);
564
+ return;
565
+ }
566
+ case "VariableDeclaration": {
567
+ for (const declaration of node.declarations) {
568
+ const id = declaration.id;
569
+ if (id.type !== "Identifier") {
570
+ throw new Error("Only simple variable names are supported");
571
+ }
572
+ declareVar(ctx, id.name, declaration.init ? evalNode(ctx, declaration.init) : undefined);
573
+ }
574
+ return;
575
+ }
576
+ case "ExpressionStatement":
577
+ evalNode(ctx, node.expression);
578
+ return;
579
+ case "Literal":
580
+ return node.value;
581
+ case "TemplateLiteral": {
582
+ const quasis = node.quasis;
583
+ const expressions = node.expressions;
584
+ let text = "";
585
+ quasis.forEach((quasi, index) => {
586
+ text += quasi.value.cooked ?? "";
587
+ if (index < expressions.length) {
588
+ text += String(evalNode(ctx, expressions[index]));
589
+ }
590
+ });
591
+ return text;
592
+ }
593
+ case "Identifier":
594
+ return lookup(ctx, node.name);
595
+ case "ThisExpression":
596
+ return;
597
+ case "ChainExpression":
598
+ return evalNode(ctx, node.expression);
599
+ case "MemberExpression":
600
+ return readMember(ctx, node);
601
+ case "CallExpression": {
602
+ const callee = node.callee;
603
+ const args = [];
604
+ for (const argument of node.arguments) {
605
+ if (argument.type === "SpreadElement") {
606
+ const spread = evalNode(ctx, argument.argument);
607
+ if (Array.isArray(spread))
608
+ args.push(...spread);
609
+ } else {
610
+ args.push(evalNode(ctx, argument));
611
+ }
612
+ }
613
+ if (callee.type === "MemberExpression") {
614
+ const { object, prop, optional } = memberTarget(ctx, callee);
615
+ if (object === null || object === undefined) {
616
+ if (optional || node.optional === true)
617
+ return;
618
+ throw new Error(`Cannot call "${String(prop)}" of ${String(object)}`);
619
+ }
620
+ const fn3 = object[prop];
621
+ if (typeof fn3 !== "function") {
622
+ if (node.optional === true && fn3 == null)
623
+ return;
624
+ throw new Error(`"${String(prop)}" is not a function`);
625
+ }
626
+ if (fn3 === Function || fn3 === eval) {
627
+ throw new Error("This function is not allowed");
628
+ }
629
+ return fn3.apply(object, args);
630
+ }
631
+ const fn2 = evalNode(ctx, callee);
632
+ if (typeof fn2 !== "function") {
633
+ if (node.optional === true && fn2 == null)
634
+ return;
635
+ throw new Error("Value is not a function");
636
+ }
637
+ if (fn2 === Function || fn2 === eval) {
638
+ throw new Error("This function is not allowed");
639
+ }
640
+ return fn2(...args);
641
+ }
642
+ case "ArrowFunctionExpression": {
643
+ const params = node.params;
644
+ const body = node.body;
645
+ const capturedScopes = [...ctx.scopes];
646
+ return (...args) => {
647
+ const callScope = new Map;
648
+ params.forEach((param, index) => {
649
+ if (param.type !== "Identifier") {
650
+ throw new Error("Only simple parameters are supported");
651
+ }
652
+ callScope.set(param.name, args[index]);
653
+ });
654
+ const callCtx = {
655
+ steps: ctx.steps,
656
+ scopes: [...capturedScopes, callScope]
657
+ };
658
+ const result = evalNode(callCtx, body);
659
+ ctx.steps = callCtx.steps;
660
+ if (result instanceof ReturnSignal)
661
+ return result.value;
662
+ return node.expression === true ? result : undefined;
809
663
  };
810
664
  }
811
- return {};
812
- }).flat().reduce((acc, item) => ({ ...acc, ...item }), {}) ?? { hidden: false };
813
- };
814
- // lib/validators/validator.ts
815
- import { Ajv } from "ajv";
816
- import ajvErrors from "ajv-errors";
817
- import addFormats from "ajv-formats";
818
- var ajv = new Ajv({ allErrors: true });
819
- addFormats(ajv, ["email", "uri"]);
820
- ajvErrors(ajv);
821
- var validatorCache = new WeakMap;
822
- var compiledValidator = (brickSpec, locale) => {
823
- let byLocale = validatorCache.get(brickSpec);
824
- if (!byLocale) {
825
- byLocale = new Map;
826
- validatorCache.set(brickSpec, byLocale);
827
- }
828
- const cacheKey = locale ?? "";
829
- let validate = byLocale.get(cacheKey);
830
- if (!validate) {
831
- const schema = buildValidationSchema(brickSpec, locale);
832
- if (!schema)
833
- return;
834
- try {
835
- validate = ajv.compile(schema);
836
- } catch {
837
- return;
665
+ case "BinaryExpression": {
666
+ const left = evalNode(ctx, node.left);
667
+ const right = evalNode(ctx, node.right);
668
+ switch (node.operator) {
669
+ case "===":
670
+ return left === right;
671
+ case "!==":
672
+ return left !== right;
673
+ case "==":
674
+ return left == right;
675
+ case "!=":
676
+ return left != right;
677
+ case "<":
678
+ return left < right;
679
+ case "<=":
680
+ return left <= right;
681
+ case ">":
682
+ return left > right;
683
+ case ">=":
684
+ return left >= right;
685
+ case "+":
686
+ return left + right;
687
+ case "-":
688
+ return left - right;
689
+ case "*":
690
+ return left * right;
691
+ case "/":
692
+ return left / right;
693
+ case "%":
694
+ return left % right;
695
+ case "**":
696
+ return left ** right;
697
+ default:
698
+ throw new Error(`Unsupported operator "${String(node.operator)}"`);
699
+ }
700
+ }
701
+ case "LogicalExpression": {
702
+ const left = evalNode(ctx, node.left);
703
+ switch (node.operator) {
704
+ case "&&":
705
+ return left && evalNode(ctx, node.right);
706
+ case "||":
707
+ return left || evalNode(ctx, node.right);
708
+ case "??":
709
+ return left ?? evalNode(ctx, node.right);
710
+ default:
711
+ throw new Error(`Unsupported operator "${String(node.operator)}"`);
712
+ }
838
713
  }
839
- byLocale.set(cacheKey, validate);
840
- }
841
- return validate;
842
- };
843
- var customErrors = (brickSpec, data, locale) => {
844
- const errors = {};
845
- const dataMap = data ?? {};
846
- for (const brick of iterateSchemaBricks(brickSpec)) {
847
- const key = brick.configs?.key;
848
- if (!key)
849
- continue;
850
- for (const validation2 of brick.validations ?? []) {
851
- if (validation2.validator !== "custom" || !validation2.customValidator) {
852
- continue;
714
+ case "UnaryExpression": {
715
+ const argument = evalNode(ctx, node.argument);
716
+ switch (node.operator) {
717
+ case "!":
718
+ return !argument;
719
+ case "-":
720
+ return -argument;
721
+ case "+":
722
+ return +argument;
723
+ case "typeof":
724
+ return typeof argument;
725
+ default:
726
+ throw new Error(`Unsupported operator "${String(node.operator)}"`);
853
727
  }
854
- const result = evalBrickCode(`const value = dataMap?.[${JSON.stringify(key)}];
855
- ${validation2.customValidator}`, dataMap);
856
- if (result === true || result === undefined || result === null)
857
- continue;
858
- if (key in errors)
859
- continue;
860
- if (typeof result === "string")
861
- errors[key] = result;
862
- else if (result instanceof Error)
863
- errors[key] = result.message;
864
- else {
865
- const resolved = resolveLocalizedText(validation2.message, locale);
866
- errors[key] = typeof resolved === "string" ? resolved : defaultValidationMessage("custom", locale);
728
+ }
729
+ case "ConditionalExpression":
730
+ return evalNode(ctx, node.test) ? evalNode(ctx, node.consequent) : evalNode(ctx, node.alternate);
731
+ case "AssignmentExpression": {
732
+ const target = node.left;
733
+ if (target.type !== "Identifier" || node.operator !== "=") {
734
+ throw new Error("Only simple assignments are supported");
867
735
  }
736
+ const value = evalNode(ctx, node.right);
737
+ assign(ctx, target.name, value);
738
+ return value;
868
739
  }
869
- }
870
- return errors;
871
- };
872
- var keyOfError = (error) => {
873
- if (error.keyword === "required") {
874
- return error.params.missingProperty;
875
- }
876
- if (error.keyword === "errorMessage") {
877
- const inner = error.params.errors?.[0];
878
- return inner ? keyOfError(inner) : undefined;
879
- }
880
- return error.instancePath.split("/").filter(Boolean)[0];
881
- };
882
- var hiddenKeys = (brickSpec, dataMap) => {
883
- const hidden = new Set;
884
- const walk = (brick, parentHidden) => {
885
- const isHidden = parentHidden || (brick.rules?.length ? getAffectedProperties(brick.rules, dataMap).hidden === true : false);
886
- const key = brick.configs?.key;
887
- if (isHidden && key)
888
- hidden.add(key);
889
- if (brick.type === "collection")
890
- return;
891
- for (const child of brick.children ?? [])
892
- walk(child, isHidden);
893
- };
894
- walk(brickSpec, false);
895
- return hidden;
896
- };
897
- var validateBrickSpecDataDetailed = (brickSpec, data, locale) => {
898
- const errors = {};
899
- const validate = compiledValidator(brickSpec, locale);
900
- if (validate && !validate(data)) {
901
- for (const error of validate.errors ?? []) {
902
- const key = keyOfError(error);
903
- if (key && !(key in errors) && error.message) {
904
- errors[key] = error.message;
740
+ case "ArrayExpression": {
741
+ const values = [];
742
+ for (const element of node.elements) {
743
+ if (!element)
744
+ continue;
745
+ if (element.type === "SpreadElement") {
746
+ const spread = evalNode(ctx, element.argument);
747
+ if (Array.isArray(spread))
748
+ values.push(...spread);
749
+ } else {
750
+ values.push(evalNode(ctx, element));
751
+ }
905
752
  }
753
+ return values;
906
754
  }
907
- }
908
- for (const [key, message] of Object.entries(customErrors(brickSpec, data, locale))) {
909
- if (!(key in errors))
910
- errors[key] = message;
911
- }
912
- const dataMap = typeof data === "object" && data !== null ? data : undefined;
913
- for (const key of hiddenKeys(brickSpec, dataMap)) {
914
- delete errors[key];
915
- }
916
- return { valid: Object.keys(errors).length === 0, errors };
917
- };
918
- var validateBrickSpecData = (brickSpec, data, locale) => validateBrickSpecDataDetailed(brickSpec, data, locale).valid;
919
- var stripEmpty = (data) => {
920
- if (typeof data !== "object" || data === null)
921
- return {};
922
- return Object.fromEntries(Object.entries(data).filter(([, value]) => value !== "" && value !== null && value !== undefined));
923
- };
924
- var rowSpecCache = new WeakMap;
925
- var rowSpecOf = (collection) => {
926
- let rowSpec = rowSpecCache.get(collection);
927
- if (!rowSpec) {
928
- rowSpec = {
929
- type: "panel",
930
- id: "collection-row",
931
- name: "Row",
932
- configs: { key: `${collection.configs?.key ?? "collection"}_row` },
933
- children: collection.children ?? []
934
- };
935
- rowSpecCache.set(collection, rowSpec);
936
- }
937
- return rowSpec;
938
- };
939
- var validateFormData = (brickSpec, data, locale) => {
940
- const present = stripEmpty(data);
941
- const errors = {
942
- ...validateBrickSpecDataDetailed(brickSpec, present, locale).errors
943
- };
944
- for (const brick of iterateSchemaBricks(brickSpec)) {
945
- if (brick.type !== "collection")
946
- continue;
947
- const key = brick.configs?.key;
948
- if (!key)
949
- continue;
950
- const rows = present[key];
951
- if (!Array.isArray(rows))
952
- continue;
953
- const rowSpec = rowSpecOf(brick);
954
- rows.forEach((row, index) => {
955
- const rowResult = validateFormData(rowSpec, row, locale);
956
- for (const [field, message] of Object.entries(rowResult.errors)) {
957
- const prefixed = `${key}[${index}].${field}`;
958
- if (!(prefixed in errors))
959
- errors[prefixed] = message;
755
+ case "ObjectExpression": {
756
+ const result = {};
757
+ for (const property of node.properties) {
758
+ if (property.type === "SpreadElement") {
759
+ const spread = evalNode(ctx, property.argument);
760
+ if (spread && typeof spread === "object") {
761
+ for (const [key2, value] of Object.entries(spread)) {
762
+ result[String(guardProp(key2))] = value;
763
+ }
764
+ }
765
+ continue;
766
+ }
767
+ const keyNode = property.key;
768
+ const key = property.computed ? guardProp(evalNode(ctx, keyNode)) : guardProp(keyNode.type === "Identifier" ? keyNode.name : keyNode.value);
769
+ result[String(key)] = evalNode(ctx, property.value);
960
770
  }
961
- });
771
+ return result;
772
+ }
773
+ default:
774
+ throw new Error(`Unsupported syntax: ${node.type}`);
962
775
  }
963
- return { valid: Object.keys(errors).length === 0, errors };
964
776
  };
965
- // lib/services/unsafe_js_runner_service.ts
966
- import { parse as parse3 } from "acorn";
777
+ var SAFE_BUILTINS = {
778
+ undefined: undefined,
779
+ NaN: NaN,
780
+ Infinity: Infinity,
781
+ Math,
782
+ JSON,
783
+ Number,
784
+ String,
785
+ Boolean,
786
+ Array,
787
+ Object,
788
+ Date,
789
+ parseInt,
790
+ parseFloat,
791
+ isNaN,
792
+ isFinite
793
+ };
794
+ function runSandboxed(code, globals = {}) {
795
+ const ast = parse(code, {
796
+ ecmaVersion: "latest",
797
+ allowReturnOutsideFunction: true
798
+ });
799
+ const ctx = {
800
+ steps: 0,
801
+ scopes: [
802
+ new Map(Object.entries({ ...SAFE_BUILTINS, ...globals })),
803
+ new Map
804
+ ]
805
+ };
806
+ const result = evalNode(ctx, ast);
807
+ return result instanceof ReturnSignal ? result.value : undefined;
808
+ }
967
809
 
968
- class UnsafeEvalJsRunnerService {
810
+ // lib/services/js_runner_service.ts
811
+ class SandboxJsRunnerService {
969
812
  validateJs(js2, options = { ecmaVersion: "latest", allowReturnOutsideFunction: true }) {
970
813
  try {
971
- parse3(js2, options);
814
+ parse2(js2, options);
972
815
  return { valid: true };
973
816
  } catch (error) {
974
817
  return {
@@ -977,301 +820,550 @@ class UnsafeEvalJsRunnerService {
977
820
  };
978
821
  }
979
822
  }
980
- eval(js, scope = {}) {
981
- const prelude = Object.entries(scope).map(([name, value]) => `const ${name} = ${JSON.stringify(value)};`).join(`
982
- `);
983
- const fn = `(() => {${prelude}
984
- ${js}})();`;
985
- const validation = this.validateJs(fn, { ecmaVersion: "latest" });
986
- if (!validation.valid)
987
- throw validation.error;
988
- return eval(fn);
823
+ eval(js2, scope2 = {}) {
824
+ return runSandboxed(js2, scope2);
989
825
  }
990
826
  }
991
- // lib/i18n.ts
992
- var en = {
993
- "palette.search": "Search bricks…",
994
- "palette.empty": "No brick matches “{query}”",
995
- "builder.undo": "Undo",
996
- "builder.redo": "Redo",
997
- "builder.editLocale": "Editing language",
998
- "builder.copyJson": "Copy JSON",
999
- "builder.importJson": "Import JSON",
1000
- "builder.importInvalid": "Invalid form JSON",
1001
- "builder.pasteHint": "Press Ctrl+V to paste a spec",
1002
- "builder.copied": "Copied!",
1003
- "builder.downloadJson": "Download .json",
1004
- "builder.hint": "Select a brick to edit its properties",
1005
- "drop.hint": "Drop a brick here",
1006
- "empty.title": "Your form is empty",
1007
- "empty.hint": "Drag a brick from the palette to get started",
1008
- "actions.move": "Move",
1009
- "actions.duplicate": "Duplicate",
1010
- "actions.delete": "Delete",
1011
- "stepper.back": "← Back",
1012
- "stepper.next": "Next →",
1013
- "stepper.submit": "Submit",
1014
- "panel.cfg.validateSteps": "Validate each step",
1015
- "panel.cfg.allowStepClick": "Allow clicking steps to jump",
1016
- "panel.cfg.showSubmit": "Submit button on last step",
1017
- "panel.cfg.validateTabs": "Validate tab before leaving it",
1018
- "signature.clear": "Clear",
1019
- "panel.tab.config": "Config",
1020
- "panel.tab.validation": "Validation",
1021
- "panel.tab.styles": "Styles",
1022
- "panel.tab.rules": "Rules",
1023
- "panel.key": "Key",
1024
- "panel.label": "Label",
1025
- "panel.placeholder": "Placeholder",
1026
- "panel.prefix": "Prefix",
1027
- "panel.suffix": "Suffix",
1028
- "panel.accept": "Accepted file types",
1029
- "panel.mask": "Input mask (9=digit, a=letter, *=any)",
1030
- "panel.uploadUrl": "Upload URL",
1031
- "panel.cfg.multiple": "Allow multiple files",
1032
- "panel.optionsSource": "Options source",
1033
- "panel.options": "Options (one per line)",
1034
- "panel.content": "Content",
1035
- "panel.dataKey": "Data key",
1036
- "panel.url": "URL",
1037
- "panel.headers": "Headers ({token} reads form data)",
1038
- "panel.searchParam": "Server search param",
1039
- "panel.labelKey": "Label key",
1040
- "panel.valueKey": "Value key",
1041
- "panel.errorMessage": "Error message",
1042
- "panel.required": "Required",
1043
- "panel.minLength": "Min length",
1044
- "panel.maxLength": "Max length",
1045
- "panel.min": "Min",
1046
- "panel.max": "Max",
1047
- "panel.pattern": "Pattern (regex)",
1048
- "panel.minItems": "Min items",
1049
- "panel.maxItems": "Max items",
1050
- "panel.emailFormat": "Email format",
1051
- "panel.urlFormat": "URL format",
1052
- "panel.custom": "Custom (JavaScript)",
1053
- "panel.fallbackMessage": "Fallback message",
1054
- "panel.styleProperty": "property",
1055
- "panel.styleValue": "value",
1056
- "file.choose": "Choose a file…",
1057
- "file.uploading": "Uploading…",
1058
- "file.remove": "Remove file",
1059
- "file.addAnother": "+ Add a file",
1060
- "address.street": "Street",
1061
- "address.city": "City",
1062
- "address.zip": "Postal code",
1063
- "address.country": "Country",
1064
- "grid.addRow": "+ Add row",
1065
- "grid.moveUp": "Move row up",
1066
- "grid.moveDown": "Move row down",
1067
- "grid.removeRow": "Remove row",
1068
- "grid.empty": "No rows yet",
1069
- "grid.columns": "Row template",
1070
- "select.search": "Search…",
1071
- "select.empty": "No options",
1072
- "select.clear": "Clear",
1073
- "rules.when": "When",
1074
- "rules.then": "Then",
1075
- "rules.addRule": "+ Add rule",
1076
- "rules.addEffect": "+ Effect",
1077
- "rules.removeRule": "Remove rule",
1078
- "rules.removeEffect": "Remove effect",
1079
- "rules.mode.simple": "simple",
1080
- "rules.mode.json": "json",
1081
- "rules.mode.js": "JS",
1082
- "category.Inputs": "Inputs",
1083
- "category.Layout": "Layout",
1084
- "category.Data": "Data",
1085
- "brick.text.name": "Text",
1086
- "brick.email.name": "Email",
1087
- "brick.password.name": "Password",
1088
- "brick.url.name": "URL",
1089
- "brick.phone.name": "Phone",
1090
- "brick.textarea.name": "Text area",
1091
- "brick.number.name": "Number",
1092
- "brick.date.name": "Date",
1093
- "brick.time.name": "Time",
1094
- "brick.datetime.name": "Date / Time",
1095
- "brick.select.name": "Select",
1096
- "brick.multi-select.name": "Multi select",
1097
- "brick.radio.name": "Radio",
1098
- "brick.select-boxes.name": "Select boxes",
1099
- "brick.checkbox.name": "Checkbox",
1100
- "brick.tags.name": "Tags",
1101
- "brick.signature.name": "Signature",
1102
- "brick.data-grid.name": "Data grid",
1103
- "brick.address.name": "Address",
1104
- "brick.file.name": "File",
1105
- "brick.hidden.name": "Hidden",
1106
- "brick.content.name": "Content",
1107
- "brick.recap.name": "Recap",
1108
- "brick.nested-form.name": "Nested form",
1109
- "brick.code.name": "Code",
1110
- "nestedForm.ref": "Referenced form",
1111
- "nestedForm.missing": "Set the specRef config to reference a form.",
1112
- "nestedForm.loading": "Loading nested forms…",
1113
- "panel.specRef": "Referenced form (specRef)",
1114
- "panel.optionsRef": "Options catalog (optionsRef)",
1115
- "recap.empty": "Nothing to summarize yet.",
1116
- "recap.yes": "Yes",
1117
- "recap.no": "No",
1118
- "panel.cfg.showEmpty": "Show empty fields",
1119
- "panel.cfg.groupBySections": "Group by section",
1120
- "brick.group.name": "Group",
1121
- "brick.row.name": "Row",
1122
- "brick.column.name": "Column",
1123
- "brick.stepper.name": "Stepper",
1124
- "brick.tabs.name": "Tabs"
827
+
828
+ // lib/services/data_source_service.ts
829
+ class FetchDataSourceService {
830
+ defaults;
831
+ cache = new Map;
832
+ constructor(defaults = {}) {
833
+ this.defaults = defaults;
834
+ }
835
+ fetchOptions(url, options) {
836
+ const headers = { ...this.defaults.headers, ...options?.headers };
837
+ const init = {};
838
+ if (Object.keys(headers).length)
839
+ init.headers = headers;
840
+ if (this.defaults.credentials)
841
+ init.credentials = this.defaults.credentials;
842
+ const key = `${url}|${JSON.stringify(init)}`;
843
+ let pending = this.cache.get(key);
844
+ if (!pending) {
845
+ pending = fetch(url, Object.keys(init).length ? init : undefined).then((response) => {
846
+ if (!response.ok) {
847
+ throw new Error(`Data source request failed (${response.status}): ${url}`);
848
+ }
849
+ return response.json();
850
+ }).then((json) => Array.isArray(json) ? json : []).catch((error) => {
851
+ this.cache.delete(key);
852
+ throw error;
853
+ });
854
+ this.cache.set(key, pending);
855
+ }
856
+ return pending;
857
+ }
858
+ }
859
+
860
+ // lib/services/file_upload_service.ts
861
+ class Base64FileUploadService {
862
+ async upload(file) {
863
+ const encode = globalThis.btoa;
864
+ if (!encode)
865
+ throw new Error("No base64 encoder available");
866
+ const bytes = new Uint8Array(await file.arrayBuffer());
867
+ let binary = "";
868
+ for (const byte of bytes)
869
+ binary += String.fromCharCode(byte);
870
+ return {
871
+ name: file.name,
872
+ type: file.type,
873
+ size: file.size,
874
+ url: `data:${file.type};base64,${encode(binary)}`
875
+ };
876
+ }
877
+ }
878
+
879
+ class UrlFileUploadService {
880
+ defaults;
881
+ constructor(defaults = {}) {
882
+ this.defaults = defaults;
883
+ }
884
+ get fetchImplementation() {
885
+ return this.defaults.fetch ?? globalThis.fetch.bind(globalThis);
886
+ }
887
+ resolveUrl(options) {
888
+ const url = options?.url ?? this.defaults.url;
889
+ if (!url) {
890
+ throw new Error("No upload URL configured — set it on the brick (uploadUrl) or on UrlFileUploadService");
891
+ }
892
+ return url;
893
+ }
894
+ mergedHeaders(options) {
895
+ return { ...this.defaults.headers, ...options?.headers };
896
+ }
897
+ async upload(file, options) {
898
+ const url = this.resolveUrl(options);
899
+ const blob = typeof Blob !== "undefined" && file instanceof Blob ? file : new Blob([await file.arrayBuffer()], { type: file.type });
900
+ const body = new FormData;
901
+ body.append(this.defaults.fieldName ?? "file", blob, file.name);
902
+ const response = await this.fetchImplementation(url, {
903
+ method: "POST",
904
+ body,
905
+ headers: this.mergedHeaders(options),
906
+ credentials: this.defaults.credentials
907
+ });
908
+ if (!response.ok) {
909
+ throw new Error(`Upload failed (${response.status})`);
910
+ }
911
+ const payload = await response.json().catch(() => ({}));
912
+ const uploadedUrl = payload.url ?? payload.path ?? payload.location ?? payload.href;
913
+ return {
914
+ name: file.name,
915
+ type: file.type,
916
+ size: file.size,
917
+ url: typeof uploadedUrl === "string" ? uploadedUrl : ""
918
+ };
919
+ }
920
+ async remove(file, options) {
921
+ if (!file.url)
922
+ return;
923
+ const target = /^https?:\/\//.test(file.url) ? file.url : this.resolveUrl(options);
924
+ const response = await this.fetchImplementation(target, {
925
+ method: "DELETE",
926
+ headers: {
927
+ "content-type": "application/json",
928
+ ...this.mergedHeaders(options)
929
+ },
930
+ credentials: this.defaults.credentials,
931
+ body: JSON.stringify({ url: file.url, name: file.name })
932
+ });
933
+ if (!response.ok) {
934
+ throw new Error(`Delete failed (${response.status})`);
935
+ }
936
+ }
937
+ }
938
+
939
+ // lib/services/spec_source_service.ts
940
+ class FetchSpecSourceService {
941
+ defaults;
942
+ cache = new Map;
943
+ constructor(defaults = {}) {
944
+ this.defaults = defaults;
945
+ }
946
+ resolveUrl(ref) {
947
+ if (/^https?:\/\//.test(ref) || !this.defaults.baseUrl)
948
+ return ref;
949
+ return `${this.defaults.baseUrl.replace(/\/$/, "")}/${ref.replace(/^\//, "")}`;
950
+ }
951
+ fetchSpec(ref) {
952
+ const url = this.resolveUrl(ref);
953
+ let pending = this.cache.get(url);
954
+ if (!pending) {
955
+ const fetchImplementation = this.defaults.fetch ?? globalThis.fetch.bind(globalThis);
956
+ pending = fetchImplementation(url, {
957
+ headers: this.defaults.headers,
958
+ credentials: this.defaults.credentials
959
+ }).then(async (response) => {
960
+ if (!response.ok) {
961
+ throw new Error(`Failed to load spec "${ref}" (${response.status})`);
962
+ }
963
+ return await response.json();
964
+ });
965
+ pending.catch(() => this.cache.delete(url));
966
+ this.cache.set(url, pending);
967
+ }
968
+ return pending;
969
+ }
970
+ }
971
+
972
+ // lib/services/option_source_service.ts
973
+ class FetchOptionSourceService {
974
+ defaults;
975
+ cache = new Map;
976
+ constructor(defaults = {}) {
977
+ this.defaults = defaults;
978
+ }
979
+ resolveUrl(ref) {
980
+ if (/^https?:\/\//.test(ref) || !this.defaults.baseUrl)
981
+ return ref;
982
+ return `${this.defaults.baseUrl.replace(/\/$/, "")}/${ref.replace(/^\//, "")}`;
983
+ }
984
+ fetchOptions(ref) {
985
+ const url = this.resolveUrl(ref);
986
+ let pending = this.cache.get(url);
987
+ if (!pending) {
988
+ const fetchImplementation = this.defaults.fetch ?? globalThis.fetch.bind(globalThis);
989
+ pending = fetchImplementation(url, {
990
+ headers: this.defaults.headers,
991
+ credentials: this.defaults.credentials
992
+ }).then(async (response) => {
993
+ if (!response.ok) {
994
+ throw new Error(`Failed to load options "${ref}" (${response.status})`);
995
+ }
996
+ return await response.json();
997
+ });
998
+ pending.catch(() => this.cache.delete(url));
999
+ this.cache.set(url, pending);
1000
+ }
1001
+ return pending;
1002
+ }
1003
+ }
1004
+
1005
+ // lib/services/index.ts
1006
+ var GLOBAL_KEY2 = Symbol.for("formkrafter.core.services");
1007
+ var globalStore2 = globalThis;
1008
+ var services = globalStore2[GLOBAL_KEY2] ??= {
1009
+ jsRunnerService: new SandboxJsRunnerService,
1010
+ dataSourceService: new FetchDataSourceService,
1011
+ fileUploadService: new Base64FileUploadService,
1012
+ specSourceService: new FetchSpecSourceService,
1013
+ optionSourceService: new FetchOptionSourceService
1125
1014
  };
1126
- var frFkTranslations = {
1127
- "palette.search": "Rechercher une brique…",
1128
- "palette.empty": "Aucune brique ne correspond à « {query} »",
1129
- "builder.undo": "Annuler",
1130
- "builder.editLocale": "Langue d'édition",
1131
- "builder.redo": "Rétablir",
1132
- "builder.copyJson": "Copier le JSON",
1133
- "builder.importJson": "Importer le JSON",
1134
- "builder.importInvalid": "JSON de formulaire invalide",
1135
- "builder.pasteHint": "Collez avec Ctrl+V pour importer une spec",
1136
- "builder.copied": "Copié !",
1137
- "builder.downloadJson": "Télécharger le .json",
1138
- "builder.hint": "Sélectionnez une brique pour éditer ses propriétés",
1139
- "drop.hint": "Déposez une brique ici",
1140
- "empty.title": "Votre formulaire est vide",
1141
- "empty.hint": "Glissez une brique depuis la palette pour commencer",
1142
- "actions.move": "Déplacer",
1143
- "actions.duplicate": "Dupliquer",
1144
- "actions.delete": "Supprimer",
1145
- "stepper.back": "← Précédent",
1146
- "stepper.next": "Suivant →",
1147
- "stepper.submit": "Envoyer",
1148
- "panel.cfg.validateSteps": "Valider chaque étape",
1149
- "panel.cfg.allowStepClick": "Autoriser le saut d’étapes au clic",
1150
- "panel.cfg.showSubmit": "Bouton d’envoi à la dernière étape",
1151
- "panel.cfg.validateTabs": "Valider l’onglet avant de le quitter",
1152
- "signature.clear": "Effacer",
1153
- "panel.tab.config": "Config",
1154
- "panel.tab.validation": "Validation",
1155
- "panel.tab.styles": "Styles",
1156
- "panel.tab.rules": "Règles",
1157
- "panel.key": "Clé",
1158
- "panel.label": "Libellé",
1159
- "panel.placeholder": "Placeholder",
1160
- "panel.prefix": "Préfixe",
1161
- "panel.suffix": "Suffixe",
1162
- "panel.accept": "Types de fichiers acceptés",
1163
- "panel.mask": "Masque de saisie (9=chiffre, a=lettre, *=tout)",
1164
- "panel.uploadUrl": "URL d'upload",
1165
- "panel.cfg.multiple": "Autoriser plusieurs fichiers",
1166
- "panel.optionsSource": "Source des options",
1167
- "panel.options": "Options (une par ligne)",
1168
- "panel.content": "Contenu",
1169
- "panel.dataKey": "Clé de données",
1170
- "panel.url": "URL",
1171
- "panel.headers": "En-têtes ({token} lit les données du formulaire)",
1172
- "panel.searchParam": "Paramètre de recherche serveur",
1173
- "panel.labelKey": "Clé du libellé",
1174
- "panel.valueKey": "Clé de la valeur",
1175
- "panel.errorMessage": "Message d'erreur",
1176
- "panel.required": "Obligatoire",
1177
- "panel.minLength": "Longueur min",
1178
- "panel.maxLength": "Longueur max",
1179
- "panel.min": "Min",
1180
- "panel.max": "Max",
1181
- "panel.pattern": "Motif (regex)",
1182
- "panel.minItems": "Éléments min",
1183
- "panel.maxItems": "Éléments max",
1184
- "panel.emailFormat": "Format email",
1185
- "panel.urlFormat": "Format URL",
1186
- "panel.custom": "Personnalisé (JavaScript)",
1187
- "panel.fallbackMessage": "Message par défaut",
1188
- "panel.styleProperty": "propriété",
1189
- "panel.styleValue": "valeur",
1190
- "file.choose": "Choisir un fichier…",
1191
- "file.uploading": "Envoi en cours…",
1192
- "file.remove": "Retirer le fichier",
1193
- "file.addAnother": "+ Ajouter un fichier",
1194
- "address.street": "Rue",
1195
- "address.city": "Ville",
1196
- "address.zip": "Code postal",
1197
- "address.country": "Pays",
1198
- "grid.addRow": "+ Ajouter une ligne",
1199
- "grid.moveUp": "Monter la ligne",
1200
- "grid.moveDown": "Descendre la ligne",
1201
- "grid.removeRow": "Supprimer la ligne",
1202
- "grid.empty": "Aucune ligne",
1203
- "grid.columns": "Modèle de ligne",
1204
- "select.search": "Rechercher…",
1205
- "select.empty": "Aucune option",
1206
- "select.clear": "Effacer",
1207
- "rules.when": "Quand",
1208
- "rules.then": "Alors",
1209
- "rules.addRule": "+ Ajouter une règle",
1210
- "rules.addEffect": "+ Effet",
1211
- "rules.removeRule": "Supprimer la règle",
1212
- "rules.removeEffect": "Supprimer l'effet",
1213
- "rules.mode.simple": "simple",
1214
- "rules.mode.json": "json",
1215
- "rules.mode.js": "JS",
1216
- "category.Inputs": "Champs",
1217
- "category.Layout": "Mise en page",
1218
- "category.Data": "Données",
1219
- "brick.text.name": "Texte",
1220
- "brick.email.name": "Email",
1221
- "brick.password.name": "Mot de passe",
1222
- "brick.url.name": "URL",
1223
- "brick.phone.name": "Téléphone",
1224
- "brick.textarea.name": "Zone de texte",
1225
- "brick.number.name": "Nombre",
1226
- "brick.date.name": "Date",
1227
- "brick.time.name": "Heure",
1228
- "brick.datetime.name": "Date / Heure",
1229
- "brick.select.name": "Liste déroulante",
1230
- "brick.multi-select.name": "Sélection multiple",
1231
- "brick.radio.name": "Boutons radio",
1232
- "brick.select-boxes.name": "Cases à cocher",
1233
- "brick.checkbox.name": "Case à cocher",
1234
- "brick.tags.name": "Étiquettes",
1235
- "brick.signature.name": "Signature",
1236
- "brick.data-grid.name": "Grille de données",
1237
- "brick.address.name": "Adresse",
1238
- "brick.file.name": "Fichier",
1239
- "brick.hidden.name": "Caché",
1240
- "brick.content.name": "Contenu",
1241
- "brick.recap.name": "Récapitulatif",
1242
- "brick.nested-form.name": "Formulaire imbriqué",
1243
- "brick.code.name": "Code",
1244
- "nestedForm.ref": "Formulaire référencé",
1245
- "nestedForm.missing": "Renseignez la config specRef pour référencer un formulaire.",
1246
- "nestedForm.loading": "Chargement des formulaires imbriqués…",
1247
- "panel.specRef": "Formulaire référencé (specRef)",
1248
- "panel.optionsRef": "Catalogue d'options (optionsRef)",
1249
- "recap.empty": "Rien à récapituler pour le moment.",
1250
- "recap.yes": "Oui",
1251
- "recap.no": "Non",
1252
- "panel.cfg.showEmpty": "Afficher les champs vides",
1253
- "panel.cfg.groupBySections": "Regrouper par section",
1254
- "brick.group.name": "Groupe",
1255
- "brick.row.name": "Ligne",
1256
- "brick.column.name": "Colonne",
1257
- "brick.stepper.name": "Étapes",
1258
- "brick.tabs.name": "Onglets"
1015
+ services.specSourceService ??= new FetchSpecSourceService;
1016
+ services.optionSourceService ??= new FetchOptionSourceService;
1017
+
1018
+ // lib/brick/utils.ts
1019
+ var evalBrickCode = (code, data) => {
1020
+ try {
1021
+ return services.jsRunnerService.eval(code, { dataMap: data ?? {} });
1022
+ } catch (error) {
1023
+ return error instanceof Error ? error : new Error(String(error));
1024
+ }
1025
+ };
1026
+ var evalRule = (rule, dataMap) => {
1027
+ if (rule.type === "jsonLogic") {
1028
+ if (rule.logic == null)
1029
+ return false;
1030
+ return jsonLogic.apply(rule.logic, dataMap) === true;
1031
+ }
1032
+ if (rule.type === "javaScript") {
1033
+ if (!rule.code)
1034
+ return false;
1035
+ return evalBrickCode(rule.code, dataMap) === true;
1036
+ }
1037
+ return false;
1038
+ };
1039
+ var getActiveRules = (rules, dataMap) => rules?.filter((rule) => evalRule(rule, dataMap));
1040
+ var getActiveEffect = (rules, dataMap) => getActiveRules(rules, dataMap)?.map((rule) => rule.effects).flat().filter((effect) => effect != null);
1041
+ var getAffectedProperties = (rules, dataMap) => {
1042
+ return getActiveEffect(rules, dataMap)?.map((effect) => {
1043
+ let value;
1044
+ if (effect.property) {
1045
+ if (effect.property.type === "boolean")
1046
+ value = effect.boolean;
1047
+ else if (effect.property.type === "logic" && effect.logic != null)
1048
+ value = jsonLogic.apply(effect.logic, dataMap);
1049
+ else if (effect.property.type === "code" && effect.code)
1050
+ value = evalBrickCode(effect.code, dataMap);
1051
+ else if (effect.property.type === "string")
1052
+ value = effect.string;
1053
+ return {
1054
+ [effect.property.target]: value
1055
+ };
1056
+ }
1057
+ return {};
1058
+ }).flat().reduce((acc, item) => ({ ...acc, ...item }), {}) ?? { hidden: false };
1259
1059
  };
1260
- var GLOBAL_KEY2 = Symbol.for("formkrafter.i18n");
1261
- var globalStore2 = globalThis;
1262
- var state = globalStore2[GLOBAL_KEY2] ??= { current: { ...en } };
1263
- function setFkTranslations(overrides = {}) {
1264
- state.current = { ...en, ...overrides };
1265
- }
1266
- function fkT(key, params) {
1267
- let text = state.current[key] ?? key;
1268
- for (const [name, value] of Object.entries(params ?? {})) {
1269
- text = text.replace(`{${name}}`, String(value));
1060
+
1061
+ // lib/utils/recap.ts
1062
+ var formatRecapValue = (brick, value) => {
1063
+ if (Array.isArray(value)) {
1064
+ return value.map((item) => formatRecapValue(brick, item)).filter(Boolean).join(", ");
1065
+ }
1066
+ if (value === true)
1067
+ return fkTOr("recap.yes", "Yes");
1068
+ if (value === false)
1069
+ return fkTOr("recap.no", "No");
1070
+ if (value && typeof value === "object") {
1071
+ const record = value;
1072
+ if (typeof record.name === "string" && "url" in record)
1073
+ return record.name;
1074
+ return Object.values(record).filter((part) => typeof part === "string" && part).join(" ");
1075
+ }
1076
+ const raw = String(value ?? "");
1077
+ if (brick.configs?.options != null) {
1078
+ const labelKey = typeof brick.configs.labelKey === "string" ? brick.configs.labelKey : "label";
1079
+ const valueKey = typeof brick.configs.valueKey === "string" ? brick.configs.valueKey : "value";
1080
+ const match = normalizeOptions(brick.configs.options, labelKey, valueKey).find((option) => option.value === raw);
1081
+ if (match)
1082
+ return match.label;
1083
+ }
1084
+ return raw;
1085
+ };
1086
+ var isEmptyRecapValue = (raw) => raw === undefined || raw === null || raw === "" || Array.isArray(raw) && raw.length === 0;
1087
+ var brickLabel = (brick, locale) => String(resolveLocalizedText(brick.configs?.label, locale) ?? brick.name ?? brick.configs?.key ?? "");
1088
+ var collectionInputs = (spec) => {
1089
+ const inputs = [];
1090
+ for (const child of spec.children ?? []) {
1091
+ const key = child.configs?.key;
1092
+ if (child.type === "input" && key && !key.startsWith("_")) {
1093
+ inputs.push(child);
1094
+ }
1095
+ }
1096
+ return inputs;
1097
+ };
1098
+ var collectRecapItems = (spec, dataMap, locale, showEmpty, groupBySections = false) => {
1099
+ if (!spec)
1100
+ return [];
1101
+ const items = [];
1102
+ for (const child of spec.children ?? []) {
1103
+ if (child.id === "recap" || child.type === "action")
1104
+ continue;
1105
+ if (getAffectedProperties(child.rules, dataMap).hidden === true)
1106
+ continue;
1107
+ const key = child.configs?.key;
1108
+ const label = brickLabel(child, locale);
1109
+ if (child.type === "collection") {
1110
+ const rows = key && Array.isArray(dataMap?.[key]) ? dataMap[key] : [];
1111
+ if (!rows.length && !showEmpty)
1112
+ continue;
1113
+ const inputs = collectionInputs(child);
1114
+ items.push({
1115
+ kind: "collection",
1116
+ label,
1117
+ columns: inputs.map((input) => brickLabel(input, locale)),
1118
+ rows: rows.map((row) => inputs.map((input) => {
1119
+ const raw2 = row?.[input.configs.key];
1120
+ return isEmptyRecapValue(raw2) ? "—" : formatRecapValue(input, raw2);
1121
+ }))
1122
+ });
1123
+ continue;
1124
+ }
1125
+ if (child.type === "panel") {
1126
+ const sectionLabel = resolveLocalizedText(child.configs?.label, locale);
1127
+ if (groupBySections && typeof sectionLabel === "string" && sectionLabel.trim()) {
1128
+ const sectionItems = collectRecapItems(child, dataMap, locale, showEmpty);
1129
+ if (sectionItems.length) {
1130
+ items.push({ kind: "section", label: sectionLabel });
1131
+ items.push(...sectionItems);
1132
+ }
1133
+ continue;
1134
+ }
1135
+ items.push(...collectRecapItems(child, dataMap, locale, showEmpty, groupBySections));
1136
+ continue;
1137
+ }
1138
+ if (child.type !== "input" || !key || key.startsWith("_"))
1139
+ continue;
1140
+ const raw = dataMap?.[key];
1141
+ if (isEmptyRecapValue(raw) && !showEmpty)
1142
+ continue;
1143
+ items.push({
1144
+ kind: "field",
1145
+ label,
1146
+ value: isEmptyRecapValue(raw) ? "—" : formatRecapValue(child, raw)
1147
+ });
1148
+ }
1149
+ return items;
1150
+ };
1151
+ // lib/brick/brick.ts
1152
+ class Brick {
1153
+ type;
1154
+ dataType;
1155
+ id;
1156
+ name;
1157
+ configsForm;
1158
+ category;
1159
+ styles;
1160
+ unWrapData;
1161
+ isPrivate;
1162
+ validators;
1163
+ defaultConfigs;
1164
+ constructor(params) {
1165
+ this.type = params.type;
1166
+ this.dataType = params.dataType;
1167
+ this.id = params.id;
1168
+ this.name = params.name;
1169
+ this.configsForm = params.configsForm;
1170
+ this.category = params.category;
1171
+ this.styles = params.styles;
1172
+ this.unWrapData = params.unWrapData ?? false;
1173
+ this.isPrivate = params.isPrivate ?? false;
1174
+ this.validators = params.validators ?? [];
1175
+ this.defaultConfigs = params.defaultConfigs;
1176
+ }
1177
+ getBrickEmptySpec() {
1178
+ return {
1179
+ type: this.type,
1180
+ dataType: this.dataType,
1181
+ id: this.id,
1182
+ name: this.name,
1183
+ ...this.category !== undefined ? { category: this.category } : {},
1184
+ ...this.configsForm !== undefined ? { configsForm: this.configsForm } : {},
1185
+ ...this.styles !== undefined ? { styles: this.styles } : {},
1186
+ ...this.defaultConfigs !== undefined ? { configs: { ...this.defaultConfigs } } : {},
1187
+ validators: this.validators
1188
+ };
1270
1189
  }
1271
- return text;
1272
1190
  }
1273
- function fkTOr(key, fallback) {
1274
- return state.current[key] ?? fallback;
1191
+ // lib/validators/validator.ts
1192
+ import { Ajv } from "ajv";
1193
+ import ajvErrors from "ajv-errors";
1194
+ import addFormats from "ajv-formats";
1195
+ var ajv = new Ajv({ allErrors: true });
1196
+ addFormats(ajv, ["email", "uri"]);
1197
+ ajvErrors(ajv);
1198
+ var validatorCache = new WeakMap;
1199
+ var compiledValidator = (brickSpec, locale) => {
1200
+ let byLocale = validatorCache.get(brickSpec);
1201
+ if (!byLocale) {
1202
+ byLocale = new Map;
1203
+ validatorCache.set(brickSpec, byLocale);
1204
+ }
1205
+ const cacheKey = locale ?? "";
1206
+ let validate = byLocale.get(cacheKey);
1207
+ if (!validate) {
1208
+ const schema = buildValidationSchema(brickSpec, locale);
1209
+ if (!schema)
1210
+ return;
1211
+ try {
1212
+ validate = ajv.compile(schema);
1213
+ } catch {
1214
+ return;
1215
+ }
1216
+ byLocale.set(cacheKey, validate);
1217
+ }
1218
+ return validate;
1219
+ };
1220
+ var customErrors = (brickSpec, data, locale) => {
1221
+ const errors = {};
1222
+ const dataMap = data ?? {};
1223
+ for (const brick of iterateSchemaBricks(brickSpec)) {
1224
+ const key = brick.configs?.key;
1225
+ if (!key)
1226
+ continue;
1227
+ for (const validation2 of brick.validations ?? []) {
1228
+ if (validation2.validator !== "custom" || !validation2.customValidator) {
1229
+ continue;
1230
+ }
1231
+ const result = evalBrickCode(`const value = dataMap?.[${JSON.stringify(key)}];
1232
+ ${validation2.customValidator}`, dataMap);
1233
+ if (result === true || result === undefined || result === null)
1234
+ continue;
1235
+ if (key in errors)
1236
+ continue;
1237
+ if (typeof result === "string")
1238
+ errors[key] = result;
1239
+ else if (result instanceof Error)
1240
+ errors[key] = result.message;
1241
+ else {
1242
+ const resolved = resolveLocalizedText(validation2.message, locale);
1243
+ errors[key] = typeof resolved === "string" ? resolved : defaultValidationMessage("custom", locale);
1244
+ }
1245
+ }
1246
+ }
1247
+ return errors;
1248
+ };
1249
+ var keyOfError = (error) => {
1250
+ if (error.keyword === "required") {
1251
+ return error.params.missingProperty;
1252
+ }
1253
+ if (error.keyword === "errorMessage") {
1254
+ const inner = error.params.errors?.[0];
1255
+ return inner ? keyOfError(inner) : undefined;
1256
+ }
1257
+ return error.instancePath.split("/").filter(Boolean)[0];
1258
+ };
1259
+ var hiddenKeys = (brickSpec, dataMap) => {
1260
+ const hidden = new Set;
1261
+ const walk = (brick, parentHidden) => {
1262
+ const isHidden = parentHidden || (brick.rules?.length ? getAffectedProperties(brick.rules, dataMap).hidden === true : false);
1263
+ const key = brick.configs?.key;
1264
+ if (isHidden && key)
1265
+ hidden.add(key);
1266
+ if (brick.type === "collection")
1267
+ return;
1268
+ for (const child of brick.children ?? [])
1269
+ walk(child, isHidden);
1270
+ };
1271
+ walk(brickSpec, false);
1272
+ return hidden;
1273
+ };
1274
+ var validateBrickSpecDataDetailed = (brickSpec, data, locale) => {
1275
+ const errors = {};
1276
+ const validate = compiledValidator(brickSpec, locale);
1277
+ if (validate && !validate(data)) {
1278
+ for (const error of validate.errors ?? []) {
1279
+ const key = keyOfError(error);
1280
+ if (key && !(key in errors) && error.message) {
1281
+ errors[key] = error.message;
1282
+ }
1283
+ }
1284
+ }
1285
+ for (const [key, message] of Object.entries(customErrors(brickSpec, data, locale))) {
1286
+ if (!(key in errors))
1287
+ errors[key] = message;
1288
+ }
1289
+ const dataMap = typeof data === "object" && data !== null ? data : undefined;
1290
+ for (const key of hiddenKeys(brickSpec, dataMap)) {
1291
+ delete errors[key];
1292
+ }
1293
+ return { valid: Object.keys(errors).length === 0, errors };
1294
+ };
1295
+ var validateBrickSpecData = (brickSpec, data, locale) => validateBrickSpecDataDetailed(brickSpec, data, locale).valid;
1296
+ var stripEmpty = (data) => {
1297
+ if (typeof data !== "object" || data === null)
1298
+ return {};
1299
+ return Object.fromEntries(Object.entries(data).filter(([, value]) => value !== "" && value !== null && value !== undefined));
1300
+ };
1301
+ var rowSpecCache = new WeakMap;
1302
+ var rowSpecOf = (collection) => {
1303
+ let rowSpec = rowSpecCache.get(collection);
1304
+ if (!rowSpec) {
1305
+ rowSpec = {
1306
+ type: "panel",
1307
+ id: "collection-row",
1308
+ name: "Row",
1309
+ configs: { key: `${collection.configs?.key ?? "collection"}_row` },
1310
+ children: collection.children ?? []
1311
+ };
1312
+ rowSpecCache.set(collection, rowSpec);
1313
+ }
1314
+ return rowSpec;
1315
+ };
1316
+ var validateFormData = (brickSpec, data, locale) => {
1317
+ const present = stripEmpty(data);
1318
+ const errors = {
1319
+ ...validateBrickSpecDataDetailed(brickSpec, present, locale).errors
1320
+ };
1321
+ for (const brick of iterateSchemaBricks(brickSpec)) {
1322
+ if (brick.type !== "collection")
1323
+ continue;
1324
+ const key = brick.configs?.key;
1325
+ if (!key)
1326
+ continue;
1327
+ const rows = present[key];
1328
+ if (!Array.isArray(rows))
1329
+ continue;
1330
+ const rowSpec = rowSpecOf(brick);
1331
+ rows.forEach((row, index) => {
1332
+ const rowResult = validateFormData(rowSpec, row, locale);
1333
+ for (const [field, message] of Object.entries(rowResult.errors)) {
1334
+ const prefixed = `${key}[${index}].${field}`;
1335
+ if (!(prefixed in errors))
1336
+ errors[prefixed] = message;
1337
+ }
1338
+ });
1339
+ }
1340
+ return { valid: Object.keys(errors).length === 0, errors };
1341
+ };
1342
+ // lib/services/unsafe_js_runner_service.ts
1343
+ import { parse as parse3 } from "acorn";
1344
+
1345
+ class UnsafeEvalJsRunnerService {
1346
+ validateJs(js2, options = { ecmaVersion: "latest", allowReturnOutsideFunction: true }) {
1347
+ try {
1348
+ parse3(js2, options);
1349
+ return { valid: true };
1350
+ } catch (error) {
1351
+ return {
1352
+ valid: false,
1353
+ error: error instanceof Error ? error : new Error(String(error))
1354
+ };
1355
+ }
1356
+ }
1357
+ eval(js, scope = {}) {
1358
+ const prelude = Object.entries(scope).map(([name, value]) => `const ${name} = ${JSON.stringify(value)};`).join(`
1359
+ `);
1360
+ const fn = `(() => {${prelude}
1361
+ ${js}})();`;
1362
+ const validation = this.validateJs(fn, { ecmaVersion: "latest" });
1363
+ if (!validation.valid)
1364
+ throw validation.error;
1365
+ return eval(fn);
1366
+ }
1275
1367
  }
1276
1368
  // lib/ops/pointer.ts
1277
1369
  function pointerFromPath(path) {
@@ -2042,6 +2134,7 @@ export {
2042
2134
  duplicateBrick,
2043
2135
  defaultValidationMessage,
2044
2136
  convertFormioForm,
2137
+ collectRecapItems,
2045
2138
  buildValidationSchema,
2046
2139
  addBrick,
2047
2140
  UrlFileUploadService,
@@ -2055,5 +2148,5 @@ export {
2055
2148
  Base64FileUploadService
2056
2149
  };
2057
2150
 
2058
- //# debugId=288836AF5D54AAA664756E2164756E21
2151
+ //# debugId=1A947A5546F0B22764756E2164756E21
2059
2152
  //# sourceMappingURL=index.js.map