@skalfa/skalfa-component 1.0.23 → 1.0.25

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.
@@ -13,7 +13,9 @@ function FormSupervisionComponent({ title, fields, submitControl, confirmation,
13
13
  const [modal, setModal] = (0, react_1.useState)(false);
14
14
  const [fresh, setFresh] = (0, react_1.useState)(true);
15
15
  const [mapGroups, setMapGroups] = (0, react_1.useState)({});
16
- const [{ formControl, setRegister, values, setValues, errors, setErrors, setDefaultValues, submit, loading, confirm, },] = (0, _utils_1.useForm)(submitControl, payload, confirmation, (data) => {
16
+ const [watchState, setWatchState] = (0, react_1.useState)({});
17
+ const watchRef = (0, react_1.useRef)({});
18
+ const [{ formControl, setRegister, setUnregister, values, setValues, errors, setErrors, setDefaultValues, submit, loading, confirm, },] = (0, _utils_1.useForm)(submitControl, payload, confirmation, (data) => {
17
19
  onSuccess?.(data);
18
20
  setModal("success");
19
21
  setTimeout(() => setModal(false), 1000);
@@ -25,6 +27,82 @@ function FormSupervisionComponent({ title, fields, submitControl, confirmation,
25
27
  else
26
28
  setModal("failed");
27
29
  });
30
+ // ==============================>
31
+ // ## Watch: collect watchers from fields
32
+ // ==============================>
33
+ const collectWatchers = (fieldList, prefix) => {
34
+ const result = [];
35
+ for (const f of fieldList) {
36
+ const inputType = f.type || "default";
37
+ const name = prefix ? `${prefix}.${f.construction?.name}` : f.construction?.name || "";
38
+ if (inputType === "cluster") {
39
+ const cluster = f.construction;
40
+ const groupKey = prefix ? `${prefix}.${cluster.name}` : cluster.name;
41
+ const group = mapGroups[groupKey] || [];
42
+ for (const gIndex of group) {
43
+ result.push(...collectWatchers(cluster.fields, `${cluster.name}[${gIndex}]`));
44
+ }
45
+ }
46
+ else if (f.onWatch) {
47
+ result.push({ name, onWatch: f.onWatch, construction: f.construction });
48
+ }
49
+ }
50
+ return result;
51
+ };
52
+ // ==============================>
53
+ // ## Watch: execute watchers on value change
54
+ // ==============================>
55
+ (0, react_1.useEffect)(() => {
56
+ const watchers = collectWatchers(fields);
57
+ if (watchers.length === 0) {
58
+ if (Object.keys(watchRef.current).length > 0) {
59
+ watchRef.current = {};
60
+ setWatchState({});
61
+ }
62
+ return;
63
+ }
64
+ const valMap = {};
65
+ values.forEach((v) => { valMap[v.name] = v.value; });
66
+ const nextState = {};
67
+ const valueUpdates = [];
68
+ for (const { name, onWatch, construction } of watchers) {
69
+ const prev = watchRef.current[name] || {};
70
+ const action = onWatch({ values: valMap, self: name, prev });
71
+ if (!action)
72
+ continue;
73
+ nextState[name] = action;
74
+ if (action.hidden && !prev.hidden)
75
+ setUnregister(name);
76
+ if (action.required !== prev.required) {
77
+ const baseValidations = Array.isArray(construction?.validations) ? [...construction.validations] : [];
78
+ const newValidations = action.required ? (baseValidations.includes("required") ? baseValidations : [...baseValidations, "required"]) : baseValidations.filter((v) => v !== "required");
79
+ setRegister({ name, validations: newValidations });
80
+ }
81
+ if (action.reset) {
82
+ const cur = valMap[name];
83
+ if (cur != null && cur !== "")
84
+ valueUpdates.push({ name, value: "" });
85
+ }
86
+ else if (action.value !== undefined && action.value !== valMap[name]) {
87
+ valueUpdates.push({ name, value: action.value });
88
+ }
89
+ }
90
+ if (JSON.stringify(watchRef.current) !== JSON.stringify(nextState)) {
91
+ watchRef.current = nextState;
92
+ setWatchState(nextState);
93
+ }
94
+ if (valueUpdates.length > 0) {
95
+ const merged = [...values];
96
+ for (const upd of valueUpdates) {
97
+ const idx = merged.findIndex(v => v.name === upd.name);
98
+ if (idx >= 0)
99
+ merged[idx] = upd;
100
+ else
101
+ merged.push(upd);
102
+ }
103
+ setValues(merged);
104
+ }
105
+ }, [values, fields, mapGroups]);
28
106
  const GroupsFromDefaults = (defaults) => {
29
107
  const groups = {};
30
108
  Object.keys(defaults).forEach((key) => {
@@ -53,18 +131,42 @@ function FormSupervisionComponent({ title, fields, submitControl, confirmation,
53
131
  resetFresh();
54
132
  }, [fields]);
55
133
  (0, react_1.useEffect)(() => {
134
+ const minGroups = {};
135
+ const initialValues = [];
136
+ const processMinClusters = (formList, prefix) => {
137
+ formList.forEach((form) => {
138
+ if (form.type === "cluster" && form.construction) {
139
+ const { name: mapName, fields: innerForms, min } = form.construction;
140
+ const groupKey = prefix ? `${prefix}.${mapName}` : mapName;
141
+ const minCount = Math.max(0, min || 0);
142
+ if (minCount > 0) {
143
+ minGroups[groupKey] = Array.from({ length: minCount }, (_, i) => i);
144
+ for (let gIndex = 0; gIndex < minCount; gIndex++) {
145
+ innerForms.forEach((inner) => {
146
+ const fieldName = `${groupKey}[${gIndex}].${inner.construction?.name}`;
147
+ initialValues.push({ name: fieldName, value: "" });
148
+ });
149
+ }
150
+ }
151
+ }
152
+ });
153
+ };
154
+ processMinClusters(fields);
56
155
  if (defaultValue) {
57
156
  setDefaultValues(defaultValue);
58
157
  const derivedGroups = GroupsFromDefaults(defaultValue);
59
- setMapGroups(derivedGroups);
158
+ setMapGroups({ ...minGroups, ...derivedGroups });
60
159
  resetFresh();
61
160
  }
62
161
  else {
63
162
  setDefaultValues(null);
64
- setMapGroups({});
163
+ setMapGroups(minGroups);
164
+ if (initialValues.length > 0) {
165
+ setValues(initialValues);
166
+ }
65
167
  resetFresh();
66
168
  }
67
- }, [defaultValue]);
169
+ }, [defaultValue, fields]);
68
170
  const generateColClass = (col) => String(col).split(" ").map((c) => (c.includes(":") ? `${c.replace(":", ":col-span-")}` : `col-span-${c}`)).join(" ");
69
171
  const inputMap = {
70
172
  default: __1.InputComponent,
@@ -85,13 +187,29 @@ function FormSupervisionComponent({ title, fields, submitControl, confirmation,
85
187
  const renderInput = (form, key, prefix) => {
86
188
  const inputType = form.type || "default";
87
189
  const name = prefix ? `${prefix}.${form.construction?.name}` : form.construction?.name || "input_name";
88
- if (form?.onHide?.(values))
190
+ const valMap = {};
191
+ values.forEach((v) => { valMap[v.name] = v.value; });
192
+ if (form?.onHide?.(valMap))
193
+ return null;
194
+ const ws = watchState[name];
195
+ if (ws?.hidden)
89
196
  return null;
90
197
  if (inputType === "cluster") {
91
- const { name: mapName, fields: innerForms, label, tip, wrap, className } = form.construction;
198
+ const { name: mapName, fields: innerForms, label, tip, wrap, className, min } = form.construction;
199
+ const minCount = Math.max(0, min || 0);
92
200
  const groupKey = prefix ? `${prefix}.${mapName}` : mapName;
93
- const group = mapGroups[groupKey] || [0];
94
- const addGroup = () => setMapGroups((prev) => ({ ...prev, [groupKey]: [...group, group.length] }));
201
+ const defaultGroup = minCount > 0 ? Array.from({ length: minCount }, (_, i) => i) : [];
202
+ const group = mapGroups[groupKey] ?? defaultGroup;
203
+ const showDeleteButton = group.length > minCount;
204
+ const addGroup = () => {
205
+ const nextIndex = group.length;
206
+ setMapGroups((prev) => ({ ...prev, [groupKey]: [...group, nextIndex] }));
207
+ const newGroupValues = innerForms.map((inner) => ({
208
+ name: `${groupKey}[${nextIndex}].${inner.construction?.name}`,
209
+ value: "",
210
+ }));
211
+ setValues([...values, ...newGroupValues]);
212
+ };
95
213
  const removeGroup = (index) => {
96
214
  const filteredGroup = group.filter((_, i) => i !== index);
97
215
  const newGroup = filteredGroup.map((_, i) => i);
@@ -116,14 +234,15 @@ function FormSupervisionComponent({ title, fields, submitControl, confirmation,
116
234
  });
117
235
  setValues(updatedValues);
118
236
  };
119
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("flex flex-col gap-4", generateColClass(form.col || "12")), children: [group.map((gIndex) => ((0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("relative pr-8", wrap && "p-4 rounded border", className), children: [label && (0, jsx_runtime_1.jsxs)("p", { className: "input-label", children: [label, " ", gIndex + 1] }), tip && (0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-tip"), children: tip }), (label || tip) && (0, jsx_runtime_1.jsx)("div", { className: "mb-2" }), (0, jsx_runtime_1.jsx)("div", { className: "w-full grid grid-cols-12 gap-4", children: innerForms.map((inner, i) => renderInput(inner, i, `${mapName}[${gIndex}]`)) }), (0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/times", paint: "danger", variant: "outline", size: "xs", className: (0, _utils_1.cn)("absolute top-10 right-2 translate-x-[50%] -translate-y-[50%]", wrap && "translate-x-0 -translate-y-0 top-1 right-1"), onClick: () => removeGroup(gIndex) })] }, gIndex))), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/plus", label: `${l.base.add ? l.base.add() : "Add"} ${label || mapName}`, variant: "outline", size: "sm", onClick: addGroup }) })] }, key));
237
+ const clusterError = errors?.find((err) => err.name === groupKey || err.name === mapName)?.error;
238
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("flex flex-col gap-4", generateColClass(form.col || "12")), children: [label && (0, jsx_runtime_1.jsx)("p", { className: "input-label", children: label }), clusterError && (0, jsx_runtime_1.jsx)("small", { className: "input-error-message", children: clusterError }), group.map((gIndex) => ((0, jsx_runtime_1.jsxs)("div", { className: (0, _utils_1.cn)("relative pr-8", wrap && "p-4 rounded border", className), children: [label && (0, jsx_runtime_1.jsxs)("p", { className: "input-label", children: [label, " ", gIndex + 1] }), tip && (0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-tip"), children: tip }), (label || tip) && (0, jsx_runtime_1.jsx)("div", { className: "mb-2" }), (0, jsx_runtime_1.jsx)("div", { className: "w-full grid grid-cols-12 gap-4", children: innerForms.map((inner, i) => renderInput(inner, i, `${groupKey}[${gIndex}]`)) }), showDeleteButton && ((0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/times", paint: "danger", variant: "outline", size: "xs", className: (0, _utils_1.cn)("absolute top-10 right-2 translate-x-[50%] -translate-y-[50%]", wrap && "translate-x-0 -translate-y-0 top-1 right-1"), onClick: () => removeGroup(gIndex) }))] }, gIndex))), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(__1.ButtonComponent, { icon: "solid/plus", label: `${l.base.add ? l.base.add() : "Add"} ${label || mapName}`, variant: "outline", size: "sm", onClick: addGroup }) })] }, key));
120
239
  }
121
240
  if (inputType === "custom") {
122
241
  const customRender = form.construction;
123
242
  return ((0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)(form.className, generateColClass(form.col || "12")), children: customRender?.({ formControl, values, setValues, errors, setErrors, setRegister, prefixName: prefix }) }, key));
124
243
  }
125
244
  const Component = inputMap[inputType] || __1.InputComponent;
126
- return ((0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)(form.className, generateColClass(form.col || "12")), children: (0, jsx_runtime_1.jsx)(Component, { ...form.construction, ...formControl(name) }) }, key));
245
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)(form.className, generateColClass(form.col || "12")), children: (0, jsx_runtime_1.jsx)(Component, { ...form.construction, ...formControl(name), disabled: ws?.disabled, readOnly: ws?.readonly, name: name }) }, key));
127
246
  };
