@mostajs/schema-form 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/llms.txt +12 -0
  2. package/package.json +1 -1
  3. package/src/index.js +25 -1
package/llms.txt CHANGED
@@ -42,6 +42,18 @@ Repeater côté navigateur : câbler add (clone d'un `.sf-item`, réindexer `nam
42
42
  ## STATUTS (exemple QatraxFlow)
43
43
  `resultat.statut` = Validé / Non validé / En cours / Interrompu (voir `examples/qatraxflow-recette.schema.js`).
44
44
 
45
+ ## LE CHAMP FICHIER (0.3.0)
46
+ `{ path, type: 'fichier', accept?, capture?, multiple? }`
47
+
48
+ - ⚠️ **L'`enctype="multipart/form-data"` est posé AUTOMATIQUEMENT** dès qu'un champ `fichier`
49
+ figure au schéma — répéteurs compris. Sans lui, le navigateur envoie le seul NOM du fichier,
50
+ jamais ses octets : il ne se plaint pas, le serveur reçoit un champ texte, et la photo n'arrive
51
+ jamais. C'est l'oubli le plus fréquent des formulaires de téléversement, et il est SILENCIEUX.
52
+ - ⚠️ **La valeur n'est jamais pré-remplie** : un navigateur refuse qu'une page choisisse un fichier
53
+ (ce serait une exfiltration). Un écran de modification montre la pièce déjà envoyée À CÔTÉ.
54
+ - ⚠️ `capture="environment"` ouvre l'**appareil photo** sur mobile, pas la galerie.
55
+ - ⚠️ Les OCTETS ne passent pas par `readValues` : ils se lisent avec `@mostajs/form-data-extract`.
56
+
45
57
  ## PIÈGES
46
58
  - **Zéro-dép, HTML-string** : `renderForm` rend une chaîne ; côté React, injecter ou porter en composants.
47
59
  - `readValues` : Array traité comme liste [name,value] (pas via `.entries()`), FormData via `.entries()`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/schema-form",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Interface de SAISIE générique pilotée par schéma (zéro-dép) : sections + champs typés (text/textarea/number/boolean/select/color/date/localized/repeater), chemins imbriqués, multilingue AR/FR/EN. Rendu HTML (renderForm) + relecture (readValues). Réutilisable par toute app @mostajs.",
5
5
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
6
  "license": "AGPL-3.0-or-later",
package/src/index.js CHANGED
@@ -49,6 +49,20 @@ function renderControl(f, value, name) {
49
49
  return `<input type="number" name="${name}"${req}${ph} value="${escapeHtml(value ?? '')}"${f.min != null ? ` min="${f.min}"` : ''}${f.max != null ? ` max="${f.max}"` : ''}${f.step != null ? ` step="${f.step}"` : ''} style="${inputStyle}">`;
50
50
  case 'boolean':
51
51
  return `<label style="display:inline-flex;gap:.4rem;align-items:center"><input type="checkbox" name="${name}" value="true"${value ? ' checked' : ''}> ${escapeHtml(label(f.help) || 'Oui')}</label>`;
52
+ // ── LE CHAMP FICHIER (0.3.0) ──────────────────────────────────────────
53
+ //
54
+ // ⚠️ `capture` ouvre l'APPAREIL PHOTO sur mobile, au lieu de la galerie. C'est ce qu'on veut
55
+ // pour photographier une facture au comptoir : demander à un exploitant de prendre la photo,
56
+ // de la retrouver dans sa galerie, puis de la choisir, c'est trois occasions d'abandonner.
57
+ //
58
+ // ⚠️ La valeur d'un champ fichier n'est JAMAIS pré-remplie : un navigateur refuse qu'une page
59
+ // décide quel fichier est sélectionné (ce serait une exfiltration). Un formulaire de
60
+ // modification doit donc afficher la pièce DÉJÀ envoyée à côté du champ, pas dedans.
61
+ case 'fichier':
62
+ return `<input type="file" name="${name}"${req}`
63
+ + `${f.accept ? ` accept="${escapeHtml(f.accept)}"` : ''}`
64
+ + `${f.capture ? ` capture="${escapeHtml(f.capture)}"` : ''}`
65
+ + `${f.multiple ? ' multiple' : ''} style="${inputStyle}">`;
52
66
  case 'select':
53
67
  return `<select name="${name}"${req} style="${inputStyle}">${(f.options || []).map((o) => `<option value="${escapeHtml(o.value)}"${String(value) === String(o.value) ? ' selected' : ''}>${escapeHtml(label(o.label ?? o.value))}</option>`).join('')}</select>`;
54
68
  case 'montant': {
@@ -150,7 +164,17 @@ export function renderForm(schema, values = {}, opts = {}) {
150
164
  <legend style="padding:0 .5rem;font-weight:700;color:${SF_THEME.blue}">${escapeHtml(label(sec.label, locale) || sec.id)}</legend>${inner}</fieldset>`;
151
165
  }).join('');
152
166
  const submit = opts.submitLabel === null ? '' : `<button type="submit" style="background:${SF_THEME.blue};color:#fff;border:0;border-radius:.45rem;padding:.6rem 1.2rem;font-weight:600;cursor:pointer">${escapeHtml(opts.submitLabel || 'Enregistrer')}</button>`;
153
- return `<form class="sf-form"${opts.id ? ` id="${opts.id}"` : ''}${opts.action ? ` method="post" action="${opts.action}"` : ''}>
167
+ // ── L'ENCTYPE EST POSÉ TOUT SEUL, ET C'EST LE POINT ───────────────────────
168
+ //
169
+ // ⚠️ Un formulaire qui porte un `<input type=file>` SANS `enctype="multipart/form-data"` envoie
170
+ // le seul NOM du fichier, jamais ses octets. Le navigateur ne se plaint pas, le serveur reçoit
171
+ // un champ texte qui ressemble à un fichier, et la photo n'arrive jamais. C'est l'oubli le plus
172
+ // fréquent des formulaires de téléversement, et il est parfaitement silencieux.
173
+ //
174
+ // On ne le laisse donc pas à la charge de l'appelant : dès qu'un champ `fichier` figure au
175
+ // schéma — y compris DANS un répéteur — l'attribut est posé.
176
+ const aUnFichier = JSON.stringify(schema).includes('"fichier"');
177
+ return `<form class="sf-form"${opts.id ? ` id="${opts.id}"` : ''}${opts.action ? ` method="post" action="${opts.action}"` : ''}${aUnFichier ? ' enctype="multipart/form-data"' : ''}>
154
178
  ${schema.title ? `<h2 style="color:${SF_THEME.ink}">${escapeHtml(label(schema.title, locale))}</h2>` : ''}${body}${submit}</form>`;
155
179
  }
156
180