@bgroup/wise-form 1.0.3 → 1.0.5

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 (269) hide show
  1. package/README.md +1 -1
  2. package/package.json +23 -4
  3. package/postcss.config.js +6 -0
  4. package/src/form/styles.css +11 -37
  5. package/src/form/view/components/containers/index.tsx +33 -4
  6. package/src/form/view/components/error.tsx +0 -3
  7. package/src/form/view/components/field/container.tsx +1 -1
  8. package/src/form/view/components/field/index.tsx +42 -7
  9. package/src/form/view/components/rows/row-container.tsx +37 -9
  10. package/src/form/view/components/rows/wrapper.tsx +17 -6
  11. package/src/form/view/components/wrapped-form.tsx +38 -5
  12. package/src/form/view/hooks/use-model.ts +91 -25
  13. package/src/form/view/index.tsx +15 -3
  14. package/src/models/field.ts +504 -538
  15. package/tailwind.config.js +11 -0
  16. package/tsconfig.json +2 -0
  17. package/vite.config.ts +59 -0
  18. package/dist/components/ui/Checkbox.d.ts +0 -14
  19. package/dist/components/ui/Checkbox.d.ts.map +0 -1
  20. package/dist/components/ui/Checkbox.js +0 -43
  21. package/dist/components/ui/Checkbox.js.map +0 -1
  22. package/dist/components/ui/CheckboxGroup.d.ts +0 -15
  23. package/dist/components/ui/CheckboxGroup.d.ts.map +0 -1
  24. package/dist/components/ui/CheckboxGroup.js +0 -33
  25. package/dist/components/ui/CheckboxGroup.js.map +0 -1
  26. package/dist/components/ui/Input.d.ts +0 -14
  27. package/dist/components/ui/Input.d.ts.map +0 -1
  28. package/dist/components/ui/Input.js +0 -49
  29. package/dist/components/ui/Input.js.map +0 -1
  30. package/dist/components/ui/Radio.d.ts +0 -14
  31. package/dist/components/ui/Radio.d.ts.map +0 -1
  32. package/dist/components/ui/Radio.js +0 -43
  33. package/dist/components/ui/Radio.js.map +0 -1
  34. package/dist/components/ui/Select.d.ts +0 -18
  35. package/dist/components/ui/Select.d.ts.map +0 -1
  36. package/dist/components/ui/Select.js +0 -44
  37. package/dist/components/ui/Select.js.map +0 -1
  38. package/dist/components/ui/Textarea.d.ts +0 -13
  39. package/dist/components/ui/Textarea.d.ts.map +0 -1
  40. package/dist/components/ui/Textarea.js +0 -42
  41. package/dist/components/ui/Textarea.js.map +0 -1
  42. package/dist/components/ui/index.d.ts +0 -13
  43. package/dist/components/ui/index.d.ts.map +0 -1
  44. package/dist/components/ui/index.js +0 -7
  45. package/dist/components/ui/index.js.map +0 -1
  46. package/dist/form/index.d.ts +0 -10
  47. package/dist/form/index.d.ts.map +0 -1
  48. package/dist/form/index.js +0 -5
  49. package/dist/form/index.js.map +0 -1
  50. package/dist/form/interfaces/field-container.d.ts +0 -8
  51. package/dist/form/interfaces/field-container.d.ts.map +0 -1
  52. package/dist/form/interfaces/field-container.js +0 -2
  53. package/dist/form/interfaces/field-container.js.map +0 -1
  54. package/dist/form/interfaces/interfaces.d.ts +0 -8
  55. package/dist/form/interfaces/interfaces.d.ts.map +0 -1
  56. package/dist/form/interfaces/interfaces.js +0 -2
  57. package/dist/form/interfaces/interfaces.js.map +0 -1
  58. package/dist/form/interfaces/settings.d.ts +0 -10
  59. package/dist/form/interfaces/settings.d.ts.map +0 -1
  60. package/dist/form/interfaces/settings.js +0 -2
  61. package/dist/form/interfaces/settings.js.map +0 -1
  62. package/dist/form/interfaces/template.d.ts +0 -6
  63. package/dist/form/interfaces/template.d.ts.map +0 -1
  64. package/dist/form/interfaces/template.js +0 -2
  65. package/dist/form/interfaces/template.js.map +0 -1
  66. package/dist/form/interfaces/wise-form-specs.d.ts +0 -9
  67. package/dist/form/interfaces/wise-form-specs.d.ts.map +0 -1
  68. package/dist/form/interfaces/wise-form-specs.js +0 -2
  69. package/dist/form/interfaces/wise-form-specs.js.map +0 -1
  70. package/dist/form/view/components/containers/index.d.ts +0 -3
  71. package/dist/form/view/components/containers/index.d.ts.map +0 -1
  72. package/dist/form/view/components/containers/index.js +0 -12
  73. package/dist/form/view/components/containers/index.js.map +0 -1
  74. package/dist/form/view/components/error.d.ts +0 -5
  75. package/dist/form/view/components/error.d.ts.map +0 -1
  76. package/dist/form/view/components/error.js +0 -8
  77. package/dist/form/view/components/error.js.map +0 -1
  78. package/dist/form/view/components/field/container.d.ts +0 -5
  79. package/dist/form/view/components/field/container.d.ts.map +0 -1
  80. package/dist/form/view/components/field/container.js +0 -5
  81. package/dist/form/view/components/field/container.js.map +0 -1
  82. package/dist/form/view/components/field/index.d.ts +0 -18
  83. package/dist/form/view/components/field/index.d.ts.map +0 -1
  84. package/dist/form/view/components/field/index.js +0 -89
  85. package/dist/form/view/components/field/index.js.map +0 -1
  86. package/dist/form/view/components/field/selection.d.ts +0 -2
  87. package/dist/form/view/components/field/selection.d.ts.map +0 -1
  88. package/dist/form/view/components/field/selection.js +0 -35
  89. package/dist/form/view/components/field/selection.js.map +0 -1
  90. package/dist/form/view/components/field/use-field.d.ts +0 -4
  91. package/dist/form/view/components/field/use-field.d.ts.map +0 -1
  92. package/dist/form/view/components/field/use-field.js +0 -41
  93. package/dist/form/view/components/field/use-field.js.map +0 -1
  94. package/dist/form/view/components/rows/row-container.d.ts +0 -18
  95. package/dist/form/view/components/rows/row-container.d.ts.map +0 -1
  96. package/dist/form/view/components/rows/row-container.js +0 -89
  97. package/dist/form/view/components/rows/row-container.js.map +0 -1
  98. package/dist/form/view/components/rows/wrapper.d.ts +0 -12
  99. package/dist/form/view/components/rows/wrapper.d.ts.map +0 -1
  100. package/dist/form/view/components/rows/wrapper.js +0 -27
  101. package/dist/form/view/components/rows/wrapper.js.map +0 -1
  102. package/dist/form/view/components/wrapped-form.d.ts +0 -6
  103. package/dist/form/view/components/wrapped-form.d.ts.map +0 -1
  104. package/dist/form/view/components/wrapped-form.js +0 -26
  105. package/dist/form/view/components/wrapped-form.js.map +0 -1
  106. package/dist/form/view/context.d.ts +0 -23
  107. package/dist/form/view/context.d.ts.map +0 -1
  108. package/dist/form/view/context.js +0 -7
  109. package/dist/form/view/context.js.map +0 -1
  110. package/dist/form/view/hooks/use-model.d.ts +0 -10
  111. package/dist/form/view/hooks/use-model.d.ts.map +0 -1
  112. package/dist/form/view/hooks/use-model.js +0 -31
  113. package/dist/form/view/hooks/use-model.js.map +0 -1
  114. package/dist/form/view/hooks/use-template.d.ts +0 -14
  115. package/dist/form/view/hooks/use-template.d.ts.map +0 -1
  116. package/dist/form/view/hooks/use-template.js +0 -57
  117. package/dist/form/view/hooks/use-template.js.map +0 -1
  118. package/dist/form/view/hooks/use-types.d.ts +0 -2
  119. package/dist/form/view/hooks/use-types.d.ts.map +0 -1
  120. package/dist/form/view/hooks/use-types.js +0 -19
  121. package/dist/form/view/hooks/use-types.js.map +0 -1
  122. package/dist/form/view/index.d.ts +0 -3
  123. package/dist/form/view/index.d.ts.map +0 -1
  124. package/dist/form/view/index.js +0 -38
  125. package/dist/form/view/index.js.map +0 -1
  126. package/dist/formulas/helpers/condition-types.d.ts +0 -5
  127. package/dist/formulas/helpers/condition-types.d.ts.map +0 -1
  128. package/dist/formulas/helpers/condition-types.js +0 -5
  129. package/dist/formulas/helpers/condition-types.js.map +0 -1
  130. package/dist/formulas/helpers/evaluations.d.ts +0 -15
  131. package/dist/formulas/helpers/evaluations.d.ts.map +0 -1
  132. package/dist/formulas/helpers/evaluations.js +0 -44
  133. package/dist/formulas/helpers/evaluations.js.map +0 -1
  134. package/dist/formulas/helpers/formula.d.ts +0 -6
  135. package/dist/formulas/helpers/formula.d.ts.map +0 -1
  136. package/dist/formulas/helpers/formula.js +0 -26
  137. package/dist/formulas/helpers/formula.js.map +0 -1
  138. package/dist/formulas/helpers/lexer.d.ts +0 -10
  139. package/dist/formulas/helpers/lexer.d.ts.map +0 -1
  140. package/dist/formulas/helpers/lexer.js +0 -73
  141. package/dist/formulas/helpers/lexer.js.map +0 -1
  142. package/dist/formulas/helpers/parser.d.ts +0 -24
  143. package/dist/formulas/helpers/parser.d.ts.map +0 -1
  144. package/dist/formulas/helpers/parser.js +0 -48
  145. package/dist/formulas/helpers/parser.js.map +0 -1
  146. package/dist/formulas/helpers/token.d.ts +0 -14
  147. package/dist/formulas/helpers/token.d.ts.map +0 -1
  148. package/dist/formulas/helpers/token.js +0 -14
  149. package/dist/formulas/helpers/token.js.map +0 -1
  150. package/dist/formulas/index.d.ts +0 -59
  151. package/dist/formulas/index.d.ts.map +0 -1
  152. package/dist/formulas/index.js +0 -186
  153. package/dist/formulas/index.js.map +0 -1
  154. package/dist/formulas/types/formulas.d.ts +0 -68
  155. package/dist/formulas/types/formulas.d.ts.map +0 -1
  156. package/dist/formulas/types/formulas.js +0 -2
  157. package/dist/formulas/types/formulas.js.map +0 -1
  158. package/dist/formulas/types/index.d.ts +0 -5
  159. package/dist/formulas/types/index.d.ts.map +0 -1
  160. package/dist/formulas/types/index.js +0 -2
  161. package/dist/formulas/types/index.js.map +0 -1
  162. package/dist/formulas/variants/array-formula.d.ts +0 -24
  163. package/dist/formulas/variants/array-formula.d.ts.map +0 -1
  164. package/dist/formulas/variants/array-formula.js +0 -142
  165. package/dist/formulas/variants/array-formula.js.map +0 -1
  166. package/dist/formulas/variants/base.d.ts +0 -6
  167. package/dist/formulas/variants/base.d.ts.map +0 -1
  168. package/dist/formulas/variants/base.js +0 -3
  169. package/dist/formulas/variants/base.js.map +0 -1
  170. package/dist/formulas/variants/basic.d.ts +0 -18
  171. package/dist/formulas/variants/basic.d.ts.map +0 -1
  172. package/dist/formulas/variants/basic.js +0 -128
  173. package/dist/formulas/variants/basic.js.map +0 -1
  174. package/dist/formulas/variants/comparison.d.ts +0 -25
  175. package/dist/formulas/variants/comparison.d.ts.map +0 -1
  176. package/dist/formulas/variants/comparison.js +0 -153
  177. package/dist/formulas/variants/comparison.js.map +0 -1
  178. package/dist/formulas/variants/conditional.d.ts +0 -18
  179. package/dist/formulas/variants/conditional.d.ts.map +0 -1
  180. package/dist/formulas/variants/conditional.js +0 -183
  181. package/dist/formulas/variants/conditional.js.map +0 -1
  182. package/dist/formulas/variants/iterative-array.d.ts +0 -20
  183. package/dist/formulas/variants/iterative-array.d.ts.map +0 -1
  184. package/dist/formulas/variants/iterative-array.js +0 -155
  185. package/dist/formulas/variants/iterative-array.js.map +0 -1
  186. package/dist/formulas/variants/per-value.d.ts +0 -20
  187. package/dist/formulas/variants/per-value.d.ts.map +0 -1
  188. package/dist/formulas/variants/per-value.js +0 -154
  189. package/dist/formulas/variants/per-value.js.map +0 -1
  190. package/dist/index.d.ts +0 -5
  191. package/dist/index.d.ts.map +0 -1
  192. package/dist/index.js +0 -6
  193. package/dist/index.js.map +0 -1
  194. package/dist/models/base.d.ts +0 -55
  195. package/dist/models/base.d.ts.map +0 -1
  196. package/dist/models/base.js +0 -146
  197. package/dist/models/base.js.map +0 -1
  198. package/dist/models/callback-manager.d.ts +0 -7
  199. package/dist/models/callback-manager.d.ts.map +0 -1
  200. package/dist/models/callback-manager.js +0 -89
  201. package/dist/models/callback-manager.js.map +0 -1
  202. package/dist/models/field.d.ts +0 -121
  203. package/dist/models/field.d.ts.map +0 -1
  204. package/dist/models/field.js +0 -515
  205. package/dist/models/field.js.map +0 -1
  206. package/dist/models/index.d.ts +0 -13
  207. package/dist/models/index.d.ts.map +0 -1
  208. package/dist/models/index.js +0 -7
  209. package/dist/models/index.js.map +0 -1
  210. package/dist/models/model.d.ts +0 -37
  211. package/dist/models/model.d.ts.map +0 -1
  212. package/dist/models/model.js +0 -245
  213. package/dist/models/model.js.map +0 -1
  214. package/dist/models/plugins/base.d.ts +0 -9
  215. package/dist/models/plugins/base.d.ts.map +0 -1
  216. package/dist/models/plugins/base.js +0 -3
  217. package/dist/models/plugins/base.js.map +0 -1
  218. package/dist/models/plugins/formula.d.ts +0 -18
  219. package/dist/models/plugins/formula.d.ts.map +0 -1
  220. package/dist/models/plugins/formula.js +0 -82
  221. package/dist/models/plugins/formula.js.map +0 -1
  222. package/dist/models/plugins/index.d.ts +0 -11
  223. package/dist/models/plugins/index.d.ts.map +0 -1
  224. package/dist/models/plugins/index.js +0 -52
  225. package/dist/models/plugins/index.js.map +0 -1
  226. package/dist/models/plugins/plugins.d.ts +0 -7
  227. package/dist/models/plugins/plugins.d.ts.map +0 -1
  228. package/dist/models/plugins/plugins.js +0 -7
  229. package/dist/models/plugins/plugins.js.map +0 -1
  230. package/dist/models/types/base-wise-model.d.ts +0 -7
  231. package/dist/models/types/base-wise-model.d.ts.map +0 -1
  232. package/dist/models/types/base-wise-model.js +0 -2
  233. package/dist/models/types/base-wise-model.js.map +0 -1
  234. package/dist/models/types/callbacks.d.ts +0 -19
  235. package/dist/models/types/callbacks.d.ts.map +0 -1
  236. package/dist/models/types/callbacks.js +0 -2
  237. package/dist/models/types/callbacks.js.map +0 -1
  238. package/dist/models/types/disabled.d.ts +0 -8
  239. package/dist/models/types/disabled.d.ts.map +0 -1
  240. package/dist/models/types/disabled.js +0 -2
  241. package/dist/models/types/disabled.js.map +0 -1
  242. package/dist/models/types/form-field.d.ts +0 -25
  243. package/dist/models/types/form-field.d.ts.map +0 -1
  244. package/dist/models/types/form-field.js +0 -2
  245. package/dist/models/types/form-field.js.map +0 -1
  246. package/dist/models/types/model.d.ts +0 -13
  247. package/dist/models/types/model.d.ts.map +0 -1
  248. package/dist/models/types/model.js +0 -2
  249. package/dist/models/types/model.js.map +0 -1
  250. package/dist/models/types/plugins.d.ts +0 -13
  251. package/dist/models/types/plugins.d.ts.map +0 -1
  252. package/dist/models/types/plugins.js +0 -2
  253. package/dist/models/types/plugins.js.map +0 -1
  254. package/dist/models/types/wrapped-form-model-props.d.ts +0 -11
  255. package/dist/models/types/wrapped-form-model-props.d.ts.map +0 -1
  256. package/dist/models/types/wrapped-form-model-props.js +0 -2
  257. package/dist/models/types/wrapped-form-model-props.js.map +0 -1
  258. package/dist/models/wrapper.d.ts +0 -30
  259. package/dist/models/wrapper.d.ts.map +0 -1
  260. package/dist/models/wrapper.js +0 -213
  261. package/dist/models/wrapper.js.map +0 -1
  262. package/dist/settings/index.d.ts +0 -7
  263. package/dist/settings/index.d.ts.map +0 -1
  264. package/dist/settings/index.js +0 -26
  265. package/dist/settings/index.js.map +0 -1
  266. package/dist/utils/pending-promise.d.ts +0 -6
  267. package/dist/utils/pending-promise.d.ts.map +0 -1
  268. package/dist/utils/pending-promise.js +0 -24
  269. package/dist/utils/pending-promise.js.map +0 -1