128
247
  (0, react_1.useEffect)(() => {
129
248
  (modal == "failed") && setModal(false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skalfa/skalfa-component",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "Reusable UI components for Skalfa App.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -72,7 +72,7 @@ export function InputComponent({
72
72
  // =========================>
73
73
  // ## Initial
74
74
  // =========================>
75
- const inputHandler = useInputHandler(props.name, value, validations, register, props.type == "file")
75
+ const inputHandler = useInputHandler(props.name, value, validations, register, props.type == "file", unregister)
76
76
  const randomId = useInputRandomId()
77
77
 
78
78
 
@@ -70,7 +70,7 @@ export function InputCheckboxComponent({
70
70
  // =========================>
71
71
  // ## initial
72
72
  // =========================>
73
- const inputHandler = useInputHandler(name, value, validations, register, false)
73
+ const inputHandler = useInputHandler(name, value, validations, register, false, unregister)
74
74
 
75
75
 
76
76
  // =========================>
@@ -55,7 +55,7 @@ export function InputCurrencyComponent({
55
55
  // =========================>
56
56
  // ## Initial
57
57
  // =========================>
58
- const inputHandler = useInputHandler(props.name, value, validations, register, false)
58
+ const inputHandler = useInputHandler(props.name, value, validations, register, false, unregister)
59
59
  const randomId = useInputRandomId()
60
60
 
61
61
 
@@ -54,7 +54,7 @@ export function InputDateComponent({
54
54
  // =========================>
55
55
  // ## Initial
56
56
  // =========================>
57
- const inputHandler = useInputHandler(props.name, value, validations, register, false)
57
+ const inputHandler = useInputHandler(props.name, value, validations, register, false, unregister)
58
58
  const randomId = useInputRandomId()
59
59
 
60
60
 
@@ -61,7 +61,7 @@ export function InputDatetimeComponent({
61
61
  // =========================>
62
62
  // ## Initial
63
63
  // =========================>
64
- const inputHandler = useInputHandler(props.name, value, validations, register, false)
64
+ const inputHandler = useInputHandler(props.name, value, validations, register, false, unregister)
65
65
  const randomId = useInputRandomId()
66
66
 
67
67
 
@@ -73,7 +73,7 @@ export function InputDocumentComponent({
73
73
  // =========================>
74
74
  // ## Initial
75
75
  // =========================>
76
- const inputHandler = useInputHandler(props.name, value, validations, register, props.type == "file")
76
+ const inputHandler = useInputHandler(props.name, value, validations, register, props.type == "file", unregister)
77
77
  const randomId = useInputRandomId()
78
78
 
79
79
  // =========================>
@@ -56,7 +56,7 @@ export const InputImageComponent: React.FC<InputImageProps> = ({
56
56
  const [cropSrc, setCropSrc] = useState<string | null>(null);
57
57
  const [openCrop, setOpenCrop] = useState(false);
58
58
 
59
- const inputHandler = useInputHandler(name, value, validations, register, true);
59
+ const inputHandler = useInputHandler(name, value, validations, register, true, unregister);
60
60
  const [invalidMessage, setInvalidMessage] = useValidation(inputHandler.value, validations, invalid, inputHandler.idle);
61
61
 
62
62
  useEffect(() => {
@@ -53,7 +53,7 @@ export function InputNumberComponent({
53
53
  // =========================>
54
54
  // ## Initial
55
55
  // =========================>
56
- const inputHandler = useInputHandler(props.name, value, validations, register, false)
56
+ const inputHandler = useInputHandler(props.name, value, validations, register, false, unregister)
57
57
  const randomId = useInputRandomId()
58
58
 
59
59
 
@@ -55,7 +55,7 @@ export function InputPasswordComponent({
55
55
  // =========================>
56
56
  // ## Initial
57
57
  // =========================>
58
- const inputHandler = useInputHandler(props.name, value, validations, register, false)
58
+ const inputHandler = useInputHandler(props.name, value, validations, register, false, unregister)
59
59
  const randomId = useInputRandomId()
60
60
  const randomConfirmId = useInputRandomId()
61
61
 
@@ -68,7 +68,7 @@ export function InputRadioComponent({
68
68
  // =========================>
69
69
  // ## Initial
70
70
  // =========================>
71
- const inputHandler = useInputHandler(name, value, validations, register, false)
71
+ const inputHandler = useInputHandler(name, value, validations, register, false, unregister)
72
72
 
73
73
 
74
74
  // =========================>
@@ -53,7 +53,7 @@ export function InputTimeComponent({
53
53
  // =========================>
54
54
  // ## Initial
55
55
  // =========================>
56
- const inputHandler = useInputHandler(props.name, value, validations, register, false)
56
+ const inputHandler = useInputHandler(props.name, value, validations, register, false, unregister)
57
57
  const randomId = useInputRandomId()
58
58
 
59
59