@streamline-pulse/formkrafter-core 0.8.0 → 0.10.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;
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;
493
+
494
+ class ReturnSignal {
495
+ value;
496
+ constructor(value) {
497
+ this.value = value;
723
498
  }
724
499
  }
725
-
726
- // lib/services/option_source_service.ts
727
- class FetchOptionSourceService {
728
- defaults;
729
- cache = new Map;
730
- constructor(defaults = {}) {
731
- this.defaults = defaults;
732
- }
733
- resolveUrl(ref) {
734
- if (/^https?:\/\//.test(ref) || !this.defaults.baseUrl)
735
- return ref;
736
- return `${this.defaults.baseUrl.replace(/\/$/, "")}/${ref.replace(/^\//, "")}`;
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
  }
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);
770
+ }
771
+ return result;
772
+ }
773
+ default:
774
+ throw new Error(`Unsupported syntax: ${node.type}`);
907
775
  }
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
776
  };
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;
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
938
793
  };
939
- var validateFormData = (brickSpec, data, locale) => {
940
- const present = stripEmpty(data);
941
- const errors = {
942
- ...validateBrickSpecDataDetailed(brickSpec, present, locale).errors
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
+ ]
943
805
  };
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;
960
- }
961
- });
962
- }
963
- return { valid: Object.keys(errors).length === 0, errors };
964
- };
965
- // lib/services/unsafe_js_runner_service.ts
966
- import { parse as parse3 } from "acorn";
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,579 @@ 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 };
1059
+ };
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;
1259
1085
  };
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 };
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/utils/remote.ts
1152
+ function interpolateTemplate(template, dataMap) {
1153
+ return template.replace(/\{(\w+)\}/g, (_, token) => {
1154
+ const value = dataMap?.[token];
1155
+ return value === undefined || value === null ? "" : String(value);
1156
+ });
1265
1157
  }
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));
1158
+ function parseHeaderLines(raw, dataMap) {
1159
+ if (typeof raw !== "string" || !raw.trim())
1160
+ return;
1161
+ const headers = {};
1162
+ for (const line of raw.split(`
1163
+ `)) {
1164
+ const separator = line.indexOf(":");
1165
+ if (separator <= 0)
1166
+ continue;
1167
+ const name = line.slice(0, separator).trim();
1168
+ const value = interpolateTemplate(line.slice(separator + 1).trim(), dataMap);
1169
+ if (name)
1170
+ headers[name] = value;
1270
1171
  }
1271
- return text;
1172
+ return Object.keys(headers).length ? headers : undefined;
1272
1173
  }
