@rettangoli/ui 1.0.0-rc13 → 1.0.0-rc15

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 (37) hide show
  1. package/dist/rettangoli-iife-layout.min.js +81 -49
  2. package/dist/rettangoli-iife-ui.min.js +150 -49
  3. package/package.json +10 -5
  4. package/src/common/dimensions.js +85 -0
  5. package/src/common/responsive.js +72 -0
  6. package/src/common.js +6 -4
  7. package/src/components/dropdownMenu/dropdownMenu.schema.yaml +1 -1
  8. package/src/components/form/form.handlers.js +328 -152
  9. package/src/components/form/form.methods.js +205 -0
  10. package/src/components/form/form.schema.yaml +16 -271
  11. package/src/components/form/form.store.js +535 -95
  12. package/src/components/form/form.view.yaml +73 -52
  13. package/src/components/globalUi/globalUi.handlers.js +4 -4
  14. package/src/components/popoverInput/popoverInput.handlers.js +64 -50
  15. package/src/components/popoverInput/popoverInput.schema.yaml +3 -1
  16. package/src/components/popoverInput/popoverInput.store.js +9 -3
  17. package/src/components/popoverInput/popoverInput.view.yaml +4 -4
  18. package/src/components/select/select.handlers.js +15 -19
  19. package/src/components/select/select.schema.yaml +2 -0
  20. package/src/components/select/select.store.js +8 -6
  21. package/src/components/select/select.view.yaml +4 -4
  22. package/src/components/sliderInput/sliderInput.handlers.js +15 -1
  23. package/src/components/sliderInput/sliderInput.schema.yaml +3 -0
  24. package/src/components/sliderInput/sliderInput.store.js +2 -1
  25. package/src/components/sliderInput/sliderInput.view.yaml +2 -2
  26. package/src/components/tooltip/tooltip.schema.yaml +1 -1
  27. package/src/deps/createGlobalUI.js +4 -4
  28. package/src/entry-iife-layout.js +6 -0
  29. package/src/entry-iife-ui.js +8 -0
  30. package/src/index.js +8 -0
  31. package/src/primitives/checkbox.js +295 -0
  32. package/src/primitives/input-date.js +31 -0
  33. package/src/primitives/input-datetime.js +31 -0
  34. package/src/primitives/input-time.js +31 -0
  35. package/src/primitives/input.js +43 -1
  36. package/src/primitives/textarea.js +3 -0
  37. package/src/primitives/view.js +6 -2
@@ -15,35 +15,74 @@ const encode = (input) => {
15
15
  return ""
16
16
  }
17
17
  return `"${escapeHtml(String(input))}"`;
18
+ };
19
+
20
+ const isObjectLike = (value) => value !== null && typeof value === "object";
21
+ const isPlainObject = (value) => isObjectLike(value) && !Array.isArray(value);
22
+ const isPathLike = (path) => typeof path === "string" && path.includes(".");
23
+ const hasBracketPathToken = (path) => typeof path === "string" && /[\[\]]/.test(path);
24
+
25
+ function pickByPaths(obj, paths) {
26
+ const result = {};
27
+ for (const path of paths) {
28
+ if (typeof path !== "string" || path.length === 0) continue;
29
+ const value = get(obj, path);
30
+ if (value !== undefined) {
31
+ set(result, path, value);
32
+ }
33
+ }
34
+ return result;
18
35
  }
19
36
 
