@msbci/form-editor 1.1.0 → 1.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.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ComponentType } from 'react';
3
- import { IFormDefinition, IFormPage, IFormRoster, IFormVariable, IFormType, IDataSourceConnector, IConditionRule, IRosterVariableRef } from '@msbci/form-core';
2
+ import { ReactNode, ComponentType } from 'react';
3
+ import { IFormDefinition, IFormPage, IFormRoster, IFormVariable, IVariableTemplate, IVariableTemplateOverrides, IFormType, IDataSourceConnector, IConditionRule, IRosterVariableRef } from '@msbci/form-core';
4
4
  export { IDataSourceConnector, IFormDefinition, IFormPage, IFormRoster, IFormVariable, IMosobiThemeBase } from '@msbci/form-core';
5
5
  import * as zustand from 'zustand';
6
6
 
@@ -40,6 +40,12 @@ interface IEditorAdapter {
40
40
  saveForm?: (form: IFormDefinition) => Promise<void>;
41
41
  loadFormTypes?: () => Promise<IFormType[]>;
42
42
  saveFormType?: (type: IFormType) => Promise<void>;
43
+ /**
44
+ * Optional — fetch the variable templates of a scope.
45
+ * When absent, the editor's "Variables" toolbox tab stays empty and
46
+ * shows the {@link IEditorLabels.toolbox.noTemplates} message.
47
+ */
48
+ loadTemplates?: (scopeId: string) => Promise<IVariableTemplate[]>;
43
49
  }
44
50
  /** Complete editor state */