1273
- function fkTOr(key, fallback) {
1274
- return state.current[key] ?? fallback;
1174
+ function appendSearchParam(url, param, term) {
1175
+ if (!term)
1176
+ return url;
1177
+ const separator = url.includes("?") ? "&" : "?";
1178
+ return `${url}${separator}${encodeURIComponent(param)}=${encodeURIComponent(term)}`;
1179
+ }
1180
+ // lib/brick/brick.ts
1181
+ class Brick {
1182
+ type;
1183
+ dataType;
1184
+ id;
1185
+ name;
1186
+ configsForm;
1187
+ category;
1188
+ styles;
1189
+ unWrapData;
1190
+ isPrivate;
1191
+ validators;
1192
+ defaultConfigs;
1193
+ constructor(params) {
1194
+ this.type = params.type;
1195
+ this.dataType = params.dataType;
1196
+ this.id = params.id;
1197
+ this.name = params.name;
1198
+ this.configsForm = params.configsForm;
1199
+ this.category = params.category;
1200
+ this.styles = params.styles;
1201
+ this.unWrapData = params.unWrapData ?? false;
1202
+ this.isPrivate = params.isPrivate ?? false;
1203
+ this.validators = params.validators ?? [];
1204
+ this.defaultConfigs = params.defaultConfigs;
1205
+ }
1206
+ getBrickEmptySpec() {
1207
+ return {
1208
+ type: this.type,
1209
+ dataType: this.dataType,
1210
+ id: this.id,
1211
+ name: this.name,
1212
+ ...this.category !== undefined ? { category: this.category } : {},
1213
+ ...this.configsForm !== undefined ? { configsForm: this.configsForm } : {},
1214
+ ...this.styles !== undefined ? { styles: this.styles } : {},
1215
+ ...this.defaultConfigs !== undefined ? { configs: { ...this.defaultConfigs } } : {},
1216
+ validators: this.validators
1217
+ };
1218
+ }
1219
+ }
1220
+ // lib/validators/validator.ts
1221
+ import { Ajv } from "ajv";
1222
+ import ajvErrors from "ajv-errors";
1223
+ import addFormats from "ajv-formats";
1224
+ var ajv = new Ajv({ allErrors: true });
1225
+ addFormats(ajv, ["email", "uri"]);
1226
+ ajvErrors(ajv);
1227
+ var validatorCache = new WeakMap;
1228
+ var compiledValidator = (brickSpec, locale) => {
1229
+ let byLocale = validatorCache.get(brickSpec);
1230
+ if (!byLocale) {
1231
+ byLocale = new Map;
1232
+ validatorCache.set(brickSpec, byLocale);
1233
+ }
1234
+ const cacheKey = locale ?? "";
1235
+ let validate = byLocale.get(cacheKey);
1236
+ if (!validate) {
1237
+ const schema = buildValidationSchema(brickSpec, locale);
1238
+ if (!schema)
1239
+ return;
1240
+ try {
1241
+ validate = ajv.compile(schema);
1242
+ } catch {
1243
+ return;
1244
+ }
1245
+ byLocale.set(cacheKey, validate);
1246
+ }
1247
+ return validate;
1248
+ };
1249
+ var customErrors = (brickSpec, data, locale) => {
1250
+ const errors = {};
1251
+ const dataMap = data ?? {};
1252
+ for (const brick of iterateSchemaBricks(brickSpec)) {
1253
+ const key = brick.configs?.key;
1254
+ if (!key)
1255
+ continue;
1256
+ for (const validation2 of brick.validations ?? []) {
1257
+ if (validation2.validator !== "custom" || !validation2.customValidator) {
1258
+ continue;
1259
+ }
1260
+ const result = evalBrickCode(`const value = dataMap?.[${JSON.stringify(key)}];
1261
+ ${validation2.customValidator}`, dataMap);
1262
+ if (result === true || result === undefined || result === null)
1263
+ continue;
1264
+ if (key in errors)
1265
+ continue;
1266
+ if (typeof result === "string")
1267
+ errors[key] = result;
1268
+ else if (result instanceof Error)
1269
+ errors[key] = result.message;
1270
+ else {
1271
+ const resolved = resolveLocalizedText(validation2.message, locale);
1272
+ errors[key] = typeof resolved === "string" ? resolved : defaultValidationMessage("custom", locale);
1273
+ }
1274
+ }
1275
+ }
1276
+ return errors;
1277
+ };
1278
+ var keyOfError = (error) => {
1279
+ if (error.keyword === "required") {
1280
+ return error.params.missingProperty;
1281
+ }
1282
+ if (error.keyword === "errorMessage") {
1283
+ const inner = error.params.errors?.[0];
1284
+ return inner ? keyOfError(inner) : undefined;
1285
+ }
1286
+ return error.instancePath.split("/").filter(Boolean)[0];
1287
+ };
1288
+ var hiddenKeys = (brickSpec, dataMap) => {
1289
+ const hidden = new Set;
1290
+ const walk = (brick, parentHidden) => {
1291
+ const isHidden = parentHidden || (brick.rules?.length ? getAffectedProperties(brick.rules, dataMap).hidden === true : false);
1292
+ const key = brick.configs?.key;
1293
+ if (isHidden && key)
1294
+ hidden.add(key);
1295
+ if (brick.type === "collection")
1296
+ return;
1297
+ for (const child of brick.children ?? [])
1298
+ walk(child, isHidden);
1299
+ };
1300
+ walk(brickSpec, false);
1301
+ return hidden;
1302
+ };
1303
+ var validateBrickSpecDataDetailed = (brickSpec, data, locale) => {
1304
+ const errors = {};
1305
+ const validate = compiledValidator(brickSpec, locale);
1306
+ if (validate && !validate(data)) {
1307
+ for (const error of validate.errors ?? []) {
1308
+ const key = keyOfError(error);
1309
+ if (key && !(key in errors) && error.message) {
1310
+ errors[key] = error.message;
1311
+ }
1312
+ }
1313
+ }
1314
+ for (const [key, message] of Object.entries(customErrors(brickSpec, data, locale))) {
1315
+ if (!(key in errors))
1316
+ errors[key] = message;
1317
+ }
1318
+ const dataMap = typeof data === "object" && data !== null ? data : undefined;
1319
+ for (const key of hiddenKeys(brickSpec, dataMap)) {
1320
+ delete errors[key];
1321
+ }
1322
+ return { valid: Object.keys(errors).length === 0, errors };
1323
+ };
1324
+ var validateBrickSpecData = (brickSpec, data, locale) => validateBrickSpecDataDetailed(brickSpec, data, locale).valid;
1325
+ var stripEmpty = (data) => {
1326
+ if (typeof data !== "object" || data === null)
1327
+ return {};
1328
+ return Object.fromEntries(Object.entries(data).filter(([, value]) => value !== "" && value !== null && value !== undefined));
1329
+ };
1330
+ var rowSpecCache = new WeakMap;
1331
+ var rowSpecOf = (collection) => {
1332
+ let rowSpec = rowSpecCache.get(collection);
1333
+ if (!rowSpec) {
1334
+ rowSpec = {
1335
+ type: "panel",
1336
+ id: "collection-row",
1337
+ name: "Row",
1338
+ configs: { key: `${collection.configs?.key ?? "collection"}_row` },
1339
+ children: collection.children ?? []
1340
+ };
1341
+ rowSpecCache.set(collection, rowSpec);
1342
+ }
1343
+ return rowSpec;
1344
+ };
1345
+ var validateFormData = (brickSpec, data, locale) => {
1346
+ const present = stripEmpty(data);
1347
+ const errors = {
1348
+ ...validateBrickSpecDataDetailed(brickSpec, present, locale).errors
1349
+ };
1350
+ for (const brick of iterateSchemaBricks(brickSpec)) {
1351
+ if (brick.type !== "collection")
1352
+ continue;
1353
+ const key = brick.configs?.key;
1354
+ if (!key)
1355
+ continue;
1356
+ const rows = present[key];
1357
+ if (!Array.isArray(rows))
1358
+ continue;
1359
+ const rowSpec = rowSpecOf(brick);
1360
+ rows.forEach((row, index) => {
1361
+ const rowResult = validateFormData(rowSpec, row, locale);
1362
+ for (const [field, message] of Object.entries(rowResult.errors)) {
1363
+ const prefixed = `${key}[${index}].${field}`;
1364
+ if (!(prefixed in errors))
1365
+ errors[prefixed] = message;
1366
+ }
1367
+ });
1368
+ }
1369
+ return { valid: Object.keys(errors).length === 0, errors };
1370
+ };
1371
+ // lib/services/unsafe_js_runner_service.ts
1372
+ import { parse as parse3 } from "acorn";
1373
+
1374
+ class UnsafeEvalJsRunnerService {
1375
+ validateJs(js2, options = { ecmaVersion: "latest", allowReturnOutsideFunction: true }) {
1376
+ try {
1377
+ parse3(js2, options);
1378
+ return { valid: true };
1379
+ } catch (error) {
1380
+ return {
1381
+ valid: false,
1382
+ error: error instanceof Error ? error : new Error(String(error))
1383
+ };
1384
+ }
1385
+ }
1386
+ eval(js, scope = {}) {
1387
+ const prelude = Object.entries(scope).map(([name, value]) => `const ${name} = ${JSON.stringify(value)};`).join(`
1388
+ `);
1389
+ const fn = `(() => {${prelude}
1390
+ ${js}})();`;
1391
+ const validation = this.validateJs(fn, { ecmaVersion: "latest" });
1392
+ if (!validation.valid)
1393
+ throw validation.error;
1394
+ return eval(fn);
1395
+ }
1275
1396
  }
1276
1397
  // lib/ops/pointer.ts
1277
1398
  function pointerFromPath(path) {
@@ -2025,11 +2146,13 @@ export {
2025
2146
  registerValidationMessages,
2026
2147
  pointerOfUid,
2027
2148
  pointerFromPath,
2149
+ parseHeaderLines,
2028
2150
  normalizeOptions,
2029
2151
  moveBrick,
2030
2152
  iterateSchemaBricks,
2031
2153
  iterateBricks,
2032
2154
  isLocalizedObject,
2155
+ interpolateTemplate,
2033
2156
  hasNestedForms,
2034
2157
  getBrickData,
2035
2158
  getBrickAt,
@@ -2042,7 +2165,9 @@ export {
2042
2165
  duplicateBrick,
2043
2166
  defaultValidationMessage,
2044
2167
  convertFormioForm,
2168
+ collectRecapItems,
2045
2169
  buildValidationSchema,
2170
+ appendSearchParam,
2046
2171
  addBrick,
2047
2172
  UrlFileUploadService,
2048
2173
  UnsafeEvalJsRunnerService,
@@ -2055,5 +2180,5 @@ export {
2055
2180
  Base64FileUploadService
2056
2181
  };
2057
2182
 
2058
- //# debugId=288836AF5D54AAA664756E2164756E21
2183
+ //# debugId=99487FB3CC6D18E564756E2164756E21
2059
2184
  //# sourceMappingURL=index.js.map