20
- function pick(obj, keys) {
21
- return keys.reduce((acc, key) => {
22
- if (key in obj) acc[key] = obj[key];
23
- return acc;
24
- }, {});
37
+ function normalizeWhenDirectives(form) {
38
+ if (!isPlainObject(form) || !Array.isArray(form.fields)) {
39
+ return form;
40
+ }
41
+
42
+ const normalizeFields = (fields = []) =>
43
+ fields.map((field) => {
44
+ if (!isPlainObject(field)) {
45
+ return field;
46
+ }
47
+
48
+ if (typeof field.$when === "string" && field.$when.trim().length > 0) {
49
+ const { $when, ...rest } = field;
50
+ const normalizedField = Array.isArray(rest.fields)
51
+ ? { ...rest, fields: normalizeFields(rest.fields) }
52
+ : rest;
53
+ return {
54
+ [`$if ${$when}`]: normalizedField,
55
+ };
56
+ }
57
+
58
+ if (Array.isArray(field.fields)) {
59
+ return {
60
+ ...field,
61
+ fields: normalizeFields(field.fields),
62
+ };
63
+ }
64
+
65
+ return field;
66
+ });
67
+
68
+ return {
69
+ ...form,
70
+ fields: normalizeFields(form.fields),
71
+ };
25
72
  }
26
73
 
27
- export const createInitialState = () => Object.freeze({
28
- formValues: {},
29
- tooltipState: {
30
- open: false,
31
- x: 0,
32
- y: 0,
33
- content: ''
34
- },
35
- });
36
-
37
- // Lodash-like utility functions for nested property access
38
- const get = (obj, path, defaultValue = undefined) => {
39
- if (!path) {
40
- return;
41
- }
42
- const keys = path.split(/[\[\].]/).filter((key) => key !== "");
74
+ // Nested property access utilities
75
+ export const get = (obj, path, defaultValue = undefined) => {
76
+ if (!path) return defaultValue;
77
+ if (!isObjectLike(obj)) return defaultValue;
78
+ if (hasBracketPathToken(path)) return defaultValue;
79
+ const keys = path.split(".").filter((key) => key !== "");
43
80
  let current = obj;
44
-
45
81
  for (const key of keys) {
46
82
  if (current === null || current === undefined || !(key in current)) {
83
+ if (Object.prototype.hasOwnProperty.call(obj, path)) {
84
+ return obj[path];
85
+ }
47
86
  return defaultValue;
48
87
  }
49
88
  current = current[key];
@@ -51,16 +90,21 @@ const get = (obj, path, defaultValue = undefined) => {
51
90
  return current;
52
91
  };
53
92
 
54
- const set = (obj, path, value) => {
55
- const keys = path.split(/[\[\].]/).filter((key) => key !== "");
56
-
57
- // If path contains array notation, delete the original flat key
58
- if (path.includes("[") && path in obj) {
93
+ export const set = (obj, path, value) => {
94
+ if (!isObjectLike(obj) || typeof path !== "string" || path.length === 0) {
95
+ return obj;
96
+ }
97
+ if (hasBracketPathToken(path)) {
98
+ return obj;
99
+ }
100
+ const keys = path.split(".").filter((key) => key !== "");
101
+ if (keys.length === 0) {
102
+ return obj;
103
+ }
104
+ if (isPathLike(path) && Object.prototype.hasOwnProperty.call(obj, path)) {
59
105
  delete obj[path];
60
106
  }
61
-
62
107
  let current = obj;
63
-
64
108
  for (let i = 0; i < keys.length - 1; i++) {
65
109
  const key = keys[i];
66
110
  if (
@@ -68,19 +112,15 @@ const set = (obj, path, value) => {
68
112
  typeof current[key] !== "object" ||
69
113
  current[key] === null
70
114
  ) {
71
- // Check if next key is a number to create array
72
- const nextKey = keys[i + 1];
73
- const isArrayIndex = /^\d+$/.test(nextKey);
74
- current[key] = isArrayIndex ? [] : {};
115
+ current[key] = {};
75
116
  }
76
117
  current = current[key];
77
118
  }
78
-
79
119
  current[keys[keys.length - 1]] = value;
80
120
  return obj;
81
121
  };
82
122
 
83
- const blacklistedAttrs = ["id", "class", "style", "slot", "form", "defaultValues", "context", "autofocus", "key"];
123
+ const blacklistedAttrs = ["id", "class", "style", "slot", "form", "defaultValues", "disabled"];
84
124
 
85
125
  const stringifyAttrs = (props = {}) => {
86
126
  return Object.entries(props)
@@ -89,100 +129,500 @@ const stringifyAttrs = (props = {}) => {
89
129
  .join(" ");
90
130
  };
91
131
 
92
- export const selectForm = ({ props }) => {
93
- const { form = {} } = props;
94
- const { context } = props;
132
+ // --- Validation ---
133
+
134
+ const PATTERN_PRESETS = {
135
+ email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
136
+ url: /^https?:\/\/.+/,
137
+ };
138
+
139
+ const DEFAULT_MESSAGES = {
140
+ required: "This field is required",
141
+ minLength: (val) => `Must be at least ${val} characters`,
142
+ maxLength: (val) => `Must be at most ${val} characters`,
143
+ pattern: "Invalid format",
144
+ invalidDate: "Invalid date format",
145
+ invalidTime: "Invalid time format",
146
+ invalidDateTime: "Invalid date and time format",
147
+ minTemporal: (val) => `Must be on or after ${val}`,
148
+ maxTemporal: (val) => `Must be on or before ${val}`,
149
+ };
150
+
151
+ const DATE_FIELD_TYPE = "input-date";
152
+ const TIME_FIELD_TYPE = "input-time";
153
+ const DATETIME_FIELD_TYPE = "input-datetime";
154
+
155
+ const DATE_REGEX = /^(\d{4})-(\d{2})-(\d{2})$/;
156
+ const TIME_REGEX = /^(\d{2}):(\d{2})(?::(\d{2}))?$/;
157
+ const DATETIME_REGEX = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}(?::\d{2})?)$/;
158
+
159
+ const parseDateParts = (value) => {
160
+ if (typeof value !== "string") return null;
161
+ const match = DATE_REGEX.exec(value);
162
+ if (!match) return null;
163
+
164
+ const year = Number(match[1]);
165
+ const month = Number(match[2]);
166
+ const day = Number(match[3]);
167
+ if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) {
168
+ return null;
169
+ }
170
+ if (month < 1 || month > 12 || day < 1 || day > 31) {
171
+ return null;
172
+ }
173
+
174
+ const date = new Date(Date.UTC(year, month - 1, day));
175
+ const valid = date.getUTCFullYear() === year
176
+ && date.getUTCMonth() === month - 1
177
+ && date.getUTCDate() === day;
178
+ if (!valid) return null;
179
+
180
+ return { year, month, day };
181
+ };
182
+
183
+ const parseTimeParts = (value) => {
184
+ if (typeof value !== "string") return null;
185
+ const match = TIME_REGEX.exec(value);
186
+ if (!match) return null;
187
+
188
+ const hour = Number(match[1]);
189
+ const minute = Number(match[2]);
190
+ const second = match[3] === undefined ? 0 : Number(match[3]);
191
+ if (!Number.isInteger(hour) || !Number.isInteger(minute) || !Number.isInteger(second)) {
192
+ return null;
193
+ }
194
+ if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59) {
195
+ return null;
196
+ }
197
+
198
+ return { hour, minute, second };
199
+ };
200
+
201
+ const normalizeTimeComparable = (value) => {
202
+ const parts = parseTimeParts(value);
203
+ if (!parts) return null;
204
+ return `${String(parts.hour).padStart(2, "0")}:${String(parts.minute).padStart(2, "0")}:${String(parts.second).padStart(2, "0")}`;
205
+ };
206
+
207
+ const normalizeDateComparable = (value) => {
208
+ return parseDateParts(value) ? value : null;
209
+ };
210
+
211
+ const normalizeDateTimeComparable = (value) => {
212
+ if (typeof value !== "string") return null;
213
+ const match = DATETIME_REGEX.exec(value);
214
+ if (!match) return null;
215
+
216
+ const date = normalizeDateComparable(match[1]);
217
+ const time = normalizeTimeComparable(match[2]);
218
+ if (!date || !time) return null;
219
+
220
+ return `${date}T${time}`;
221
+ };
222
+
223
+ const getTemporalNormalization = (fieldType) => {
224
+ if (fieldType === DATE_FIELD_TYPE) {
225
+ return {
226
+ normalize: normalizeDateComparable,
227
+ invalidMessage: DEFAULT_MESSAGES.invalidDate,
228
+ };
229
+ }
230
+ if (fieldType === TIME_FIELD_TYPE) {
231
+ return {
232
+ normalize: normalizeTimeComparable,
233
+ invalidMessage: DEFAULT_MESSAGES.invalidTime,
234
+ };
235
+ }
236
+ if (fieldType === DATETIME_FIELD_TYPE) {
237
+ return {
238
+ normalize: normalizeDateTimeComparable,
239
+ invalidMessage: DEFAULT_MESSAGES.invalidDateTime,
240
+ };
241
+ }
242
+ return null;
243
+ };
244
+
245
+ const validateTemporalField = (field, value) => {
246
+ const temporal = getTemporalNormalization(field.type);
247
+ if (!temporal) return null;
248
+
249
+ if (value === undefined || value === null || value === "") {
250
+ return null;
251
+ }
252
+
253
+ const comparableValue = temporal.normalize(String(value));
254
+ if (!comparableValue) {
255
+ return temporal.invalidMessage;
256
+ }
257
+
258
+ if (field.min !== undefined && field.min !== null && String(field.min) !== "") {
259
+ const minComparable = temporal.normalize(String(field.min));
260
+ if (minComparable && comparableValue < minComparable) {
261
+ return DEFAULT_MESSAGES.minTemporal(String(field.min));
262
+ }
263
+ }
264
+
265
+ if (field.max !== undefined && field.max !== null && String(field.max) !== "") {
266
+ const maxComparable = temporal.normalize(String(field.max));
267
+ if (maxComparable && comparableValue > maxComparable) {
268
+ return DEFAULT_MESSAGES.maxTemporal(String(field.max));
269
+ }
270
+ }
271
+
272
+ return null;
273
+ };
274
+
275
+ export const validateField = (field, value) => {
276
+ // Check required
277
+ if (field.required) {
278
+ const isEmpty =
279
+ value === undefined ||
280
+ value === null ||
281
+ value === "" ||
282
+ (typeof value === "boolean" && value === false);
283
+ // For numbers, 0 is a valid value
284
+ const isEmptyNumber = field.type === "input-number" && value === null;
285
+ const shouldFail = field.type === "input-number" ? isEmptyNumber : isEmpty;
286
+
287
+ if (shouldFail) {
288
+ if (typeof field.required === "object" && field.required.message) {
289
+ return field.required.message;
290
+ }
291
+ return DEFAULT_MESSAGES.required;
292
+ }
293
+ }
294
+
295
+ const temporalError = validateTemporalField(field, value);
296
+ if (temporalError) {
297
+ return temporalError;
298
+ }
299
+
300
+ // Check rules
301
+ if (Array.isArray(field.rules)) {
302
+ for (const rule of field.rules) {
303
+ const error = validateRule(rule, value);
304
+ if (error) return error;
305
+ }
306
+ }
307
+
308
+ return null;
309
+ };
310
+
311
+ const validateRule = (rule, value) => {
312
+ // Skip validation on empty values (required handles that)
313
+ if (value === undefined || value === null || value === "") return null;
95
314
 
96
- if (context) {
97
- const result = parseAndRender(form, context);
98
- return result
315
+ const strValue = String(value);
316
+
317
+ switch (rule.rule) {
318
+ case "minLength": {
319
+ if (strValue.length < rule.value) {
320
+ return rule.message || DEFAULT_MESSAGES.minLength(rule.value);
321
+ }
322
+ return null;
323
+ }
324
+ case "maxLength": {
325
+ if (strValue.length > rule.value) {
326
+ return rule.message || DEFAULT_MESSAGES.maxLength(rule.value);
327
+ }
328
+ return null;
329
+ }
330
+ case "pattern": {
331
+ const preset = PATTERN_PRESETS[rule.value];
332
+ let regex = preset;
333
+ if (!regex) {
334
+ try {
335
+ regex = new RegExp(rule.value);
336
+ } catch {
337
+ return rule.message || DEFAULT_MESSAGES.pattern;
338
+ }
339
+ }
340
+ if (!regex.test(strValue)) {
341
+ return rule.message || DEFAULT_MESSAGES.pattern;
342
+ }
343
+ return null;
344
+ }
345
+ default:
346
+ return null;
347
+ }
348
+ };
349
+
350
+ export const validateForm = (fields, formValues) => {
351
+ const errors = {};
352
+ const dataFields = collectAllDataFields(fields);
353
+
354
+ for (const field of dataFields) {
355
+ const value = get(formValues, field.name);
356
+ const error = validateField(field, value);
357
+ if (error) {
358
+ errors[field.name] = error;
359
+ }
360
+ }
361
+
362
+ return {
363
+ valid: Object.keys(errors).length === 0,
364
+ errors,
365
+ };
366
+ };
367
+
368
+ // --- Field helpers ---
369
+
370
+ const DISPLAY_TYPES = ["section", "read-only-text", "slot"];
371
+
372
+ export const isDataField = (field) => {
373
+ return !DISPLAY_TYPES.includes(field.type);
374
+ };
375
+
376
+ export const collectAllDataFields = (fields) => {
377
+ const result = [];
378
+ for (const field of fields) {
379
+ if (field.type === "section" && Array.isArray(field.fields)) {
380
+ result.push(...collectAllDataFields(field.fields));
381
+ } else if (isDataField(field)) {
382
+ result.push(field);
383
+ }
384
+ }
385
+ return result;
386
+ };
387
+
388
+ export const getDefaultValue = (field) => {
389
+ switch (field.type) {
390
+ case "input-text":
391
+ case "input-date":
392
+ case "input-time":
393
+ case "input-datetime":
394
+ case "input-textarea":
395
+ case "popover-input":
396
+ return "";
397
+ case "input-number":
398
+ return null;
399
+ case "select":
400
+ return null;
401
+ case "checkbox":
402
+ return false;
403
+ case "color-picker":
404
+ return "#000000";
405
+ case "slider":
406
+ case "slider-with-input":
407
+ return field.min !== undefined ? field.min : 0;
408
+ case "image":
409
+ return null;
410
+ default:
411
+ return null;
412
+ }
413
+ };
414
+
415
+ export const flattenFields = (fields, startIdx = 0) => {
416
+ const result = [];
417
+ let idx = startIdx;
418
+
419
+ for (const field of fields) {
420
+ if (field.type === "section") {
421
+ result.push({
422
+ ...field,
423
+ _isSection: true,
424
+ _idx: idx,
425
+ });
426
+ idx++;
427
+ if (Array.isArray(field.fields)) {
428
+ const nested = flattenFields(field.fields, idx);
429
+ result.push(...nested);
430
+ idx += nested.length;
431
+ }
432
+ } else {
433
+ result.push({
434
+ ...field,
435
+ _isSection: false,
436
+ _idx: idx,
437
+ });
438
+ idx++;
439
+ }
99
440
  }
100
441
 
101
- return form;
442
+ return result;
102
443
  };
103
444
 
445
+ // --- Store ---
446
+
447
+ export const createInitialState = () =>
448
+ Object.freeze({
449
+ formValues: {},
450
+ errors: {},
451
+ reactiveMode: false,
452
+ tooltipState: {
453
+ open: false,
454
+ x: 0,
455
+ y: 0,
456
+ content: "",
457
+ },
458
+ });
459
+
460
+ export const selectForm = ({ state, props }) => {
461
+ const { form = {} } = props || {};
462
+ const normalizedForm = normalizeWhenDirectives(form);
463
+ const context = isPlainObject(props?.context) ? props.context : {};
464
+ const stateFormValues = isPlainObject(state?.formValues)
465
+ ? state.formValues
466
+ : {};
467
+ const mergedContext = {
468
+ ...context,
469
+ ...stateFormValues,
470
+ formValues: stateFormValues,
471
+ };
472
+
473
+ if (Object.keys(mergedContext).length > 0) {
474
+ return parseAndRender(normalizedForm, mergedContext);
475
+ }
476
+ return normalizedForm;
477
+ };
104
478
 
105
479
  export const selectViewData = ({ state, props }) => {
106
480
  const containerAttrString = stringifyAttrs(props);
107
-
108
481
  const form = selectForm({ state, props });
109
- const fields = structuredClone(form.fields || []);
110
- fields.forEach((field) => {
111
- // Use formValues from state if available, otherwise fall back to defaultValues from props
112
- const defaultValue = get(state.formValues, field.name)
113
- if (["read-only-text"].includes(field.inputType)) {
114
- field.defaultValue = defaultValue
115
- }
116
- if (["select"].includes(field.inputType)) {
117
- const defaultValues = props?.defaultValues;
118
- if (defaultValues && defaultValues[field.name] !== undefined) {
119
- field.selectedValue = defaultValues[field.name];
120
- }
482
+ const fields = form.fields || [];
483
+ const formDisabled = !!props?.disabled;
484
+
485
+ // Flatten fields for template iteration
486
+ const flatFields = flattenFields(fields);
487
+
488
+ // Enrich each field with computed properties
489
+ flatFields.forEach((field, arrIdx) => {
490
+ field._arrIdx = arrIdx;
491
+
492
+ if (field._isSection) return;
493
+
494
+ const isData = isDataField(field);
495
+ field._disabled = formDisabled || !!field.disabled;
496
+
497
+ if (isData && field.name) {
498
+ field._error = state.errors[field.name] || null;
499
+ }
500
+
501
+ // Type-specific computed props
502
+ if (field.type === "input-text") {
503
+ field._inputType = field.inputType || "text";
121
504
  }
122
- if (field.inputType === "image") {
123
- const src = field.src;
124
- // Only set imageSrc if src exists and is not empty
125
- field.imageSrc = src && src.trim() ? src : null;
126
- // Set placeholder text
127
- field.placeholderText = field.placeholder || "No Image";
505
+
506
+ if (field.type === "select") {
507
+ const val = get(state.formValues, field.name);
508
+ field._selectedValue = val !== undefined ? val : null;
509
+ field.placeholder = field.placeholder || "";
510
+ // clearable defaults to true; noClear is the inverse
511
+ field.noClear = field.clearable === false;
512
+ }
513
+
514
+ if (field.type === "image") {
515
+ const src = get(state.formValues, field.name);
516
+ field._imageSrc = src && String(src).trim() ? src : null;
517
+ field.placeholderText = field.placeholderText || "No Image";
518
+ }
519
+
520
+ if (field.type === "read-only-text") {
521
+ field.content = field.content || "";
128
522
  }
129
- if (field.inputType === "waveform") {
130
- const waveformData = field.waveformData;
131
- // Only set waveformData if it exists
132
- field.waveformData = waveformData || null;
133
- // Set placeholder text
134
- field.placeholderText = field.placeholder || "No Waveform";
523
+
524
+ if (field.type === "checkbox") {
525
+ const inlineText = typeof field.content === "string"
526
+ ? field.content
527
+ : (typeof field.checkboxLabel === "string" ? field.checkboxLabel : "");
528
+ field._checkboxText = inlineText;
135
529
  }
136
530
  });
137
531
 
532
+ // Actions
533
+ const actions = form.actions || { buttons: [] };
534
+ const layout = actions.layout || "split";
535
+ const buttons = (actions.buttons || []).map((btn, i) => ({
536
+ ...btn,
537
+ _globalIdx: i,
538
+ variant: btn.variant || "se",
539
+ _disabled: formDisabled || !!btn.disabled,
540
+ pre: btn.pre || "",
541
+ suf: btn.suf || "",
542
+ }));
543
+
544
+ let actionsData;
545
+ if (layout === "split") {
546
+ actionsData = {
547
+ _layout: "split",
548
+ buttons,
549
+ _leftButtons: buttons.filter((b) => b.align === "left"),
550
+ _rightButtons: buttons.filter((b) => b.align !== "left"),
551
+ };
552
+ } else {
553
+ actionsData = {
554
+ _layout: layout,
555
+ buttons,
556
+ _allButtons: buttons,
557
+ };
558
+ }
559
+
138
560
  return {
139
- key: props?.key,
140
561
  containerAttrString,
141
562
  title: form?.title || "",
142
563
  description: form?.description || "",
143
- fields: fields,
144
- actions: props?.form?.actions || {
145
- buttons: [],
146
- },
564
+ flatFields,
565
+ actions: actionsData,
147
566
  formValues: state.formValues,
148
567
  tooltipState: state.tooltipState,
149
568
  };
150
569
  };
151
570
 
152
- export const selectState = ({ state }) => {
153
- return state;
154
- };
155
-
156
571
  export const selectFormValues = ({ state, props }) => {
157
572
  const form = selectForm({ state, props });
158
-
159
- return pick(
573
+ const dataFields = collectAllDataFields(form.fields || []);
574
+ return pickByPaths(
160
575
  state.formValues,
161
- form.fields.map((field) => field.name),
576
+ dataFields.map((f) => f.name).filter((name) => typeof name === "string" && name.length > 0),
162
577
  );
163
578
  };
164
579
 
165
- export const getFormFieldValue = ({ state }, name) => {
166
- return get(state.formValues, name);
167
- };
168
-
169
- export const setFormValues = ({ state }, payload = {}) => {
170
- state.formValues = payload.formValues || {};
171
- };
172
-
173
580
  export const setFormFieldValue = ({ state, props }, payload = {}) => {
174
581
  const { name, value } = payload;
175
- if (!name) {
176
- return;
177
- }
582
+ if (!name) return;
178
583
  set(state.formValues, name, value);
179
- // remove non visible values
584
+ pruneHiddenValues({ state, props });
585
+ };
586
+
587
+ export const pruneHiddenValues = ({ state, props }) => {
588
+ if (!props) return;
589
+ // Prune to only visible field names
180
590
  const form = selectForm({ state, props });
181
- const formValues = pick(
591
+ const dataFields = collectAllDataFields(form.fields || []);
592
+ state.formValues = pickByPaths(
182
593
  state.formValues,
183
- form.fields.map((field) => field.name),
594
+ dataFields.map((f) => f.name).filter((name) => typeof name === "string" && name.length > 0),
184
595
  );
185
- state.formValues = formValues;
596
+ };
597
+
598
+ export const setFormValues = ({ state }, payload = {}) => {
599
+ const { values } = payload;
600
+ if (!values || typeof values !== "object") return;
601
+ Object.keys(values).forEach((key) => {
602
+ set(state.formValues, key, values[key]);
603
+ });
604
+ };
605
+
606
+ export const resetFormValues = ({ state }, payload = {}) => {
607
+ const { defaultValues = {} } = payload;
608
+ state.formValues = defaultValues ? structuredClone(defaultValues) : {};
609
+ state.errors = {};
610
+ state.reactiveMode = false;
611
+ };
612
+
613
+ export const setErrors = ({ state }, payload = {}) => {
614
+ state.errors = payload.errors || {};
615
+ };
616
+
617
+ export const clearFieldError = ({ state }, payload = {}) => {
618
+ const { name } = payload;
619
+ if (name && state.errors[name]) {
620
+ delete state.errors[name];
621
+ }
622
+ };
623
+
624
+ export const setReactiveMode = ({ state }) => {
625
+ state.reactiveMode = true;
186
626
  };
187
627
 
188
628
  export const showTooltip = ({ state }, payload = {}) => {
@@ -198,6 +638,6 @@ export const showTooltip = ({ state }, payload = {}) => {
198
638
  export const hideTooltip = ({ state }) => {
199
639
  state.tooltipState = {
200
640
  ...state.tooltipState,
201
- open: false
641
+ open: false,
202
642
  };
203
643
  };