@llmnative/react 1.47.0 → 2.0.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.
@@ -33,6 +33,7 @@ export interface I18nDict {
33
33
  requiredField: string;
34
34
  requiredFieldGeneric: string;
35
35
  saveSuccess: string;
36
+ saveError: string;
36
37
  deleteSuccess: string;
37
38
  noticeRequiredFields: string;
38
39
  noChangesToSave?: string;
@@ -66,7 +66,7 @@ export type { SortableListProps, SortableListCard, SortableListReorderMeta } fro
66
66
  export { default as SideNav } from './blocks/SideNav';
67
67
  export type { SideNavProps, SideNavItemDef } from './blocks/SideNav';
68
68
  export { default as Form, useFormContext } from './widgets/Form';
69
- export type { FieldLabel, FormFieldProps, FormProps, FormDeleteArgs, FormDeleteHandler, FormFinallyArgs, FormFinallyHandler, FormSaveArgs, FormSaveHandler, ChangeHandler, FieldOnChange } from './widgets/Form';
69
+ export type { FieldLabel, FormFieldProps, FormProps, FormDeleteArgs, FormDeleteHandler, FormFinallyArgs, FormFinallyHandler, FormSaveArgs, FormSaveHandler, FormSaveProps, FormSubmitArgs, FormSubmitHandler, FormSubmitProps, ChangeHandler, FieldOnChange } from './widgets/Form';
70
70
  export { FormControllerContext, useFormActions, useFormController } from './widgets/form-controller';
71
71
  export type { FormActionEvent, FormController, FormControllerActions, FormControllerState, FormDraftStatus } from './widgets/form-controller';
72
72
  export { default as Grid } from './widgets/Grid';
@@ -17,6 +17,14 @@ interface BaseInputProps extends FormFieldProps {
17
17
  validator?: (value: FieldValue) => string | undefined | Promise<string | undefined>;
18
18
  /** Content rendered as an absolute overlay inside the input (right side). No addon border/bg. */
19
19
  afterInset?: React.ReactNode;
20
+ /** The keyboard a phone shows (e.g. `"numeric"` for a code of digits). */
21
+ inputMode?: React.HTMLAttributes<HTMLInputElement>['inputMode'];
22
+ /** At most this many characters can be typed. */
23
+ maxLength?: number;
24
+ /** A regular expression the value must match, for the browser (e.g. `"[0-9]{6}"`); `validator` for the Form's own check. */
25
+ pattern?: string;
26
+ /** What the browser may fill in (e.g. `"one-time-code"` for a code received by email or SMS, `"tel"`, `"email"`). */
27
+ autoComplete?: string;
20
28
  }
21
29
  interface LabelProps {
22
30
  /** A text or a React node, drawn inside the `<label>` before the required mark. */
@@ -73,10 +81,12 @@ export declare const fieldAddonClass = "inline-flex shrink-0 items-center border
73
81
  export declare const FieldError: ({ message }: {
74
82
  message: string;
75
83
  }) => React.JSX.Element;
76
- export declare const Input: ({ name, onChange, defaultValue, placeholder, label, type, required, readOnlyAfterSet, disabled, before, after, afterInset, feedback, min, max, step, id, labelClassName, inheritWrapperClassName, wrapperClassName, className, validator, labelMode, }: BaseInputProps) => React.JSX.Element;
84
+ export declare const Input: ({ name, onChange, defaultValue, placeholder, label, type, required, readOnlyAfterSet, disabled, before, after, afterInset, feedback, min, max, step, id, labelClassName, inheritWrapperClassName, wrapperClassName, className, validator, labelMode, inputMode, maxLength, pattern, autoComplete, }: BaseInputProps) => React.JSX.Element;
77
85
  export declare const String: (props: InputProps) => React.JSX.Element;
78
86
  export declare const Number: (props: InputProps) => React.JSX.Element;
79
87
  export declare const Email: (props: InputProps) => React.JSX.Element;
88
+ /** A phone number: the phone shows its number keyboard. */
89
+ export declare const Tel: (props: InputProps) => React.JSX.Element;
80
90
  export declare const Password: (props: InputProps) => React.JSX.Element;
81
91
  export declare const Color: ({ className, ...props }: InputProps) => React.JSX.Element;
82
92
  export declare const Date: (props: InputProps) => React.JSX.Element;
@@ -17,7 +17,7 @@ export type FieldOnChange = (params: {
17
17
  record: RecordProps;
18
18
  onChange: FormHandleChange;
19
19
  }) => void;
20
- export type InputType = "text" | "number" | "email" | "password" | "color" | "date" | "time" | "datetime-local" | "week" | "month" | "range" | "checkbox" | "radio" | "url";
20
+ export type InputType = "text" | "number" | "email" | "tel" | "password" | "color" | "date" | "time" | "datetime-local" | "week" | "month" | "range" | "checkbox" | "radio" | "url";
21
21
  export type LabelMode = "default" | "floating";
22
22
  interface FormContextProps {
23
23
  name: string;
@@ -96,13 +96,28 @@ export type FormDeleteArgs = {
96
96
  };
97
97
  export type FormFinallyArgs = {
98
98
  record?: RecordProps;
99
- action: 'create' | 'update' | 'delete';
99
+ /** `submit` after `onSubmit` (action form); the others after a save or a delete. */
100
+ action: 'create' | 'update' | 'delete' | 'submit';
100
101
  };
101
- export type FormSaveHandler = (args: FormSaveArgs) => Promise<string | undefined>;
102
+ /** What `onSubmit` receives: the record of the fields, as typed. */
103
+ export type FormSubmitArgs = {
104
+ record: RecordProps;
105
+ };
106
+ /**
107
+ * Called on save. Return a provider path to write the record there, `undefined` to write it at the
108
+ * Form's `path` (nothing written without a `path`), `false` when it failed and the page already says
109
+ * why (nothing written, no notice, `save()` resolves `false`). Throwing shows its message in the Form's notice.
110
+ */
111
+ export type FormSaveHandler = (args: FormSaveArgs) => Promise<string | undefined | false>;
102
112
  export type FormDeleteHandler = (args: FormDeleteArgs) => Promise<string | undefined>;
103
113
  export type FormFinallyHandler = (args: FormFinallyArgs) => Promise<boolean>;
104
- /** Core props shared by all Form variants. */
105
- interface BaseFormProps {
114
+ /**
115
+ * The action of an action form. Throwing shows its message in the Form's notice; returning `false` means it
116
+ * failed and the page already says why (no notice, no `onComplete`, `save()` resolves `false`).
117
+ */
118
+ export type FormSubmitHandler = (args: FormSubmitArgs) => Promise<void | false> | void | false;
119
+ /** Props shared by both kinds of Form: the one that saves a record and the one that runs an action. */
120
+ interface FormCommonProps {
106
121
  /** Visual wrapper: `"card"` adds a card shell; `"empty"` renders bare. */
107
122
  appearance?: "card" | "empty";
108
123
  /** Shared controller for external actions, custom save buttons and draft state. */
@@ -111,32 +126,16 @@ interface BaseFormProps {
111
126
  header?: React.ReactNode;
112
127
  /** Custom content rendered in the form footer area. */
113
128
  footer?: React.ReactNode;
114
- /** Provider path for data load and save. Omit for a pure local form. */
115
- path?: string;
116
- /** Generate a custom primary key for new records instead of auto-push. */
117
- keyGenerator?: (record: RecordProps) => string;
118
129
  /** Transform the record after loading from the provider. */
119
130
  onLoad?: (record: RecordProps) => void;
120
131
  /** Called on every field change with the current record state. */
121
132
  onRecordChange?: (record: RecordProps) => void;
122
- /** Transform the record before saving. Return the final record or a custom path. */
123
- onSave?: FormSaveHandler;
124
- /** Called before deletion. Return a path override or `undefined`. */
125
- onDelete?: FormDeleteHandler;
126
- /** Called after save or delete. Return `false` to suppress default navigation. */
133
+ /** Called after a save, a delete or a submit that worked. Return `false` to suppress default navigation. */
127
134
  onComplete?: FormFinallyHandler;
128
135
  /** Log field-change events to the console (dev helper). */
129
136
  log?: boolean;
130
- /** Show the inline save/delete notice banner. Defaults to `true`. */
137
+ /** Show the inline notice banner (saved, deleted, required fields, errors). Defaults to `true`; `false` lets an error of `onSave`/`onSubmit` reject `save()` instead. */
131
138
  showNotice?: boolean;
132
- /**
133
- * Whether saving needs a changed field. Defaults to `true`: an unchanged form says "No changes
134
- * to save" and its save is disabled. `false` makes it an action form (CR-117), whose initial
135
- * values are already a valid submission (e.g. start a release with the proposed version):
136
- * `save()` validates and calls `onSave` even when nothing changed. `isDirty` still tells
137
- * whether something did.
138
- */
139
- requireChanges?: boolean;
140
139
  /**
141
140
  * Stable bucket that isolates this form's local draft. Omit to disable draft persistence.
142
141
  * The Form appends its own path/route identity, so one bucket safely serves many forms.
@@ -163,32 +162,61 @@ interface BaseFormProps {
163
162
  * `[&>form]:` child selector on `wrapperClassName`.
164
163
  */
165
164
  formClassName?: string;
166
- }
167
- interface FormDefaultProps extends BaseFormProps {
168
- children: React.ReactNode | ((fields: FormTree) => React.ReactNode) | ((args: {
165
+ /** Field elements, a render-prop receiving the field tree, or a record-aware function. */
166
+ children?: React.ReactNode | ((fields: FormTree) => React.ReactNode) | ((args: {
169
167
  record?: RecordProps;
170
168
  }) => React.ReactNode);
169
+ /** Initial field values (a new record, or the proposed values of an action). */
171
170
  defaultValues?: RecordProps;
172
171
  }
173
- interface FormDatabaseProps extends FormDefaultProps {
174
- path: string;
172
+ /** What a Form that saves a record takes: where, how and what it writes. */
173
+ interface FormSaveOptions {
174
+ /** Provider path the record is read from and written to. Omitted: read from the route hash if any, else nothing read; nothing written unless `onSave` returns a path. */
175
+ path?: string;
176
+ /** Generate a custom primary key for new records instead of auto-push. */
177
+ keyGenerator?: (record: RecordProps) => string;
178
+ /**
179
+ * Called on save, after the fields are valid and only when one changed. Return a provider path to
180
+ * write the record there, `undefined` to write it at `path`. To run an action instead (call a
181
+ * command, send a code) use `onSubmit`.
182
+ */
183
+ onSave?: FormSaveHandler;
184
+ /** Called before deletion. Return a path override or `undefined`. */
185
+ onDelete?: FormDeleteHandler;
175
186
  }
176
- interface FormModelProps extends BaseFormProps {
177
- model: ModelProps;
178
- children?: ((fields: FormTree) => React.ReactNode);
187
+ /**
188
+ * A Form that saves a record: it reads it from `path`, writes it there (or where `onSave` says),
189
+ * shows "Saved", and saves only when a field changed. It shows its own Save button in the card.
190
+ */
191
+ export interface FormSaveProps extends FormCommonProps, FormSaveOptions {
192
+ onSubmit?: never;
179
193
  }
180
194
  /**
181
- * Public props for `<Form>`.
182
- * When `path` points to an existing record the form loads its data automatically.
183
- * When `keyGenerator` or `defaultValues` is set the form operates in create-only mode.
195
+ * An action form: it saves nothing. On submit (Enter in a field, or `useFormController().save()` from
196
+ * your own button) it checks the fields and calls `onSubmit({ record })`, changed or not. No Save
197
+ * button, no "Saved": the button and what follows are yours; an error of `onSubmit` shows in its
198
+ * notice. It takes no `path`, `onSave`, `onDelete` or `keyGenerator`.
184
199
  */
185
- export interface FormProps extends BaseFormProps {
186
- /** Field elements, a render-prop receiving the field tree, or a record-aware function. */
187
- children?: React.ReactNode | ((fields: FormTree) => React.ReactNode) | ((args: {
188
- record?: RecordProps;
189
- }) => React.ReactNode);
190
- /** Initial field values for a new record. */
191
- defaultValues?: RecordProps;
200
+ export interface FormSubmitProps extends FormCommonProps {
201
+ /** The action: e.g. `({ record }) => call('requestLoginCode', record)`. */
202
+ onSubmit: FormSubmitHandler;
203
+ path?: never;
204
+ keyGenerator?: never;
205
+ onSave?: never;
206
+ onDelete?: never;
207
+ }
208
+ /** Public props for `<Form>`: a Form that saves a record (`FormSaveProps`) or an action form (`FormSubmitProps`, with `onSubmit`). */
209
+ export type FormProps = FormSaveProps | FormSubmitProps;
210
+ /** What the inner Form takes: either kind, checked by `Form`. */
211
+ interface FormDataProps extends FormCommonProps, FormSaveOptions {
212
+ onSubmit?: FormSubmitHandler;
213
+ }
214
+ interface FormDatabaseProps extends FormDataProps {
215
+ path: string;
216
+ }
217
+ interface FormModelProps extends Omit<FormCommonProps, 'children'>, FormSaveOptions {
218
+ model: ModelProps;
219
+ children?: ((fields: FormTree) => React.ReactNode);
192
220
  }
193
221
  declare function Form(props: FormProps): React.JSX.Element;
194
222
  export declare const FormDatabase: (props: FormDatabaseProps) => React.JSX.Element;
package/llms-full.txt CHANGED
@@ -305,7 +305,7 @@ export class RestDataProvider implements DataProviderAdapter {
305
305
  | `draftBucket` | `string` | Bucket stabile che isola la bozza locale; omettilo per disabilitare la persistenza |
306
306
  | `noticeAnchorRef` | `React.RefObject<HTMLElement>` | Ancoraggio sticky per notifiche |
307
307
  | `showNotice` | `boolean` | Mostra notifiche save/delete (default `true`) |
308
- | `requireChanges` | `boolean` | Default `true`: senza un campo cambiato il salvataggio dice "No changes to save" ed è disabilitato. `false` = form d'azione (CR-117): i valori iniziali sono già un invio valido (es. avviare una release con la versione proposta), `save()` valida e chiama `onSave` anche senza modifiche; `isDirty` dice comunque se qualcosa è cambiato |
308
+ | `onSubmit` | `FormSubmitHandler` | `async ({ record }) => void`. **Form d'azione** (CR-125): non legge e non scrive nulla; invia anche senza modifiche e con Invio in un campo; niente pulsante Salva né "Salvato" (il pulsante è tuo: `useFormController().save()`); l'errore lanciato appare nel suo avviso; restituire `false` = è fallito e la pagina dice già perché (nessun avviso né `onComplete`). Esclude `path`, `onSave`, `onDelete`, `keyGenerator` (errore di tipo e a runtime). Sostituisce `requireChanges={false}` della 1.x |
309
309
  | `children` | `ReactNode \| ((fields) => ReactNode) \| (({ record }) => ReactNode)` | Contenuto form |
310
310
  | `header` | `ReactNode` | Contenuto header custom |
311
311
  | `footer` | `ReactNode` | Contenuto footer custom |
@@ -316,9 +316,9 @@ export class RestDataProvider implements DataProviderAdapter {
316
316
  | `log` | `boolean` | Logga ogni modifica su console |
317
317
  | `onLoad` | `(record) => void` | Side-effect dopo il caricamento (non trasforma) |
318
318
  | `onRecordChange` | `(record) => void` | Cambio campo in tempo reale |
319
- | `onSave` | `FormSaveHandler` | `async ({ record, prevRecord?, storagePath?, action }) => string \| undefined` |
319
+ | `onSave` | `FormSaveHandler` | `async ({ record, prevRecord?, storagePath?, action }) => string \| undefined`. Form che **salva un record**, solo se un campo è cambiato: restituisce il path dove scriverlo, `undefined` = il `path` del Form (senza `path` non scrive), `false` = è fallito e la pagina dice già perché (non scrive, nessun avviso, `save()` dà `false`). Per un'azione (chiamare un comando) usa `onSubmit`. L'errore lanciato appare nell'avviso del Form |
320
320
  | `onDelete` | `FormDeleteHandler` | `async ({ record }) => string \| undefined` |
321
- | `onComplete` | `FormFinallyHandler` | `async ({ record, action }) => boolean` |
321
+ | `onComplete` | `FormFinallyHandler` | `async ({ record, action }) => boolean`; `action` è `'create' \| 'update' \| 'delete' \| 'submit'` |
322
322
 
323
323
  **Form controller** — stato e azioni native del form accessibili da qualsiasi punto della pagina.
324
324
 
@@ -337,15 +337,18 @@ const form = useFormController();
337
337
  <Form controller={form} draftBucket="workspace/acme" onSave={...} />
338
338
  ```
339
339
 
340
- Esempio — form d'azione, i valori proposti si inviano anche senza modificarli (CR-117):
340
+ Esempio — form d'azione: chiama un comando, non salva nulla (CR-125):
341
341
  ```tsx
342
342
  const form = useFormController();
343
343
 
344
- <LoadingButton label="Avvia la release" onClick={() => form.save()} />
345
- <Form controller={form} appearance="empty" requireChanges={false} defaultValues={{ version: '1.4.0', notes }} onSave={async ({ record }) => { await start(record); return undefined; }}>
346
- <Input name="version" label="Versione" required />
344
+ <Form controller={form} appearance="empty" onSubmit={({ record }) => call('verifyLoginCode', record)}>
345
+ <Input name="email" type="email" label="Email" required />
346
+ <Input name="code" label="Codice" required inputMode="numeric" maxLength={6} pattern="[0-9]{6}" autoComplete="one-time-code" />
347
347
  </Form>
348
+ <LoadingButton label="Entra con il codice" loading={form.isSaving} onClick={() => form.save()} />
348
349
  ```
350
+ Con valori proposti (avviare una release con la versione proposta) si inviano anche senza modificarli:
351
+ `<Form controller={form} appearance="empty" defaultValues={{ version: '1.4.0' }} onSubmit={({ record }) => start(record)}>`.
349
352
 
350
353
  Esempio — uso dentro il form (senza passare controller):
351
354
  ```tsx
@@ -368,6 +371,8 @@ Azioni: `save()`, `delete()`, `reset()`, `restoreDraft()`, `discardDraft()`.
368
371
  <Input name="x" label="..." required /> // text (default)
369
372
  <Input name="price" type="number" min={0} max={100} step={0.01} />
370
373
  <Input name="email" type="email" />
374
+ <Input name="phone" type="tel" autoComplete="tel" /> // o <Tel name="phone" />
375
+ <Input name="code" inputMode="numeric" maxLength={6} pattern="[0-9]{6}" autoComplete="one-time-code" /> // un codice monouso
371
376
  <Input name="pwd" type="password" />
372
377
  <Input name="date" type="date" />
373
378
  <Input name="color" type="color" />
@@ -375,8 +380,8 @@ Azioni: `save()`, `delete()`, `reset()`, `restoreDraft()`, `discardDraft()`.
375
380
  <Checkbox name="active" valueChecked="yes" />
376
381
  <Switch name="gate" label="Passa dalla CI" disabled title="L'agente non committa" /> // CR-116: disabled (non cambia né con il clic né da tastiera), title anche sull'etichetta: il motivo al passaggio del mouse
377
382
  ```
378
- `Input` props: `name`(req) `label` `type` `required` `disabled` `placeholder` `min` `max` `step` `validator:(v)=>string|undefined`
379
- `type`: `'text' | 'number' | 'email' | 'password' | 'color' | 'date' | 'time' | 'datetime-local' | 'week' | 'month' | 'range' | 'checkbox' | 'radio' | 'url'`
383
+ `Input` props: `name`(req) `label` `type` `required` `disabled` `placeholder` `min` `max` `step` `inputMode` `maxLength` `pattern` `autoComplete` `validator:(v)=>string|undefined`
384
+ `type`: `'text' | 'number' | 'email' | 'tel' | 'password' | 'color' | 'date' | 'time' | 'datetime-local' | 'week' | 'month' | 'range' | 'checkbox' | 'radio' | 'url'`
380
385
 
381
386
  **NOTA**: il tipo HTML si passa con `type`. `inputType` e i sotto-componenti `Input.Number`/`Input.Email`/`Input.Date` non esistono.
382
387
 
@@ -1191,6 +1196,8 @@ const registry = createAIProviderRegistry(definitions)
1191
1196
  // Azioni di Chatbot (CR-107): con `content` aprono un menu; senza, sono pulsanti con `onClick`.
1192
1197
  // <Chatbot actions={[{ key: 'finish', icon: 'git-pull-request', label: 'Concludi', showLabel: true,
1193
1198
  // disabled: running, onClick: finish }]} … />
1199
+ // Allegati di Chatbot (`attachments`): file scelti, trascinati o incollati con Ctrl+V (uno screenshot diventa
1200
+ // `pasted-<data>.png`, CR-124); il testo incollato resta testo. Arrivano in `onSubmit` come `files`.
1194
1201
  // Comandi "/" di Chatbot: `commands` (EditorCommand[]) + `commandsTrigger` (default "/").
1195
1202
  // Il menu mostra `/name` e sotto la `description`; il filtro cerca in entrambi (CR-111).
1196
1203
  // Senza `handler` la scelta inserisce "/name " e l'invio manda il testo così com'è.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llmnative/react",
3
- "version": "1.47.0",
3
+ "version": "2.0.0",
4
4
  "description": "LLM-native React framework for deterministic UI generation. Data-driven by default, schema-driven optional. Ultra-low token consumption — built for AI agents, not just humans.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",