45
51
  interface EditorState {
@@ -52,11 +58,18 @@ interface EditorState {
52
58
  history: HistoryEntry[];
53
59
  historyIndex: number;
54
60
  adapter: IEditorAdapter | null;
61
+ /**
62
+ * Variable templates surfaced to the "Variables" toolbox tab. Populated
63
+ * via `setAvailableTemplates` (typically by `FormEditor` after
64
+ * `adapter.loadTemplates(form.scopeId)`). Out of the undo/redo history —
65
+ * this is editor-UI state, not form state.
66
+ */
67
+ availableTemplates: IVariableTemplate[];
55
68
  }
56
69
  /** Editor actions */
57
70
  interface EditorActions {
58
71
  setForm: (form: IFormDefinition) => void;
59
- updateFormMeta: (updates: Partial<Pick<IFormDefinition, 'name' | 'code' | 'description' | 'formTypeId' | 'version'>>) => void;
72
+ updateFormMeta: (updates: Partial<Pick<IFormDefinition, 'name' | 'code' | 'description' | 'formTypeId' | 'version' | 'langConfig'>>) => void;
60
73
  addPage: (page: IFormPage) => void;
61
74
  updatePage: (pageCode: string, updates: Partial<IFormPage>) => void;
62
75
  removePage: (pageCode: string) => void;
@@ -67,7 +80,33 @@ interface EditorActions {
67
80
  addVariable: (pageCode: string, variable: IFormVariable, rosterCode?: string) => void;
68
81
  updateVariable: (pageCode: string, variableCode: string, updates: Partial<IFormVariable>, rosterCode?: string) => void;
69
82
  removeVariable: (pageCode: string, variableCode: string, rosterCode?: string) => void;
83
+ /**
84
+ * Insert a variable that inherits from a `IVariableTemplate`. Template
85
+ * fields are denormalised onto the new variable; `code` and `name` come
86
+ * from the template; `templateOverrides` starts empty.
87
+ */
88
+ addVariableFromTemplate: (pageCode: string, template: IVariableTemplate, rosterCode?: string) => void;
89
+ /**
90
+ * Override a single field of a template-linked variable. Writes the new
91
+ * value into `templateOverrides[key]` AND mirrors it on the variable so
92
+ * the canvas can render without invoking the resolver.
93
+ */
94
+ setVariableOverride: <K extends keyof IVariableTemplateOverrides>(pageCode: string, variableCode: string, key: K, value: IVariableTemplateOverrides[K], rosterCode?: string) => void;
95
+ /**
96
+ * Drop one override key — removes it from `templateOverrides` and
97
+ * restores the variable's mirrored field from the linked template.
98
+ */
99
+ resetVariableOverride: (pageCode: string, variableCode: string, key: keyof IVariableTemplateOverrides, rosterCode?: string) => void;
100
+ /**
101
+ * Cross-page / cross-roster move (kept for the existing dnd flow). Use
102
+ * {@link moveItem} for intra-page reordering of `page.items[]`.
103
+ */
70
104
  moveVariable: (fromPageCode: string, toPageCode: string, variableCode: string, newOrder: number, fromRosterCode?: string, toRosterCode?: string) => void;
105
+ /**
106
+ * Reorder the items of a page (variables + rosters mixed) by index.
107
+ * Updates each item's `order` to its new position.
108
+ */
109
+ moveItem: (pageCode: string, fromIndex: number, toIndex: number) => void;
71
110
  select: (selection: EditorSelection) => void;
72
111
  clearSelection: () => void;
73
112
  setView: (view: EditorView) => void;
@@ -78,6 +117,8 @@ interface EditorActions {
78
117
  addFormType: (type: Omit<IFormType, 'id'>) => void;
79
118
  updateFormType: (id: string, patch: Partial<IFormType>) => void;
80
119
  removeFormType: (id: string) => void;
120
+ /** Replace the variable-template list shown in the Variables tab. */
121
+ setAvailableTemplates: (templates: IVariableTemplate[]) => void;
81
122
  setAdapter: (adapter: IEditorAdapter) => void;
82
123
  save: () => Promise<void>;
83
124
  load: (formId: string) => Promise<void>;
@@ -86,6 +127,301 @@ interface EditorActions {
86
127
  }
87
128
  type EditorStore = EditorState & EditorActions;
88
129
 
130
+ /**
131
+ * IEditorLabels — full label dictionary for the FormEditor UI.
132
+ *
133
+ * Pattern A (zero external dependency): consumers pass a `labels` prop to
134
+ * FormEditor and the values flow through React context to every sub-component.
135
+ * French is the absolute default; English ships out of the box; any other
136
+ * language can be added by providing a (deep-)partial override.
137
+ *
138
+ * Functions in this interface format strings with runtime arguments (counts,
139
+ * names, indices). They are *replaced wholesale* by deepMerge, not merged.
140
+ */
141
+ type DeepPartial<T> = T extends (...args: never[]) => unknown ? T : T extends object ? {
142
+ [K in keyof T]?: DeepPartial<T[K]>;
143
+ } : T;
144
+ interface IEditorLabels {
145
+ toolbar: {
146
+ formNamePlaceholder: string;
147
+ unsaved: string;
148
+ addPage: string;
149
+ types: string;
150
+ manageFormTypesTitle: string;
151
+ undo: string;
152
+ redo: string;
153
+ save: string;
154
+ saving: string;
155
+ export: string;
156
+ import: string;
157
+ viewEditor: string;
158
+ viewPreview: string;
159
+ viewJson: string;
160
+ };
161
+ toolbox: {
162
+ searchPlaceholder: string;
163
+ addPageFirst: string;
164
+ /** Label of the first tab (existing variable/roster types). */
165
+ tabFields: string;
166
+ /** Label of the second tab (variable library from current scope). */
167
+ tabVariables: string;
168
+ /** Empty state for the Variables tab when the form has a scope but no templates. */
169
+ noTemplates: string;
170
+ /** Empty state for the Variables tab when the form has no scope at all. */
171
+ noScope: string;
172
+ groupText: string;
173
+ groupSelection: string;
174
+ groupDateTime: string;
175
+ groupMedia: string;
176
+ groupAdvanced: string;
177
+ /** Section label for the four roster variants, displayed after ADVANCED. */
178
+ groupRoster: string;
179
+ /** Roster type labels — surface the four supported variants. */
180
+ typeRosterCheck: string;
181
+ typeRosterList: string;
182
+ typeRosterCollection: string;
183
+ typeRosterCollectionExtend: string;
184
+ /**
185
+ * Shared variable-type labels. Re-used by both Toolbox(Simple) and
186
+ * VariableProperties (single source of truth).
187
+ */
188
+ types: {
189
+ text: string;
190
+ textarea: string;
191
+ number: string;
192
+ email: string;
193
+ select: string;
194
+ multiselect: string;
195
+ radio: string;
196
+ checkbox: string;
197
+ date: string;
198
+ datetime: string;
199
+ time: string;
200
+ file: string;
201
+ image: string;
202
+ gps: string;
203
+ rating: string;
204
+ calculated: string;
205
+ hidden: string;
206
+ label: string;
207
+ };
208
+ };
209
+ canvas: {
210
+ /** CanvasSimple — no page in form. */
211
+ emptyStateNoPage: string;
212
+ /** Canvas (dnd) — no page in form. */
213
+ emptyStateNoPageDnd: string;
214
+ /** Empty page — invites to add a field. */
215
+ emptyStatePage: string;
216
+ /** Fallback when page.name is blank. */
217
+ unnamedPage: string;
218
+ /** Badge on repeatable pages. */
219
+ repeatable: string;
220
+ /** "X fields · Y rosters" — count helper for the page header. */
221
+ fieldsRostersCount: (fields: number, rosters: number) => string;
222
+ /** "X fields" — count helper for the Canvas (dnd) page header. */
223
+ fieldsCount: (fields: number) => string;
224
+ /** "X vars" — count helper for the roster header. */
225
+ rosterVarsCount: (vars: number) => string;
226
+ /** Button: add a roster to the current page. */
227
+ addRoster: string;
228
+ /** Button: add a variable inside a roster. */
229
+ addFieldToRoster: string;
230
+ /** Prefix shown before the pilot variable code on a roster header. */
231
+ pilotPrefix: string;
232
+ /** Default name when auto-creating a roster. */
233
+ newRosterName: string;
234
+ /** Default name when auto-creating a roster variable. */
235
+ newFieldName: string;
236
+ /** Default name when auto-creating a toolbox-spawned variable.
237
+ * Receives the type label (e.g. "Texte court") and returns the full name. */
238
+ newVariableName: (typeLabel: string) => string;
239
+ /** Tooltip on the "has conditions" indicator. */
240
+ hasConditionsTitle: string;
241
+ /** Tooltip on the "data source connected" indicator. */
242
+ dataSourceConnectedTitle: string;
243
+ };
244
+ properties: {
245
+ /** Header of the right-hand properties panel. */
246
+ title: string;
247
+ name: string;
248
+ code: string;
249
+ description: string;
250
+ placeholder: string;
251
+ version: string;
252
+ formType: string;
253
+ noType: string;
254
+ formSummary: (pages: number, variables: number) => string;
255
+ pageSummary: (variables: number, rosters: number) => string;
256
+ rosterSummary: (variables: number, options: number) => string;
257
+ repeatablePage: string;
258
+ minInstances: string;
259
+ maxInstances: string;
260
+ controlVariableCode: string;
261
+ instanceLabel: string;
262
+ rosterType: string;
263
+ pilotVariableCode: string;
264
+ rosterTypes: {
265
+ check: string;
266
+ list: string;
267
+ collection: string;
268
+ collection_extend: string;
269
+ };
270
+ /** Banner title shown on top of VariableProperties when the variable links to a template. */
271
+ linkedTemplate: string;
272
+ /** Label for the read-only code field on a linked variable. */
273
+ templateCode: string;
274
+ /** Label for the read-only name field on a linked variable. */
275
+ templateName: string;
276
+ /** Per-override reset button label. */
277
+ resetOverride: string;
278
+ type: string;
279
+ ratingStyle: string;
280
+ ratingStyles: {
281
+ star: string;
282
+ number: string;
283
+ color: string;
284
+ };
285
+ layout: string;
286
+ startNewRow: string;
287
+ columnSpan: string;
288
+ columnSpanAuto: string;
289
+ columnSpanHalf: string;
290
+ columnSpanThird: string;
291
+ columnSpanFull: string;
292
+ required: string;
293
+ readonly: string;
294
+ hidden: string;
295
+ expression: string;
296
+ langConfigSection: string;
297
+ langConfigAvailable: string;
298
+ langConfigDefault: string;
299
+ langConfigStrategy: string;
300
+ langConfigStrategies: {
301
+ prop: string;
302
+ browser: string;
303
+ selector: string;
304
+ auto: string;
305
+ };
306
+ langConfigSelectorPosition: string;
307
+ langConfigPositions: {
308
+ top: string;
309
+ bottom: string;
310
+ };
311
+ langConfigConfirmRemove: (lang: string) => string;
312
+ langConfigConfirmYes: string;
313
+ langConfigConfirmNo: string;
314
+ };
315
+ optionsEditor: {
316
+ optionsCount: (count: number) => string;
317
+ noOptions: string;
318
+ optionLabelPlaceholder: string;
319
+ optionValuePlaceholder: string;
320
+ addOption: string;
321
+ optionLabelAria: (index: number) => string;
322
+ optionValueAria: (index: number) => string;
323
+ removeOptionAria: (index: number) => string;
324
+ };
325
+ conditionBuilder: {
326
+ conditionsCount: (count: number) => string;
327
+ addCondition: string;
328
+ rulesCount: (rules: number) => string;
329
+ addRule: string;
330
+ action: string;
331
+ match: string;
332
+ all: string;
333
+ any: string;
334
+ actions: {
335
+ show: string;
336
+ hide: string;
337
+ validate: string;
338
+ require: string;
339
+ readonly: string;
340
+ setValue: string;
341
+ };
342
+ operators: {
343
+ equals: string;
344
+ not_equals: string;
345
+ greater_than: string;
346
+ less_than: string;
347
+ greater_than_or_equal: string;
348
+ less_than_or_equal: string;
349
+ contains: string;
350
+ not_contains: string;
351
+ is_empty: string;
352
+ is_not_empty: string;
353
+ starts_with: string;
354
+ ends_with: string;
355
+ in: string;
356
+ not_in: string;
357
+ between: string;
358
+ regex: string;
359
+ };
360
+ variablePlaceholder: string;
361
+ valuePlaceholder: string;
362
+ errorMessage: string;
363
+ errorMessagePlaceholder: string;
364
+ targetVariableCode: string;
365
+ targetVariablePlaceholder: string;
366
+ valueToSet: string;
367
+ valueToSetPlaceholder: string;
368
+ };
369
+ dataSource: {
370
+ title: string;
371
+ connector: string;
372
+ none: string;
373
+ connectedTo: string;
374
+ dependencies: (count: number) => string;
375
+ selectVariable: string;
376
+ dependencyKeyPlaceholder: string;
377
+ addDependency: string;
378
+ };
379
+ formTypesDialog: {
380
+ dialogAriaLabel: string;
381
+ title: string;
382
+ closeAriaLabel: string;
383
+ noTypes: string;
384
+ confirmDelete: string;
385
+ deleteConfirm: string;
386
+ deleteCancel: string;
387
+ removeTypeAria: (name: string) => string;
388
+ newTypeNamePlaceholder: string;
389
+ newTypeCodePlaceholder: string;
390
+ addButton: string;
391
+ };
392
+ formEditor: {
393
+ previewMissing: string;
394
+ };
395
+ }
396
+
397
+ /**
398
+ * French labels for FormEditor — absolute default.
399
+ *
400
+ * Any consumer that does not pass a `labels` prop will see this content.
401
+ */
402
+
403
+ declare const frLabels: IEditorLabels;
404
+
405
+ /**
406
+ * English labels for FormEditor — opt-in via `labels` prop.
407
+ */
408
+
409
+ declare const enLabels: IEditorLabels;
410
+
411
+ interface EditorLabelsProviderProps {
412
+ labels: IEditorLabels;
413
+ children: ReactNode;
414
+ }
415
+ declare function EditorLabelsProvider({ labels, children, }: EditorLabelsProviderProps): react_jsx_runtime.JSX.Element;
416
+ /** Read the resolved labels. Defaults to French if no provider is mounted. */
417
+ declare function useLabels(): IEditorLabels;
418
+ /**
419
+ * Recursively merges `override` into `base`. Functions, arrays and primitives
420
+ * are replaced wholesale. Plain objects are merged key-by-key so a partial
421
+ * override never erases sibling keys.
422
+ */
423
+ declare function mergeLabels(base: IEditorLabels, override?: DeepPartial<IEditorLabels>): IEditorLabels;
424
+
89
425
  interface FormEditorProps {
90
426
  theme?: Record<string, unknown>;
91
427
  dataSources?: Record<string, IDataSourceConnector>;
@@ -94,6 +430,12 @@ interface FormEditorProps {
94
430
  formId?: string;
95
431
  onChange?: (form: IFormDefinition) => void;
96
432
  onSave?: () => void;
433
+ /**
434
+ * Localised labels for every visible string in the editor.
435
+ * Deep-merged with the French defaults — pass only the keys you want to
436
+ * override. Use {@link import('../i18n').enLabels} for English out of the box.
437
+ */
438
+ labels?: DeepPartial<IEditorLabels>;
97
439
  PreviewComponent?: ComponentType<{
98
440
  formSchema: IFormDefinition;
99
441
  theme?: Record<string, unknown>;
@@ -107,7 +449,7 @@ interface FormEditorProps {
107
449
  /** Wrapper around the entire editor (e.g. DndContext) */
108
450
  Wrapper?: ComponentType<any>;
109
451
  }
110
- declare function FormEditor({ theme, dataSources, adapter, initialForm, formId, onChange, onSave, PreviewComponent, ToolboxComponent, CanvasComponent, Wrapper, }: FormEditorProps): react_jsx_runtime.JSX.Element;
452
+ declare function FormEditor({ theme, dataSources, adapter, initialForm, formId, onChange, onSave, labels, PreviewComponent, ToolboxComponent, CanvasComponent, Wrapper, }: FormEditorProps): react_jsx_runtime.JSX.Element;
111
453
 
112
454
  declare const useEditorStore: zustand.UseBoundStore<zustand.StoreApi<EditorStore>>;
113
455
 
@@ -122,14 +464,19 @@ interface EditorToolbarProps {
122
464
  declare function EditorToolbar({ onSave, onExport, onImport }: EditorToolbarProps): react_jsx_runtime.JSX.Element;
123
465
 
124
466
  /**
125
- * Toolbox — left panel. Click to add variables to selected page.
126
- * No drag & drop dependency.
467
+ * Toolbox — left panel. Two tabs:
468
+ * - Champs : variable types + roster variants (original content).
469
+ * - Variables : reusable templates from the current scope.
470
+ *
471
+ * Click to add. No drag & drop dependency (see Toolbox.tsx for DnD).
127
472
  */
128
473
  declare function ToolboxSimple(): react_jsx_runtime.JSX.Element;
129
474
 
130
475
  /**
131
476
  * Canvas — center panel. Displays form structure.
132
- * Click to select. Supports roster creation and variable management.
477
+ * Click to select. Variables and rosters share a single ordered list
478
+ * (`page.items[]`) and are rendered together; rosters always break the
479
+ * current multi-column row and span the full width.
133
480
  */
134
481
  declare function CanvasSimple(): react_jsx_runtime.JSX.Element;
135
482
 
@@ -181,4 +528,4 @@ interface DataSourceSelectorProps {
181
528
  }
182
529
  declare function DataSourceSelector({ dataSourceId, dependencies, availableConnectors, availableVariables, onConnectorChange, onDependenciesChange, }: DataSourceSelectorProps): react_jsx_runtime.JSX.Element;
183
530
 
184
- export { CanvasSimple as Canvas, ConditionBuilder, type ConditionBuilderProps, DataSourceSelector, type DataSourceSelectorProps, type EditorActions, type EditorSelection, type EditorState, type EditorStore, EditorToolbar, type EditorView, FormEditor, type FormEditorProps, type HistoryEntry, type IEditorAdapter, PropertiesPanel, ToolboxSimple as Toolbox, useEditorStore };
531
+ export { CanvasSimple as Canvas, ConditionBuilder, type ConditionBuilderProps, DataSourceSelector, type DataSourceSelectorProps, type DeepPartial, type EditorActions, EditorLabelsProvider, type EditorSelection, type EditorState, type EditorStore, EditorToolbar, type EditorView, FormEditor, type FormEditorProps, type HistoryEntry, type IEditorAdapter, type IEditorLabels, PropertiesPanel, ToolboxSimple as Toolbox, frLabels as defaultLabels, enLabels, frLabels, mergeLabels, useEditorStore, useLabels };