@@ -11,580 +11,546 @@ import { IDisabled } from './types/disabled';
11
11
  * @extends ReactiveModel<IFormField>
12
12
  */
13
13
  export class FormField extends ReactiveModel<IFormField> {
14
- // The parent model, either FormModel or WrappedFormModel, containing this field.
15
- #parent: WrappedFormModel | FormModel;
16
- get parent() {
17
- return this.#parent;
18
- }
19
-
20
- #NATIVE_ACTIONS = ['hide', 'disable', 'enable', 'show', 'reset'];
21
- #EVENTS = ['onClick', 'onChange', 'onKeyup'];
22
- setEvents(events: string[]) {
23
- this.#EVENTS.concat(events);
24
- }
25
- #isReady: boolean = false;
26
- // Can be a boolean or an object specifying dynamic disablingvas logic based on other fields' values.
27
- #disabled: boolean | IDisabled = false;
14
+ // The parent model, either FormModel or WrappedFormModel, containing this field.
15
+ #parent: WrappedFormModel | FormModel;
16
+ get parent() {
17
+ return this.#parent;
18
+ }
19
+
20
+ #NATIVE_ACTIONS = ['hide', 'disable', 'enable', 'show', 'reset'];
21
+ #EVENTS = ['onClick', 'onChange', 'onKeyup'];
22
+ setEvents(events: string[]) {
23
+ this.#EVENTS.concat(events);
24
+ }
25
+ #isReady: boolean = false;
26
+ // Can be a boolean or an object specifying dynamic disablingvas logic based on other fields' values.
27
+ #disabled: boolean | IDisabled = false;
28
+
29
+ /**
30
+ * Evaluates and returns the disabled state of the field. If `#disabled` is an object, it checks the specified fields' values to determine the disabled state dynamically.
31
+ * @returns {boolean} The disabled state of the field.
32
+ */
33
+ get disabled() {
34
+ if (typeof this.#disabled !== 'object' || !this.#disabled?.fields) return this.#disabled;
35
+
36
+ const validate = field => {
37
+ if (typeof field !== 'object') return !this.#parent.form.getField(field).value;
38
+ const { name, value } = field;
39
+ const fieldInstance = this.#parent.getField(name);
40
+ if (!fieldInstance) return false;
41
+ if (field.hasOwnProperty('condition')) {
42
+ const compare = this.evaluations[field.condition](fieldInstance.value, field.value);
43
+ return compare;
44
+ }
45
+ const { value: fieldValue } = fieldInstance;
46
+ return value !== fieldValue;
47
+ };
28
48
 
49
+ return this.#disabled.fields.some(validate);
50
+ }
51
+
52
+ set disabled(value) {
53
+ if (value === this.#disabled) return;
54
+ this.#disabled = value;
55
+ this.triggerEvent();
56
+ }
57
+
58
+ // Field specifications including its type, validation rules, and other metadata.
59
+ #specs: Record<string, any>;
60
+ get specs() {
61
+ return this.#specs;
62
+ }
63
+
64
+ get attributes() {
65
+ const props = this.getProperties();
66
+ return {
67
+ ...props,
68
+ disabled: this.#disabled,
69
+ };
70
+ }
71
+
72
+ #value: string;
73
+ get value() {
74
+ return this.#value;
75
+ }
76
+
77
+ set value(value) {
78
+ this.setValue(value);
79
+ }
80
+
81
+ // Tracks other fields this field listens to for changes, enabling reactive behavior and allowing the cleanup of event listeners.
82
+ #listeningItems = new Map();
83
+
84
+ /**
85
+ * Constructs a FormField instance with specified properties and parent form model.
86
+ * @param {Object} params - Construction parameters including the parent form model and field specifications.
87
+ */
88
+ constructor({ parent, specs }: { parent; specs: IFormFieldProps }) {
89
+ let { properties, disabled, ...props } = specs;
90
+ super({
91
+ ...props,
92
+ properties: ['name', 'type', 'placeholder', 'required', 'label', 'variant', 'options', 'className', 'checked', 'id', 'icon', 'hidden', ...properties],
93
+ });
94
+
95
+ (this as any).__instanceID = `${specs.name}.${this.generateRandomNumber()}`;
96
+
97
+ this.#specs = specs;
98
+ this.#parent = parent;
99
+ (this as any).__instance = Math.random();
100
+
101
+ const toSet: Record<string, any> = {};
29
102
  /**
30
- * Evaluates and returns the disabled state of the field. If `#disabled` is an object, it checks the specified fields' values to determine the disabled state dynamically.
31
- * @returns {boolean} The disabled state of the field.
103
+ * @todo: review this code
32
104
  */
33
- get disabled() {
34
- if (typeof this.#disabled !== 'object' || !this.#disabled?.fields)
35
- return this.#disabled;
36
-
37
- const validate = (field) => {
38
- if (typeof field !== 'object')
39
- return !this.#parent.form.getField(field).value;
40
- const { name, value } = field;
41
- const fieldInstance = this.#parent.getField(name);
42
- if (!fieldInstance) return false;
43
- if (field.hasOwnProperty('condition')) {
44
- const compare = this.evaluations[field.condition](
45
- fieldInstance.value,
46
- field.value
47
- );
48
- return compare;
49
- }
50
- const { value: fieldValue } = fieldInstance;
51
- return value !== fieldValue;
52
- };
53
-
54
- return this.#disabled.fields.some(validate);
55
- }
56
-
57
- set disabled(value) {
58
- if (value === this.#disabled) return;
59
- this.#disabled = value;
60
- this.triggerEvent();
61
- }
62
-
63
- // Field specifications including its type, validation rules, and other metadata.
64
- #specs: Record<string, any>;
65
- get specs() {
66
- return this.#specs;
67
- }
68
-
69
- get attributes() {
70
- const props = this.getProperties();
71
- return {
72
- ...props,
73
- disabled: this.#disabled,
74
- };
75
- }
76
-
77
- #value: string;
78
- get value() {
79
- return this.#value;
80
- }
105
+ Object.keys(props).forEach(key => {
106
+ if (key === 'properties') return;
81
107
 
82
- set value(value) {
83
- this.setValue(value);
84
- }
85
-
86
- // Tracks other fields this field listens to for changes, enabling reactive behavior and allowing the cleanup of event listeners.
87
- #listeningItems = new Map();
88
-
89
- /**
90
- * Constructs a FormField instance with specified properties and parent form model.
91
- * @param {Object} params - Construction parameters including the parent form model and field specifications.
92
- */
93
- constructor({ parent, specs }: { parent; specs: IFormFieldProps }) {
94
- let { properties, disabled, ...props } = specs;
95
- super({
96
- ...props,
97
- properties: [
98
- 'name',
99
- 'type',
100
- 'placeholder',
101
- 'required',
102
- 'label',
103
- 'variant',
104
- 'options',
105
- 'className',
106
- 'checked',
107
- 'id',
108
- 'icon',
109
- 'hidden',
110
-
111
- ...properties,
112
- ],
108
+ if (typeof props[key] === 'string' && props[key]?.includes('state:')) {
109
+ const state = props[key].split('state:')[1];
110
+ if (state === 'create' && !this.#parent.form.update) {
111
+ props[key] = true;
112
+ }
113
+ }
114
+ toSet[key] = props[key];
115
+ });
116
+ // this.#disabled = disabled;
117
+ // this.set(toSet);
118
+
119
+ this.set(specs);
120
+ }
121
+
122
+ getProperties() {
123
+ const properties = super.getProperties();
124
+ return { ...properties, value: this.#value };
125
+ }
126
+
127
+ /**
128
+ * This method is used to set the value property of the field and fire the value.change event
129
+ *
130
+ * @param value
131
+ * @returns
132
+ */
133
+ setValue(value: string) {
134
+ if (value === this.value) return;
135
+ this.#value = value;
136
+ this.trigger('change');
137
+ this.trigger('value.change', this);
138
+ }
139
+
140
+ generateRandomNumber = () => {
141
+ return Math.floor(Math.random() * (1000000 - 10000 + 1)) + 10000;
142
+ };
143
+
144
+ /**
145
+ * Performs initial setup based on the field's specifications, setting up validation, default values, and any specified dynamic behavior.
146
+ */
147
+ initialize = () => {
148
+ this.checkSettings(this.#specs);
149
+ this.on('change', this.listenerEvents);
150
+ // this.on('value.change', this.listenerEvents);
151
+ };
152
+
153
+ /**
154
+ * Resets the field to its initial value and state, including resetting the disabled state if it's statically defined.
155
+ */
156
+ clear = () => {
157
+ // Get initial values from specs or use empty object
158
+ const initValues = this.#specs || {};
159
+ this.set(initValues);
160
+ if (initValues.hasOwnProperty('disabled')) this.disabled = initValues.disabled;
161
+ this.triggerEvent('clear');
162
+ };
163
+
164
+ /**
165
+ * Listens to changes in sibling fields (specified in dynamic disabling logic) and updates its state accordingly.
166
+ */
167
+ #listenSiblings = () => {
168
+ this.triggerEvent('change');
169
+ this.triggerEvent();
170
+ this.triggerEvent('value.change');
171
+ };
172
+
173
+ /**
174
+ * Checks and applies the field's settings, particularly for dynamic disabling, establishing listeners on related fields as necessary.
175
+ * @param {Object} props - The field's properties and settings to check and apply.
176
+ */
177
+ checkSettings(props) {
178
+ if (props.hasOwnProperty('disabled')) {
179
+ if (typeof props.disabled === 'boolean') {
180
+ this.#disabled = props.disabled;
181
+ return;
182
+ }
183
+
184
+ if (typeof props.disabled !== 'object') {
185
+ throw new Error(`The disabled property of the field ${props.name} must be a boolean or an object`);
186
+ }
187
+ if (!props.disabled.fields && !props.disabled.mode) {
188
+ throw new Error(`The disabled property of the field ${props.name} must have a fields property or a mode defined`);
189
+ }
190
+
191
+ if (props.disabled.mode) {
192
+ // posible modes : create, update;
193
+ this.#disabled = this.#parent.form.mode === props.disabled.mode;
194
+ return;
195
+ }
196
+
197
+ let allValid;
198
+ props.disabled.fields.forEach(item => {
199
+ const name = typeof item === 'string' ? item : item.name;
200
+
201
+ const instance = this.#parent.form.getField(name);
202
+ allValid = instance;
203
+ if (!allValid) return;
204
+ instance.on('change', this.#listenSiblings);
205
+ instance.on('value.change', this.#listenSiblings);
206
+ this.#listeningItems.set(name, {
207
+ item: instance,
208
+ listener: this.#listenSiblings,
113
209
  });
210
+ });
114
211
 
115
- (this as any).__instanceID = `${
116
- specs.name
117
- }.${this.generateRandomNumber()}`;
118
-
119
- this.#specs = specs;
120
- this.#parent = parent;
121
- (this as any).__instance = Math.random();
122
-
123
- const toSet: Record<string, any> = {};
124
- /**
125
- * @todo: review this code
126
- */
127
- Object.keys(props).forEach((key) => {
128
- if (key === 'properties') return;
129
-
130
- if (
131
- typeof props[key] === 'string' &&
132
- props[key]?.includes('state:')
133
- ) {
134
- const state = props[key].split('state:')[1];
135
- if (state === 'create' && !this.#parent.form.update) {
136
- props[key] = true;
137
- }
138
- }
139
- toSet[key] = props[key];
212
+ if (!allValid) {
213
+ const fieldName = this.getProperties().name || 'unknown';
214
+ throw new Error(`the field ${allValid} does not exist in the form ${(this.#parent as any).name}, field passed in invalid settings of field "${fieldName}"`);
215
+ }
216
+ this.#disabled = props.disabled;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * En este metodo se recorre el objeto asociado al evento y ejecuta cada una de las acciones asociadas
222
+ * como las acciones nativas del FormModel (HIDE, SHOW, DISABLE, ENABLE), hace el seteo de propiedades
223
+ * en caso de recibir field y ejecuta las callbacks asociadas
224
+ * @param actions objeto con las acciones que se van a realizar al ejecutarse el evento asociado
225
+ * @returns
226
+ */
227
+ async #executeEvent(actions) {
228
+ if (typeof actions !== 'object' || Array.isArray(actions)) return;
229
+
230
+ const formModel = this.#parent.form;
231
+
232
+ const sortedKeys = Object.keys(actions).sort((a, b) => actions[a]?.__order - actions[b]?.__order);
233
+
234
+ for (let action of sortedKeys) {
235
+ if (action === 'fields') {
236
+ for (let fieldName in actions[action]) {
237
+ const field = this.#parent.form.getField(fieldName);
238
+
239
+ if (!field) continue;
240
+ await field.isReady;
241
+ field.set(actions[action][fieldName]);
242
+ }
243
+ continue;
244
+ }
245
+ if (formModel.callbacks.hasOwnProperty(action)) {
246
+ formModel.callbacks[action]({
247
+ ...actions[action],
248
+ form: formModel,
140
249
  });
141
- // this.#disabled = disabled;
142
- // this.set(toSet);
250
+ continue;
251
+ }
143
252
 
144
- this.set(specs);
253
+ if (this.#NATIVE_ACTIONS.includes(action) && formModel.hasOwnProperty(action)) {
254
+ formModel[action](actions[action].target);
255
+ }
145
256
  }
146
-
147
- getProperties() {
148
- const properties = super.getProperties();
149
- return { ...properties, value: this.#value };
257
+ }
258
+
259
+ /**
260
+ * Busca el evento configurado en el field
261
+ * @param item objeto que tiene el evento
262
+ * @returns
263
+ */
264
+ #getEvent(item) {
265
+ let event: string;
266
+ const keys = Object.keys(item);
267
+ keys.forEach(key => {
268
+ if (event) return;
269
+ if (this.#EVENTS.includes(key)) event = key;
270
+ });
271
+ return event;
272
+ }
273
+
274
+ /**
275
+ * Metodo para identificar si es field con multiples eventos configurados o solo es un evento configurado
276
+ * hace la busqueda del evento lanzado al haber multiples
277
+ * @returns
278
+ */
279
+ listenerEvents = event2 => {
280
+ if (!this.#isReady) {
281
+ this.#isReady = true;
282
+ return;
150
283
  }
151
284
 
152
- /**
153
- * This method is used to set the value property of the field and fire the value.change event
154
- *
155
- * @param value
156
- * @returns
157
- */
158
- setValue(value: string) {
159
- if (value === this.value) return;
160
- this.#value = value;
161
- this.trigger('change');
162
- this.trigger('value.change', this);
285
+ if (!this.specs?.events) {
286
+ const event = this.#getEvent(this.specs);
287
+ if (!event) return;
288
+ this.#executeEvent(this.specs[event]);
289
+ return;
163
290
  }
164
291
 
165
- generateRandomNumber = () => {
166
- return Math.floor(Math.random() * (1000000 - 10000 + 1)) + 10000;
167
- };
292
+ const event = this.#getEvent(this.specs.events);
293
+ if (!event) return;
294
+ const item = this.specs.events[event].hasOwnProperty(this.value) ? this.specs.events[event][this.value] : null;
295
+ if (!item) return;
296
+ this.#executeEvent(item);
297
+ };
298
+
299
+ /**
300
+ * Cleans up any established listeners and internal state when the field is removed or the form is reset, ensuring no memory leaks or stale data.
301
+ */
302
+ cleanUp() {
303
+ this.#listeningItems.forEach(({ item, listener }) => item.off('change', listener));
304
+ // todo: remove all events
305
+ }
306
+
307
+ /**
308
+ * The `set` method sets one or more properties on the model.
309
+ *
310
+ *
311
+ * This method overwrites the original reactiveModel set to pass the object as param
312
+ * when the change event is fired.
313
+ * Eventually this method will be removed and the original set method will be used, but
314
+ * it requires an upgrade in the reactive model package.
315
+ * @param {keyof ReactiveModelPublic<T>} property - The name of the property to set.
316
+ * @param {*} value - The value to set the property to.
317
+ * @returns {void}
318
+ */
319
+ set(properties: Partial<IFormField>): any {
320
+ let updated = false;
321
+ try {
322
+ Object.keys(properties).forEach(prop => {
323
+ const currentProperties = Object.keys(this.getProperties());
324
+
325
+ // Verificar si la propiedad está registrada en el modelo reactivo
326
+ // Si no está en getProperties(), verificar si está en la lista de properties del constructor
327
+ const isRegisteredProperty = currentProperties.includes(prop) || (this.#specs?.properties && this.#specs.properties.includes(prop));
328
+
329
+ if (!isRegisteredProperty) {
330
+ return;
331
+ }
168
332
 
169
- /**
170
- * Performs initial setup based on the field's specifications, setting up validation, default values, and any specified dynamic behavior.
171
- */
172
- initialize = () => {
173
- this.checkSettings(this.#specs);
174
- this.on('change', this.listenerEvents);
175
- // this.on('value.change', this.listenerEvents);
176
- };
333
+ const newValue = properties[prop];
334
+ const currentValue = this[prop];
177
335
 
178
- /**
179
- * Resets the field to its initial value and state, including resetting the disabled state if it's statically defined.
180
- */
181
- clear = () => {
182
- // Get initial values from specs or use empty object
183
- const initValues = this.#specs || {};
184
- this.set(initValues);
185
- if (initValues.hasOwnProperty('disabled'))
186
- this.disabled = initValues.disabled;
187
- this.triggerEvent('clear');
188
- };
336
+ // Si los valores son iguales por referencia, no actualizar
337
+ if (currentValue === newValue) return;
189
338
 
190
- /**
191
- * Listens to changes in sibling fields (specified in dynamic disabling logic) and updates its state accordingly.
192
- */
193
- #listenSiblings = () => {
194
- this.triggerEvent('change');
195
- this.triggerEvent();
196
- this.triggerEvent('value.change');
197
- };
339
+ // Si ambos son arrays y tienen la misma referencia, no actualizar
340
+ if (Array.isArray(currentValue) && Array.isArray(newValue) && currentValue === newValue) {
341
+ return;
342
+ }
198
343
 
199
- /**
200
- * Checks and applies the field's settings, particularly for dynamic disabling, establishing listeners on related fields as necessary.
201
- * @param {Object} props - The field's properties and settings to check and apply.
202
- */
203
- checkSettings(props) {
204
- if (props.hasOwnProperty('disabled')) {
205
- if (typeof props.disabled === 'boolean') {
206
- this.#disabled = props.disabled;
207
- return;
344
+ // Si el tipo cambia (array a string, string a array, etc.), siempre actualizar
345
+ const currentIsArray = Array.isArray(currentValue);
346
+ const newIsArray = Array.isArray(newValue);
347
+ const currentIsObject = typeof currentValue === 'object' && currentValue !== null && !currentIsArray;
348
+ const newIsObject = typeof newValue === 'object' && newValue !== null && !newIsArray;
349
+
350
+ if ((currentIsArray && !newIsArray) || (!currentIsArray && newIsArray) || (currentIsObject && !newIsObject) || (!currentIsObject && newIsObject)) {
351
+ // Deep clone objects before setting to avoid reference issues
352
+ let valueToSet = newValue;
353
+ if (typeof newValue === 'object' && newValue !== null) {
354
+ if (Array.isArray(newValue)) {
355
+ valueToSet = JSON.parse(JSON.stringify(newValue));
356
+ } else {
357
+ valueToSet = JSON.parse(JSON.stringify(newValue));
208
358
  }
359
+ }
360
+ this[prop] = valueToSet;
361
+ updated = true;
362
+ return;
363
+ }
209
364
 
210
- if (typeof props.disabled !== 'object') {
211
- throw new Error(
212
- `The disabled property of the field ${props.name} must be a boolean or an object`
213
- );
365
+ // Para arrays y objetos, hacer comparación más robusta
366
+ if (typeof newValue === 'object' && newValue !== null) {
367
+ // Si currentValue es undefined/null, siempre actualizar
368
+ if (currentValue === undefined || currentValue === null) {
369
+ // Deep clone objects before setting to avoid reference issues
370
+ let valueToSet = newValue;
371
+ if (Array.isArray(newValue)) {
372
+ valueToSet = JSON.parse(JSON.stringify(newValue));
373
+ } else {
374
+ valueToSet = JSON.parse(JSON.stringify(newValue));
214
375
  }
215
- if (!props.disabled.fields && !props.disabled.mode) {
216
- throw new Error(
217
- `The disabled property of the field ${props.name} must have a fields property or a mode defined`
218
- );
376
+ this[prop] = valueToSet;
377
+ updated = true;
378
+ return;
379
+ }
380
+
381
+ // Comparar arrays
382
+ if (newIsArray && currentIsArray) {
383
+ // Si las longitudes son diferentes, actualizar
384
+ if (newValue.length !== currentValue.length) {
385
+ this[prop] = newValue;
386
+ updated = true;
387
+ return;
219
388
  }
220
-
221
- if (props.disabled.mode) {
222
- // posible modes : create, update;
223
- this.#disabled = this.#parent.form.mode === props.disabled.mode;
224
- return;
389
+ // Si el array está vacío y ambos están vacíos, no actualizar
390
+ if (newValue.length === 0 && currentValue.length === 0) {
391
+ return;
225
392
  }
226
-
227
- let allValid;
228
- props.disabled.fields.forEach((item) => {
229
- const name = typeof item === 'string' ? item : item.name;
230
-
231
- const instance = this.#parent.form.getField(name);
232
- allValid = instance;
233
- if (!allValid) return;
234
- instance.on('change', this.#listenSiblings);
235
- instance.on('value.change', this.#listenSiblings);
236
- this.#listeningItems.set(name, {
237
- item: instance,
238
- listener: this.#listenSiblings,
393
+ // Comparar contenido del array de forma más robusta
394
+ // Primero intentar comparación rápida por referencia de todo el array usando JSON.stringify
395
+ // Esto es más eficiente para detectar si el contenido es realmente diferente
396
+ try {
397
+ // Normalizar ambos arrays antes de comparar para evitar problemas con orden de propiedades
398
+ const normalizeForComparison = (arr: any[]) => {
399
+ return arr.map(item => {
400
+ if (typeof item === 'object' && item !== null && !Array.isArray(item)) {
401
+ // Ordenar propiedades del objeto para comparación estable
402
+ const sorted = Object.keys(item)
403
+ .sort()
404
+ .reduce((acc, key) => {
405
+ acc[key] = item[key];
406
+ return acc;
407
+ }, {} as any);
408
+ return sorted;
409
+ }
410
+ return item;
239
411
  });
240
- });
241
-
242
- if (!allValid) {
243
- const fieldName = this.getProperties().name || 'unknown';
244
- throw new Error(
245
- `the field ${allValid} does not exist in the form ${
246
- (this.#parent as any).name
247
- }, field passed in invalid settings of field "${fieldName}"`
248
- );
249
- }
250
- this.#disabled = props.disabled;
251
- }
252
- }
412
+ };
253
413
 
254
- /**
255
- * En este metodo se recorre el objeto asociado al evento y ejecuta cada una de las acciones asociadas
256
- * como las acciones nativas del FormModel (HIDE, SHOW, DISABLE, ENABLE), hace el seteo de propiedades
257
- * en caso de recibir field y ejecuta las callbacks asociadas
258
- * @param actions objeto con las acciones que se van a realizar al ejecutarse el evento asociado
259
- * @returns
260
- */
261
- async #executeEvent(actions) {
262
- if (typeof actions !== 'object' || Array.isArray(actions)) return;
263
-
264
- const formModel = this.#parent.form;
265
-
266
- const sortedKeys = Object.keys(actions).sort(
267
- (a, b) => actions[a]?.__order - actions[b]?.__order
268
- );
414
+ const normalizedCurrent = normalizeForComparison(currentValue);
415
+ const normalizedNew = normalizeForComparison(newValue);
269
416
 
270
- for (let action of sortedKeys) {
271
- if (action === 'fields') {
272
- for (let fieldName in actions[action]) {
273
- const field = this.#parent.form.getField(fieldName);
417
+ const currentStr = JSON.stringify(normalizedCurrent);
418
+ const newStr = JSON.stringify(normalizedNew);
274
419
 
275
- if (!field) continue;
276
- await field.isReady;
277
- field.set(actions[action][fieldName]);
420
+ if (currentStr === newStr) {
421
+ // Si el contenido es igual, no actualizar para evitar bucles infinitos
422
+ return;
423
+ }
424
+ } catch (e) {
425
+ // Si JSON.stringify falla, hacer comparación elemento por elemento
426
+ let arraysEqual = true;
427
+ for (let idx = 0; idx < newValue.length; idx++) {
428
+ const newVal = newValue[idx];
429
+ const currentVal = currentValue[idx];
430
+
431
+ // Comparación por referencia primero (más rápida)
432
+ if (newVal === currentVal) continue;
433
+
434
+ // Si son objetos, comparar con JSON.stringify
435
+ if (typeof newVal === 'object' && newVal !== null && typeof currentVal === 'object' && currentVal !== null) {
436
+ try {
437
+ const newStr = JSON.stringify(newVal);
438
+ const currentStr = JSON.stringify(currentVal);
439
+ if (newStr !== currentStr) {
440
+ arraysEqual = false;
441
+ break;
442
+ }
443
+ } catch (e) {
444
+ // Si JSON.stringify falla, comparar por referencia
445
+ if (newVal !== currentVal) {
446
+ arraysEqual = false;
447
+ break;
448
+ }
449
+ }
450
+ } else {
451
+ // Para valores primitivos, comparación directa
452
+ if (newVal !== currentVal) {
453
+ arraysEqual = false;
454
+ break;
455
+ }
278
456
  }
279
- continue;
457
+ }
458
+ if (arraysEqual) return;
280
459
  }
281
- if (formModel.callbacks.hasOwnProperty(action)) {
282
- formModel.callbacks[action]({
283
- ...actions[action],
284
- form: formModel,
285
- });
286
- continue;
287
- }
288
-
289
- if (
290
- this.#NATIVE_ACTIONS.includes(action) &&
291
- formModel.hasOwnProperty(action)
292
- ) {
293
- formModel[action](actions[action].target);
460
+ // Deep clone arrays before setting to avoid reference issues
461
+ let arrayToSet = JSON.parse(JSON.stringify(newValue));
462
+ this[prop] = arrayToSet;
463
+ updated = true;
464
+ return;
465
+ }
466
+
467
+ // Para objetos, comparar con JSON.stringify pero manejar undefined
468
+ if (newIsObject && currentIsObject) {
469
+ try {
470
+ const currentStr = JSON.stringify(currentValue);
471
+ const newStr = JSON.stringify(newValue);
472
+ if (currentStr === newStr) return;
473
+ // Si son diferentes, continuar para actualizar
474
+ } catch (e) {
475
+ // Si JSON.stringify falla (por ejemplo, con funciones), comparar por referencia
476
+ if (currentValue === newValue) return;
477
+ // Si son diferentes, continuar para actualizar
294
478
  }
479
+ }
295
480
  }
296
- }
297
481
 
298
- /**
299
- * Busca el evento configurado en el field
300
- * @param item objeto que tiene el evento
301
- * @returns
302
- */
303
- #getEvent(item) {
304
- let event: string;
305
- const keys = Object.keys(item);
306
- keys.forEach((key) => {
307
- if (event) return;
308
- if (this.#EVENTS.includes(key)) event = key;
309
- });
310
- return event;
311
- }
482
+ const descriptor = Object.getOwnPropertyDescriptor(this, prop);
312
483
 
313
- /**
314
- * Metodo para identificar si es field con multiples eventos configurados o solo es un evento configurado
315
- * hace la busqueda del evento lanzado al haber multiples
316
- * @returns
317
- */
318
- listenerEvents = (event2) => {
319
- if (!this.#isReady) {
320
- this.#isReady = true;
321
- return;
484
+ // Deep clone objects before setting to avoid reference issues
485
+ let valueToSet = newValue;
486
+ if (typeof newValue === 'object' && newValue !== null) {
487
+ if (Array.isArray(newValue)) {
488
+ valueToSet = JSON.parse(JSON.stringify(newValue));
489
+ } else {
490
+ valueToSet = JSON.parse(JSON.stringify(newValue));
491
+ }
322
492
  }
323
493
 
324
- if (!this.specs?.events) {
325
- const event = this.#getEvent(this.specs);
326
- if (!event) return;
327
- this.#executeEvent(this.specs[event]);
328
- return;
494
+ // Si la propiedad tiene un setter, usarlo en lugar de retornar
495
+ if (descriptor?.set) {
496
+ // Llamar al setter con el valor clonado
497
+ // El setter del ReactiveModel hará su propia comparación y clonado
498
+ descriptor.set.call(this, valueToSet);
499
+ updated = true;
500
+ return;
329
501
  }
330
502
 
331
- const event = this.#getEvent(this.specs.events);
332
- if (!event) return;
333
- const item = this.specs.events[event].hasOwnProperty(this.value)
334
- ? this.specs.events[event][this.value]
335
- : null;
336
- if (!item) return;
337
- this.#executeEvent(item);
338
- };
339
-
340
- /**
341
- * Cleans up any established listeners and internal state when the field is removed or the form is reset, ensuring no memory leaks or stale data.
342
- */
343
- cleanUp() {
344
- this.#listeningItems.forEach(({ item, listener }) =>
345
- item.off('change', listener)
346
- );
347
- // todo: remove all events
348
- }
349
-
350
- /**
351
- * The `set` method sets one or more properties on the model.
352
- *
353
- *
354
- * This method overwrites the original reactiveModel set to pass the object as param
355
- * when the change event is fired.
356
- * Eventually this method will be removed and the original set method will be used, but
357
- * it requires an upgrade in the reactive model package.
358
- * @param {keyof ReactiveModelPublic<T>} property - The name of the property to set.
359
- * @param {*} value - The value to set the property to.
360
- * @returns {void}
361
- */
362
- set(properties: Partial<IFormField>): any {
363
- let updated = false;
364
- try {
365
- Object.keys(properties).forEach((prop) => {
366
- const currentProperties = Object.keys(this.getProperties());
367
-
368
- // Verificar si la propiedad está registrada en el modelo reactivo
369
- // Si no está en getProperties(), verificar si está en la lista de properties del constructor
370
- const isRegisteredProperty =
371
- currentProperties.includes(prop) ||
372
- (this.#specs?.properties &&
373
- this.#specs.properties.includes(prop));
374
-
375
- if (!isRegisteredProperty) {
376
- return;
377
- }
378
-
379
- const newValue = properties[prop];
380
- const currentValue = this[prop];
381
-
382
- // Si los valores son iguales por referencia, no actualizar
383
- if (currentValue === newValue) return;
384
-
385
- // Si ambos son arrays y tienen la misma referencia, no actualizar
386
- if (
387
- Array.isArray(currentValue) &&
388
- Array.isArray(newValue) &&
389
- currentValue === newValue
390
- ) {
391
- return;
392
- }
393
-
394
- // Si el tipo cambia (array a string, string a array, etc.), siempre actualizar
395
- const currentIsArray = Array.isArray(currentValue);
396
- const newIsArray = Array.isArray(newValue);
397
- const currentIsObject =
398
- typeof currentValue === 'object' &&
399
- currentValue !== null &&
400
- !currentIsArray;
401
- const newIsObject =
402
- typeof newValue === 'object' &&
403
- newValue !== null &&
404
- !newIsArray;
405
-
406
- if (
407
- (currentIsArray && !newIsArray) ||
408
- (!currentIsArray && newIsArray) ||
409
- (currentIsObject && !newIsObject) ||
410
- (!currentIsObject && newIsObject)
411
- ) {
412
- this[prop] = newValue;
413
- updated = true;
414
- return;
415
- }
416
-
417
- // Para arrays y objetos, hacer comparación más robusta
418
- if (typeof newValue === 'object' && newValue !== null) {
419
- // Si currentValue es undefined/null, siempre actualizar
420
- if (currentValue === undefined || currentValue === null) {
421
- this[prop] = newValue;
422
- updated = true;
423
- return;
424
- }
425
-
426
- // Comparar arrays
427
- if (newIsArray && currentIsArray) {
428
- // Si las longitudes son diferentes, actualizar
429
- if (newValue.length !== currentValue.length) {
430
- this[prop] = newValue;
431
- updated = true;
432
- return;
433
- }
434
- // Si el array está vacío y ambos están vacíos, no actualizar
435
- if (
436
- newValue.length === 0 &&
437
- currentValue.length === 0
438
- ) {
439
- return;
440
- }
441
- // Comparar contenido del array de forma más robusta
442
- // Primero intentar comparación rápida por referencia de todo el array usando JSON.stringify
443
- // Esto es más eficiente para detectar si el contenido es realmente diferente
444
- try {
445
- // Normalizar ambos arrays antes de comparar para evitar problemas con orden de propiedades
446
- const normalizeForComparison = (arr: any[]) => {
447
- return arr.map((item) => {
448
- if (
449
- typeof item === 'object' &&
450
- item !== null &&
451
- !Array.isArray(item)
452
- ) {
453
- // Ordenar propiedades del objeto para comparación estable
454
- const sorted = Object.keys(item)
455
- .sort()
456
- .reduce((acc, key) => {
457
- acc[key] = item[key];
458
- return acc;
459
- }, {} as any);
460
- return sorted;
461
- }
462
- return item;
463
- });
464
- };
465
-
466
- const normalizedCurrent =
467
- normalizeForComparison(currentValue);
468
- const normalizedNew =
469
- normalizeForComparison(newValue);
470
-
471
- const currentStr =
472
- JSON.stringify(normalizedCurrent);
473
- const newStr = JSON.stringify(normalizedNew);
474
-
475
- if (currentStr === newStr) {
476
- // Si el contenido es igual, no actualizar para evitar bucles infinitos
477
- return;
478
- }
479
- } catch (e) {
480
- // Si JSON.stringify falla, hacer comparación elemento por elemento
481
- let arraysEqual = true;
482
- for (let idx = 0; idx < newValue.length; idx++) {
483
- const newVal = newValue[idx];
484
- const currentVal = currentValue[idx];
485
-
486
- // Comparación por referencia primero (más rápida)
487
- if (newVal === currentVal) continue;
488
-
489
- // Si son objetos, comparar con JSON.stringify
490
- if (
491
- typeof newVal === 'object' &&
492
- newVal !== null &&
493
- typeof currentVal === 'object' &&
494
- currentVal !== null
495
- ) {
496
- try {
497
- const newStr = JSON.stringify(newVal);
498
- const currentStr =
499
- JSON.stringify(currentVal);
500
- if (newStr !== currentStr) {
501
- arraysEqual = false;
502
- break;
503
- }
504
- } catch (e) {
505
- // Si JSON.stringify falla, comparar por referencia
506
- if (newVal !== currentVal) {
507
- arraysEqual = false;
508
- break;
509
- }
510
- }
511
- } else {
512
- // Para valores primitivos, comparación directa
513
- if (newVal !== currentVal) {
514
- arraysEqual = false;
515
- break;
516
- }
517
- }
518
- }
519
- if (arraysEqual) return;
520
- }
521
- this[prop] = newValue;
522
- updated = true;
523
- return;
524
- }
525
-
526
- // Para objetos, comparar con JSON.stringify pero manejar undefined
527
- if (newIsObject && currentIsObject) {
528
- try {
529
- const currentStr = JSON.stringify(currentValue);
530
- const newStr = JSON.stringify(newValue);
531
- if (currentStr === newStr) return;
532
- } catch (e) {
533
- // Si JSON.stringify falla (por ejemplo, con funciones), comparar por referencia
534
- if (currentValue === newValue) return;
535
- }
536
- }
537
- }
538
-
539
- const descriptor = Object.getOwnPropertyDescriptor(this, prop);
540
-
541
- if (descriptor?.set) return;
542
- this[prop] = newValue;
543
- updated = true;
544
- });
545
- } catch (e) {
546
- console.error(`Error setting properties:`, e);
547
- throw new Error(`Error setting properties: ${e}`);
548
- } finally {
549
- if (updated) this.trigger('change', this);
503
+ // Si no hay setter, asignar directamente
504
+ // Si la propiedad está definida como reactiva en el padre, usar el descriptor del padre
505
+ const parentProto = Object.getPrototypeOf(this);
506
+ if (parentProto && parentProto !== Object.prototype) {
507
+ const parentDescriptor = Object.getOwnPropertyDescriptor(parentProto, prop);
508
+ if (parentDescriptor?.set) {
509
+ // Usar el setter del padre con el valor clonado
510
+ parentDescriptor.set.call(this, valueToSet);
511
+ updated = true;
512
+ return;
513
+ }
550
514
  }
551
- }
552
515
 
553
- hide = () => {
554
- const className = this.getProperties().className || '';
555
- const isHidden = className.includes('hidden');
556
- const cls = isHidden ? className : `${className} hidden`;
557
- if (cls !== className) this.set({ className: cls });
558
- };
559
-
560
- show = () => {
561
- const className = this.getProperties().className || '';
562
- const isHidden = className.includes('hidden');
563
- const cls = isHidden
564
- ? className.replace(/\bhidden\b/g, '').trim()
565
- : className;
566
- if (cls !== className) this.set({ className: cls });
567
- };
568
-
569
- private evaluations: Record<
570
- string,
571
- (value: any, comparisonValue?: any) => boolean
572
- > = {
573
- equal: (value, comparisonValue) => value == comparisonValue,
574
- lower: (value, comparisonValue) =>
575
- Number(value) < Number(comparisonValue),
576
- upper: (value, comparisonValue) =>
577
- Number(value) > Number(comparisonValue),
578
- between: (value, [min, max]) => {
579
- const numValue = Number(value);
580
- return numValue >= Number(min) && numValue <= Number(max);
581
- },
582
- different: (value, comparisonValue) => value != comparisonValue,
583
- hasValue: (value) => ![undefined, null, '', false].includes(value),
584
- empty: (value) => [undefined, null, ''].includes(value),
585
- lessOrEqual: (value, comparisonValue) =>
586
- Number(value) <= Number(comparisonValue),
587
- greaterOrEqual: (value, comparisonValue) =>
588
- Number(value) >= Number(comparisonValue),
589
- };
516
+ // Asignación directa solo si no hay setter en ningún nivel
517
+ this[prop] = valueToSet;
518
+ updated = true;
519
+ });
520
+ } catch (e) {
521
+ console.error(`Error setting properties:`, e);
522
+ throw new Error(`Error setting properties: ${e}`);
523
+ } finally {
524
+ if (updated) this.trigger('change', this);
525
+ }
526
+ }
527
+
528
+ hide = () => {
529
+ const className = this.getProperties().className || '';
530
+ const isHidden = className.includes('hidden');
531
+ const cls = isHidden ? className : `${className} hidden`;
532
+ if (cls !== className) this.set({ className: cls });
533
+ };
534
+
535
+ show = () => {
536
+ const className = this.getProperties().className || '';
537
+ const isHidden = className.includes('hidden');
538
+ const cls = isHidden ? className.replace(/\bhidden\b/g, '').trim() : className;
539
+ if (cls !== className) this.set({ className: cls });
540
+ };
541
+
542
+ private evaluations: Record<string, (value: any, comparisonValue?: any) => boolean> = {
543
+ equal: (value, comparisonValue) => value == comparisonValue,
544
+ lower: (value, comparisonValue) => Number(value) < Number(comparisonValue),
545
+ upper: (value, comparisonValue) => Number(value) > Number(comparisonValue),
546
+ between: (value, [min, max]) => {
547
+ const numValue = Number(value);
548
+ return numValue >= Number(min) && numValue <= Number(max);
549
+ },
550
+ different: (value, comparisonValue) => value != comparisonValue,
551
+ hasValue: value => ![undefined, null, '', false].includes(value),
552
+ empty: value => [undefined, null, ''].includes(value),
553
+ lessOrEqual: (value, comparisonValue) => Number(value) <= Number(comparisonValue),
554
+ greaterOrEqual: (value, comparisonValue) => Number(value) >= Number(comparisonValue),
555
+ };
590
556
  }