@scm-manager/ui-forms 2.42.3 → 2.42.4-20230213-150253

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/Form.tsx
2
- import React18, { useCallback, useEffect, useState } from "react";
2
+ import React2, { useCallback, useEffect, useState } from "react";
3
3
  import { useForm } from "react-hook-form";
4
4
  import { ErrorNotification, Level } from "@scm-manager/ui-components";
5
5
 
@@ -18,73 +18,179 @@ function useScmFormContext() {
18
18
  // src/Form.tsx
19
19
  import { useTranslation } from "react-i18next";
20
20
  import { Button } from "@scm-manager/ui-buttons";
21
+ var SuccessNotification = ({ label, hide }) => {
22
+ if (!label) {
23
+ return null;
24
+ }
25
+ return /* @__PURE__ */ React2.createElement("div", {
26
+ className: "notification is-success"
27
+ }, /* @__PURE__ */ React2.createElement("button", {
28
+ className: "delete",
29
+ onClick: hide
30
+ }), label);
31
+ };
32
+ function Form({
33
+ children,
34
+ onSubmit,
35
+ defaultValues,
36
+ translationPath,
37
+ readOnly,
38
+ submitButtonTestId
39
+ }) {
40
+ const form = useForm({
41
+ mode: "onChange",
42
+ defaultValues
43
+ });
44
+ const { formState, handleSubmit, reset } = form;
45
+ const [ns, prefix] = translationPath;
46
+ const { t } = useTranslation(ns, { keyPrefix: prefix });
47
+ const [defaultTranslate] = useTranslation("commons", { keyPrefix: "form" });
48
+ const translateWithFallback = useCallback(
49
+ (key, ...args) => {
50
+ const translation = t(key, ...args);
51
+ if (translation === `${prefix}.${key}`) {
52
+ return "";
53
+ }
54
+ return translation;
55
+ },
56
+ [prefix, t]
57
+ );
58
+ const { isDirty, isValid, isSubmitting, isSubmitSuccessful } = formState;
59
+ const [error, setError] = useState();
60
+ const [showSuccessNotification, setShowSuccessNotification] = useState(false);
61
+ const submitButtonLabel = t("submit", { defaultValue: defaultTranslate("submit") });
62
+ const successNotification = translateWithFallback("submit-success-notification", {
63
+ defaultValue: defaultTranslate("submit-success-notification")
64
+ });
65
+ useEffect(() => {
66
+ if (isSubmitSuccessful) {
67
+ setShowSuccessNotification(true);
68
+ }
69
+ }, [isSubmitSuccessful]);
70
+ useEffect(() => reset(defaultValues), [defaultValues, reset]);
71
+ useEffect(() => {
72
+ if (isDirty) {
73
+ setShowSuccessNotification(false);
74
+ }
75
+ }, [isDirty]);
76
+ const submit = useCallback(
77
+ async (data) => {
78
+ setError(null);
79
+ try {
80
+ return await onSubmit(data);
81
+ } catch (e) {
82
+ if (e instanceof Error) {
83
+ setError(e);
84
+ } else {
85
+ throw e;
86
+ }
87
+ }
88
+ },
89
+ [onSubmit]
90
+ );
91
+ return /* @__PURE__ */ React2.createElement(ScmFormContextProvider, {
92
+ ...form,
93
+ readOnly: isSubmitting || readOnly,
94
+ t: translateWithFallback,
95
+ formId: prefix
96
+ }, /* @__PURE__ */ React2.createElement("form", {
97
+ onSubmit: handleSubmit(submit),
98
+ id: prefix
99
+ }), showSuccessNotification ? /* @__PURE__ */ React2.createElement(SuccessNotification, {
100
+ label: successNotification,
101
+ hide: () => setShowSuccessNotification(false)
102
+ }) : null, typeof children === "function" ? children(form) : children, error ? /* @__PURE__ */ React2.createElement(ErrorNotification, {
103
+ error
104
+ }) : null, !readOnly ? /* @__PURE__ */ React2.createElement(Level, {
105
+ right: /* @__PURE__ */ React2.createElement(Button, {
106
+ type: "submit",
107
+ variant: "primary",
108
+ testId: submitButtonTestId ?? "submit-button",
109
+ disabled: !isDirty || !isValid,
110
+ isLoading: isSubmitting,
111
+ form: prefix
112
+ }, submitButtonLabel)
113
+ }) : null);
114
+ }
115
+ var Form_default = Form;
21
116
 
22
117
  // src/FormRow.tsx
23
- import React2 from "react";
118
+ import React3 from "react";
24
119
  import classNames from "classnames";
25
- var FormRow = React2.forwardRef(
26
- ({ className, children, hidden, ...rest }, ref) => hidden ? null : /* @__PURE__ */ React2.createElement("div", {
120
+ import styled from "styled-components";
121
+ var FormRowDiv = styled.div`
122
+ .field {
123
+ margin-left: 0;
124
+ }
125
+
126
+ gap: 1rem;
127
+
128
+ &:not(:last-child) {
129
+ margin-bottom: 1rem;
130
+ }
131
+ `;
132
+ var FormRow = React3.forwardRef(
133
+ ({ className, children, hidden, ...rest }, ref) => hidden ? null : /* @__PURE__ */ React3.createElement(FormRowDiv, {
27
134
  ref,
28
- className: classNames("columns", className),
135
+ className: classNames("is-flex is-flex-wrap-wrap", className),
29
136
  ...rest
30
137
  }, children)
31
138
  );
32
139
  var FormRow_default = FormRow;
33
140
 
34
141
  // src/input/ControlledInputField.tsx
35
- import React10 from "react";
142
+ import React12 from "react";
36
143
  import { Controller } from "react-hook-form";
37
- import classNames8 from "classnames";
38
144
 
39
145
  // src/input/InputField.tsx
40
- import React9 from "react";
146
+ import React10 from "react";
41
147
 
42
148
  // src/base/Field.tsx
43
- import React3 from "react";
149
+ import React4 from "react";
44
150
  import classNames2 from "classnames";
45
- var Field = ({ className, children, ...rest }) => /* @__PURE__ */ React3.createElement("div", {
151
+ var Field = ({ className, children, ...rest }) => /* @__PURE__ */ React4.createElement("div", {
46
152
  className: classNames2("field", className),
47
153
  ...rest
48
154
  }, children);
49
155
  var Field_default = Field;
50
156
 
51
157
  // src/base/Control.tsx
52
- import React4 from "react";
158
+ import React5 from "react";
53
159
  import classNames3 from "classnames";
54
- var Control = ({ className, children, ...rest }) => /* @__PURE__ */ React4.createElement("div", {
160
+ var Control = ({ className, children, ...rest }) => /* @__PURE__ */ React5.createElement("div", {
55
161
  className: classNames3("control", className),
56
162
  ...rest
57
163
  }, children);
58
164
  var Control_default = Control;
59
165
 
60
166
  // src/base/label/Label.tsx
61
- import React5 from "react";
167
+ import React6 from "react";
62
168
  import classNames4 from "classnames";
63
- var Label = ({ className, children, ...rest }) => /* @__PURE__ */ React5.createElement("label", {
169
+ var Label = ({ className, children, ...rest }) => /* @__PURE__ */ React6.createElement("label", {
64
170
  className: classNames4("label", className),
65
171
  ...rest
66
172
  }, children);
67
173
  var Label_default = Label;
68
174
 
69
175
  // src/base/field-message/FieldMessage.tsx
70
- import React6 from "react";
176
+ import React7 from "react";
71
177
  import classNames5 from "classnames";
72
178
 
73
179
  // src/variants.ts
74
180
  var createVariantClass = (variant) => variant ? `is-${variant}` : void 0;
75
181
 
76
182
  // src/base/field-message/FieldMessage.tsx
77
- var FieldMessage = ({ variant, className, children }) => /* @__PURE__ */ React6.createElement("p", {
183
+ var FieldMessage = ({ variant, className, children }) => /* @__PURE__ */ React7.createElement("p", {
78
184
  className: classNames5("help", createVariantClass(variant), className)
79
185
  }, children);
80
186
  var FieldMessage_default = FieldMessage;
81
187
 
82
188
  // src/input/Input.tsx
83
- import React7 from "react";
189
+ import React8 from "react";
84
190
  import classNames6 from "classnames";
85
191
  import { createAttributesForTesting } from "@scm-manager/ui-components";
86
- var Input = React7.forwardRef(({ variant, className, testId, ...props }, ref) => {
87
- return /* @__PURE__ */ React7.createElement("input", {
192
+ var Input = React8.forwardRef(({ variant, className, testId, ...props }, ref) => {
193
+ return /* @__PURE__ */ React8.createElement("input", {
88
194
  ref,
89
195
  className: classNames6("input", createVariantClass(variant), className),
90
196
  ...props,
@@ -94,85 +200,110 @@ var Input = React7.forwardRef(({ variant, className, testId, ...props }, ref) =>
94
200
  var Input_default = Input;
95
201
 
96
202
  // src/base/help/Help.tsx
97
- import React8 from "react";
98
- import classNames7 from "classnames";
99
- var Help = ({ text, className }) => /* @__PURE__ */ React8.createElement("span", {
100
- className: classNames7("fas fa-fw fa-question-circle has-text-blue-light", className),
101
- title: text
102
- });
203
+ import React9 from "react";
204
+ import { Tooltip } from "@scm-manager/ui-overlays";
205
+ var Help = ({ text, className }) => /* @__PURE__ */ React9.createElement(Tooltip, {
206
+ className,
207
+ message: text
208
+ }, /* @__PURE__ */ React9.createElement("i", {
209
+ className: "fas fa-fw fa-question-circle has-text-blue-light"
210
+ }));
103
211
  var Help_default = Help;
104
212
 
105
213
  // src/input/InputField.tsx
106
- var InputField = React9.forwardRef(
214
+ var InputField = React10.forwardRef(
107
215
  ({ label, helpText, error, className, ...props }, ref) => {
108
216
  const variant = error ? "danger" : void 0;
109
- return /* @__PURE__ */ React9.createElement(Field_default, {
217
+ return /* @__PURE__ */ React10.createElement(Field_default, {
110
218
  className
111
- }, /* @__PURE__ */ React9.createElement(Label_default, null, label, helpText ? /* @__PURE__ */ React9.createElement(Help_default, {
219
+ }, /* @__PURE__ */ React10.createElement(Label_default, null, label, helpText ? /* @__PURE__ */ React10.createElement(Help_default, {
112
220
  className: "ml-1",
113
221
  text: helpText
114
- }) : null), /* @__PURE__ */ React9.createElement(Control_default, null, /* @__PURE__ */ React9.createElement(Input_default, {
222
+ }) : null), /* @__PURE__ */ React10.createElement(Control_default, null, /* @__PURE__ */ React10.createElement(Input_default, {
115
223
  variant,
116
224
  ref,
117
225
  ...props
118
- })), error ? /* @__PURE__ */ React9.createElement(FieldMessage_default, {
226
+ })), error ? /* @__PURE__ */ React10.createElement(FieldMessage_default, {
119
227
  variant
120
228
  }, error) : null);
121
229
  }
122
230
  );
123
231
  var InputField_default = InputField;
124
232
 
233
+ // src/FormPathContext.tsx
234
+ import React11, { useContext as useContext2, useMemo } from "react";
235
+ var ScmFormPathContext = React11.createContext("");
236
+ function useScmFormPathContext() {
237
+ return useContext2(ScmFormPathContext);
238
+ }
239
+ var ScmFormPathContextProvider = ({ children, path }) => /* @__PURE__ */ React11.createElement(ScmFormPathContext.Provider, {
240
+ value: path
241
+ }, children);
242
+ var ScmNestedFormPathContextProvider = ({ children, path }) => {
243
+ const prefix = useScmFormPathContext();
244
+ const pathWithPrefix = useMemo(() => prefix ? `${prefix}.${path}` : path, [path, prefix]);
245
+ return /* @__PURE__ */ React11.createElement(ScmFormPathContext.Provider, {
246
+ value: pathWithPrefix
247
+ }, children);
248
+ };
249
+
250
+ // src/helpers.ts
251
+ function prefixWithoutIndices(path) {
252
+ return path.replace(/(\.\d+)/g, "");
253
+ }
254
+
125
255
  // src/input/ControlledInputField.tsx
126
256
  function ControlledInputField({
127
257
  name,
128
258
  label,
129
259
  helpText,
130
260
  rules,
131
- className,
132
261
  testId,
133
262
  defaultValue,
134
263
  readOnly,
135
264
  ...props
136
265
  }) {
137
- const { control, t, readOnly: formReadonly } = useScmFormContext();
138
- const labelTranslation = label || t(`${name}.label`) || "";
139
- const helpTextTranslation = helpText || t(`${name}.helpText`);
140
- return /* @__PURE__ */ React10.createElement(Controller, {
266
+ const { control, t, readOnly: formReadonly, formId } = useScmFormContext();
267
+ const formPathPrefix = useScmFormPathContext();
268
+ const nameWithPrefix = formPathPrefix ? `${formPathPrefix}.${name}` : name;
269
+ const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix);
270
+ const labelTranslation = label || t(`${prefixedNameWithoutIndices}.label`) || "";
271
+ const helpTextTranslation = helpText || t(`${prefixedNameWithoutIndices}.helpText`);
272
+ return /* @__PURE__ */ React12.createElement(Controller, {
141
273
  control,
142
- name,
274
+ name: nameWithPrefix,
143
275
  rules,
144
276
  defaultValue,
145
- render: ({ field, fieldState }) => /* @__PURE__ */ React10.createElement(InputField_default, {
146
- className: classNames8("column", className),
277
+ render: ({ field, fieldState }) => /* @__PURE__ */ React12.createElement(InputField_default, {
147
278
  readOnly: readOnly ?? formReadonly,
148
279
  required: rules == null ? void 0 : rules.required,
149
280
  ...props,
150
281
  ...field,
282
+ form: formId,
151
283
  label: labelTranslation,
152
284
  helpText: helpTextTranslation,
153
- error: fieldState.error ? fieldState.error.message || t(`${name}.error.${fieldState.error.type}`) : void 0,
154
- testId: testId ?? `input-${name}`
285
+ error: fieldState.error ? fieldState.error.message || t(`${prefixedNameWithoutIndices}.error.${fieldState.error.type}`) : void 0,
286
+ testId: testId ?? `input-${nameWithPrefix}`
155
287
  })
156
288
  });
157
289
  }
158
290
  var ControlledInputField_default = ControlledInputField;
159
291
 
160
292
  // src/checkbox/ControlledCheckboxField.tsx
161
- import React13 from "react";
293
+ import React15 from "react";
162
294
  import { Controller as Controller2 } from "react-hook-form";
163
- import classNames9 from "classnames";
164
295
 
165
296
  // src/checkbox/CheckboxField.tsx
166
- import React12 from "react";
297
+ import React14 from "react";
167
298
 
168
299
  // src/checkbox/Checkbox.tsx
169
- import React11 from "react";
300
+ import React13 from "react";
170
301
  import { createAttributesForTesting as createAttributesForTesting2 } from "@scm-manager/ui-components";
171
- var Checkbox = React11.forwardRef(
172
- ({ readOnly, label, value, name, checked, defaultChecked, defaultValue, testId, helpText, ...props }, ref) => /* @__PURE__ */ React11.createElement("label", {
302
+ var Checkbox = React13.forwardRef(
303
+ ({ readOnly, label, value, name, checked, defaultChecked, defaultValue, testId, helpText, ...props }, ref) => /* @__PURE__ */ React13.createElement("label", {
173
304
  className: "checkbox",
174
305
  disabled: readOnly || props.disabled
175
- }, readOnly ? /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement("input", {
306
+ }, readOnly ? /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement("input", {
176
307
  type: "hidden",
177
308
  name,
178
309
  value,
@@ -180,7 +311,7 @@ var Checkbox = React11.forwardRef(
180
311
  checked,
181
312
  defaultChecked,
182
313
  readOnly: true
183
- }), /* @__PURE__ */ React11.createElement("input", {
314
+ }), /* @__PURE__ */ React13.createElement("input", {
184
315
  type: "checkbox",
185
316
  className: "mr-1",
186
317
  ref,
@@ -191,7 +322,7 @@ var Checkbox = React11.forwardRef(
191
322
  ...props,
192
323
  ...createAttributesForTesting2(testId),
193
324
  disabled: true
194
- })) : /* @__PURE__ */ React11.createElement("input", {
325
+ })) : /* @__PURE__ */ React13.createElement("input", {
195
326
  type: "checkbox",
196
327
  className: "mr-1",
197
328
  ref,
@@ -202,7 +333,7 @@ var Checkbox = React11.forwardRef(
202
333
  defaultChecked,
203
334
  ...props,
204
335
  ...createAttributesForTesting2(testId)
205
- }), label, helpText ? /* @__PURE__ */ React11.createElement(Help_default, {
336
+ }), label, helpText ? /* @__PURE__ */ React13.createElement(Help_default, {
206
337
  className: "ml-1",
207
338
  text: helpText
208
339
  }) : null)
@@ -210,9 +341,9 @@ var Checkbox = React11.forwardRef(
210
341
  var Checkbox_default = Checkbox;
211
342
 
212
343
  // src/checkbox/CheckboxField.tsx
213
- var CheckboxField = React12.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React12.createElement(Field_default, {
344
+ var CheckboxField = React14.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React14.createElement(Field_default, {
214
345
  className
215
- }, /* @__PURE__ */ React12.createElement(Control_default, null, /* @__PURE__ */ React12.createElement(Checkbox_default, {
346
+ }, /* @__PURE__ */ React14.createElement(Control_default, null, /* @__PURE__ */ React14.createElement(Checkbox_default, {
216
347
  ref,
217
348
  ...props
218
349
  }))));
@@ -224,38 +355,39 @@ function ControlledInputField2({
224
355
  label,
225
356
  helpText,
226
357
  rules,
227
- className,
228
358
  testId,
229
359
  defaultChecked,
230
360
  readOnly,
231
361
  ...props
232
362
  }) {
233
- const { control, t, readOnly: formReadonly } = useScmFormContext();
234
- const labelTranslation = label || t(`${name}.label`) || "";
235
- const helpTextTranslation = helpText || t(`${name}.helpText`);
236
- return /* @__PURE__ */ React13.createElement(Controller2, {
363
+ const { control, t, readOnly: formReadonly, formId } = useScmFormContext();
364
+ const formPathPrefix = useScmFormPathContext();
365
+ const nameWithPrefix = formPathPrefix ? `${formPathPrefix}.${name}` : name;
366
+ const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix);
367
+ const labelTranslation = label || t(`${prefixedNameWithoutIndices}.label`) || "";
368
+ const helpTextTranslation = helpText || t(`${prefixedNameWithoutIndices}.helpText`);
369
+ return /* @__PURE__ */ React15.createElement(Controller2, {
237
370
  control,
238
- name,
371
+ name: nameWithPrefix,
239
372
  rules,
240
373
  defaultValue: defaultChecked,
241
- render: ({ field }) => /* @__PURE__ */ React13.createElement(CheckboxField_default, {
242
- className: classNames9("column", className),
374
+ render: ({ field }) => /* @__PURE__ */ React15.createElement(CheckboxField_default, {
375
+ form: formId,
243
376
  readOnly: readOnly ?? formReadonly,
244
377
  defaultChecked: field.value,
245
378
  ...props,
246
379
  ...field,
247
380
  label: labelTranslation,
248
381
  helpText: helpTextTranslation,
249
- testId: testId ?? `checkbox-${name}`
382
+ testId: testId ?? `checkbox-${nameWithPrefix}`
250
383
  })
251
384
  });
252
385
  }
253
386
  var ControlledCheckboxField_default = ControlledInputField2;
254
387
 
255
388
  // src/input/ControlledSecretConfirmationField.tsx
256
- import React14 from "react";
389
+ import React16 from "react";
257
390
  import { Controller as Controller3 } from "react-hook-form";
258
- import classNames10 from "classnames";
259
391
  function ControlledSecretConfirmationField({
260
392
  name,
261
393
  label,
@@ -271,47 +403,52 @@ function ControlledSecretConfirmationField({
271
403
  readOnly,
272
404
  ...props
273
405
  }) {
274
- const { control, watch, t, readOnly: formReadonly } = useScmFormContext();
275
- const labelTranslation = label || t(`${name}.label`) || "";
276
- const helpTextTranslation = helpText || t(`${name}.helpText`);
277
- const confirmationLabelTranslation = confirmationLabel || t(`${name}.confirmation.label`) || "";
278
- const confirmationHelpTextTranslation = confirmationHelpText || t(`${name}.confirmation.helpText`);
279
- const confirmationErrorMessageTranslation = confirmationErrorMessage || t(`${name}.confirmation.errorMessage`);
280
- const secretValue = watch(name);
281
- return /* @__PURE__ */ React14.createElement(React14.Fragment, null, /* @__PURE__ */ React14.createElement(Controller3, {
406
+ const { control, watch, t, readOnly: formReadonly, formId } = useScmFormContext();
407
+ const formPathPrefix = useScmFormPathContext();
408
+ const nameWithPrefix = formPathPrefix ? `${formPathPrefix}.${name}` : name;
409
+ const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix);
410
+ const labelTranslation = label || t(`${prefixedNameWithoutIndices}.label`) || "";
411
+ const helpTextTranslation = helpText || t(`${prefixedNameWithoutIndices}.helpText`);
412
+ const confirmationLabelTranslation = confirmationLabel || t(`${prefixedNameWithoutIndices}.confirmation.label`) || "";
413
+ const confirmationHelpTextTranslation = confirmationHelpText || t(`${prefixedNameWithoutIndices}.confirmation.helpText`);
414
+ const confirmationErrorMessageTranslation = confirmationErrorMessage || t(`${prefixedNameWithoutIndices}.confirmation.errorMessage`);
415
+ const secretValue = watch(nameWithPrefix);
416
+ return /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(Controller3, {
282
417
  control,
283
- name,
418
+ name: nameWithPrefix,
284
419
  defaultValue,
285
420
  rules: {
286
421
  ...rules,
287
- deps: [`${name}Confirmation`]
422
+ deps: [`${nameWithPrefix}Confirmation`]
288
423
  },
289
- render: ({ field, fieldState }) => /* @__PURE__ */ React14.createElement(InputField_default, {
290
- className: classNames10("column", className),
424
+ render: ({ field, fieldState }) => /* @__PURE__ */ React16.createElement(InputField_default, {
425
+ className,
291
426
  readOnly: readOnly ?? formReadonly,
292
427
  ...props,
293
428
  ...field,
429
+ form: formId,
294
430
  required: rules == null ? void 0 : rules.required,
295
431
  type: "password",
296
432
  label: labelTranslation,
297
433
  helpText: helpTextTranslation,
298
- error: fieldState.error ? fieldState.error.message || t(`${name}.error.${fieldState.error.type}`) : void 0,
299
- testId: testId ?? `input-${name}`
434
+ error: fieldState.error ? fieldState.error.message || t(`${prefixedNameWithoutIndices}.error.${fieldState.error.type}`) : void 0,
435
+ testId: testId ?? `input-${nameWithPrefix}`
300
436
  })
301
- }), /* @__PURE__ */ React14.createElement(Controller3, {
437
+ }), /* @__PURE__ */ React16.createElement(Controller3, {
302
438
  control,
303
- name: `${name}Confirmation`,
439
+ name: `${nameWithPrefix}Confirmation`,
304
440
  defaultValue,
305
- render: ({ field, fieldState }) => /* @__PURE__ */ React14.createElement(InputField_default, {
306
- className: classNames10("column", className),
441
+ render: ({ field, fieldState }) => /* @__PURE__ */ React16.createElement(InputField_default, {
442
+ className,
307
443
  type: "password",
308
444
  readOnly: readOnly ?? formReadonly,
309
445
  disabled: props.disabled,
310
446
  ...field,
447
+ form: formId,
311
448
  label: confirmationLabelTranslation,
312
449
  helpText: confirmationHelpTextTranslation,
313
- error: fieldState.error ? fieldState.error.message || t(`${name}.confirmation.error.${fieldState.error.type}`) : void 0,
314
- testId: confirmationTestId ?? `input-${name}-confirmation`
450
+ error: fieldState.error ? fieldState.error.message || t(`${prefixedNameWithoutIndices}.confirmation.error.${fieldState.error.type}`) : void 0,
451
+ testId: confirmationTestId ?? `input-${nameWithPrefix}-confirmation`
315
452
  }),
316
453
  rules: {
317
454
  validate: (value) => secretValue === value || confirmationErrorMessageTranslation
@@ -320,40 +457,44 @@ function ControlledSecretConfirmationField({
320
457
  }
321
458
 
322
459
  // src/select/ControlledSelectField.tsx
323
- import React17 from "react";
460
+ import React19 from "react";
324
461
  import { Controller as Controller4 } from "react-hook-form";
325
- import classNames12 from "classnames";
326
462
 
327
463
  // src/select/SelectField.tsx
328
- import React16 from "react";
464
+ import React18 from "react";
329
465
 
330
466
  // src/select/Select.tsx
331
- import React15 from "react";
332
- import classNames11 from "classnames";
467
+ import React17 from "react";
468
+ import classNames7 from "classnames";
333
469
  import { createAttributesForTesting as createAttributesForTesting3 } from "@scm-manager/ui-components";
334
- var Select = React15.forwardRef(({ variant, children, className, testId, ...props }, ref) => /* @__PURE__ */ React15.createElement("div", {
335
- className: classNames11("select", { "is-multiple": props.multiple }, createVariantClass(variant), className)
336
- }, /* @__PURE__ */ React15.createElement("select", {
337
- ref,
338
- ...props,
339
- ...createAttributesForTesting3(testId)
340
- }, children)));
470
+ var Select = React17.forwardRef(
471
+ ({ variant, children, className, options, testId, ...props }, ref) => /* @__PURE__ */ React17.createElement("div", {
472
+ className: classNames7("select", { "is-multiple": props.multiple }, createVariantClass(variant), className)
473
+ }, /* @__PURE__ */ React17.createElement("select", {
474
+ ref,
475
+ ...props,
476
+ ...createAttributesForTesting3(testId)
477
+ }, options ? options.map((option) => /* @__PURE__ */ React17.createElement("option", {
478
+ ...option,
479
+ key: option.value
480
+ }, option.label, option.children)) : children))
481
+ );
341
482
  var Select_default = Select;
342
483
 
343
484
  // src/select/SelectField.tsx
344
- var SelectField = React16.forwardRef(
485
+ var SelectField = React18.forwardRef(
345
486
  ({ label, helpText, error, className, ...props }, ref) => {
346
487
  const variant = error ? "danger" : void 0;
347
- return /* @__PURE__ */ React16.createElement(Field_default, {
488
+ return /* @__PURE__ */ React18.createElement(Field_default, {
348
489
  className
349
- }, /* @__PURE__ */ React16.createElement(Label_default, null, label, helpText ? /* @__PURE__ */ React16.createElement(Help_default, {
490
+ }, /* @__PURE__ */ React18.createElement(Label_default, null, label, helpText ? /* @__PURE__ */ React18.createElement(Help_default, {
350
491
  className: "ml-1",
351
492
  text: helpText
352
- }) : null), /* @__PURE__ */ React16.createElement(Control_default, null, /* @__PURE__ */ React16.createElement(Select_default, {
493
+ }) : null), /* @__PURE__ */ React18.createElement(Control_default, null, /* @__PURE__ */ React18.createElement(Select_default, {
353
494
  variant,
354
495
  ref,
355
496
  ...props
356
- })), error ? /* @__PURE__ */ React16.createElement(FieldMessage_default, {
497
+ })), error ? /* @__PURE__ */ React18.createElement(FieldMessage_default, {
357
498
  variant
358
499
  }, error) : null);
359
500
  }
@@ -366,140 +507,223 @@ function ControlledSelectField({
366
507
  label,
367
508
  helpText,
368
509
  rules,
369
- className,
370
510
  testId,
371
511
  defaultValue,
372
512
  readOnly,
373
513
  ...props
374
514
  }) {
375
- const { control, t, readOnly: formReadonly } = useScmFormContext();
376
- const labelTranslation = label || t(`${name}.label`) || "";
377
- const helpTextTranslation = helpText || t(`${name}.helpText`);
378
- return /* @__PURE__ */ React17.createElement(Controller4, {
515
+ const { control, t, readOnly: formReadonly, formId } = useScmFormContext();
516
+ const formPathPrefix = useScmFormPathContext();
517
+ const nameWithPrefix = formPathPrefix ? `${formPathPrefix}.${name}` : name;
518
+ const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix);
519
+ const labelTranslation = label || t(`${prefixedNameWithoutIndices}.label`) || "";
520
+ const helpTextTranslation = helpText || t(`${prefixedNameWithoutIndices}.helpText`);
521
+ return /* @__PURE__ */ React19.createElement(Controller4, {
379
522
  control,
380
- name,
523
+ name: nameWithPrefix,
381
524
  rules,
382
525
  defaultValue,
383
- render: ({ field, fieldState }) => /* @__PURE__ */ React17.createElement(SelectField_default, {
384
- className: classNames12("column", className),
526
+ render: ({ field, fieldState }) => /* @__PURE__ */ React19.createElement(SelectField_default, {
527
+ form: formId,
385
528
  readOnly: readOnly ?? formReadonly,
386
529
  required: rules == null ? void 0 : rules.required,
387
530
  ...props,
388
531
  ...field,
389
532
  label: labelTranslation,
390
533
  helpText: helpTextTranslation,
391
- error: fieldState.error ? fieldState.error.message || t(`${name}.error.${fieldState.error.type}`) : void 0,
392
- testId: testId ?? `select-${name}`
534
+ error: fieldState.error ? fieldState.error.message || t(`${prefixedNameWithoutIndices}.error.${fieldState.error.type}`) : void 0,
535
+ testId: testId ?? `select-${nameWithPrefix}`
393
536
  })
394
537
  });
395
538
  }
396
539
  var ControlledSelectField_default = ControlledSelectField;
397
540
 
398
- // src/Form.tsx
399
- var SuccessNotification = ({ label, hide }) => {
400
- if (!label) {
541
+ // src/ScmFormListContext.tsx
542
+ import React20, { useContext as useContext3, useMemo as useMemo2 } from "react";
543
+ import { useFieldArray } from "react-hook-form";
544
+ var ScmFormListContext = React20.createContext(null);
545
+ var ScmFormListContextProvider = ({ name, children }) => {
546
+ const { control } = useScmFormContext();
547
+ const prefix = useScmFormPathContext();
548
+ const parentForm = useScmFormListContext();
549
+ const nameWithPrefix = useMemo2(() => prefix ? `${prefix}.${name}` : name, [name, prefix]);
550
+ const fieldArray = useFieldArray({
551
+ control,
552
+ name: nameWithPrefix
553
+ });
554
+ const value = useMemo2(() => ({ ...fieldArray, isNested: !!parentForm }), [fieldArray, parentForm]);
555
+ return /* @__PURE__ */ React20.createElement(ScmFormPathContextProvider, {
556
+ path: nameWithPrefix
557
+ }, /* @__PURE__ */ React20.createElement(ScmFormListContext.Provider, {
558
+ value
559
+ }, children));
560
+ };
561
+ function useScmFormListContext() {
562
+ return useContext3(ScmFormListContext);
563
+ }
564
+
565
+ // src/list/ControlledList.tsx
566
+ import React21 from "react";
567
+ import { Button as Button2 } from "@scm-manager/ui-buttons";
568
+ import { useTranslation as useTranslation2 } from "react-i18next";
569
+ function ControlledList({
570
+ withDelete,
571
+ children
572
+ }) {
573
+ const [defaultTranslate] = useTranslation2("commons", { keyPrefix: "form" });
574
+ const { readOnly, t } = useScmFormContext();
575
+ const nameWithPrefix = useScmFormPathContext();
576
+ const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix);
577
+ const { fields, remove } = useScmFormListContext();
578
+ const deleteButtonTranslation = t(`${prefixedNameWithoutIndices}.delete`, {
579
+ defaultValue: defaultTranslate("list.delete.label", {
580
+ entity: t(`${prefixedNameWithoutIndices}.entity`)
581
+ })
582
+ });
583
+ return /* @__PURE__ */ React21.createElement(React21.Fragment, null, fields.map((value, index) => /* @__PURE__ */ React21.createElement(ScmFormPathContextProvider, {
584
+ key: value.id,
585
+ path: `${nameWithPrefix}.${index}`
586
+ }, typeof children === "function" ? children({ value, index, remove: () => remove(index) }) : children, withDelete && !readOnly ? /* @__PURE__ */ React21.createElement("div", {
587
+ className: "level-right"
588
+ }, /* @__PURE__ */ React21.createElement(Button2, {
589
+ variant: "signal",
590
+ onClick: () => remove(index)
591
+ }, deleteButtonTranslation)) : null, /* @__PURE__ */ React21.createElement("hr", null))));
592
+ }
593
+ var ControlledList_default = ControlledList;
594
+
595
+ // src/table/ControlledTable.tsx
596
+ import React22 from "react";
597
+ import { Button as Button3 } from "@scm-manager/ui-buttons";
598
+ import classNames8 from "classnames";
599
+ import { useTranslation as useTranslation3 } from "react-i18next";
600
+ function ControlledTable({
601
+ withDelete,
602
+ children,
603
+ className
604
+ }) {
605
+ const [defaultTranslate] = useTranslation3("commons", { keyPrefix: "form.table" });
606
+ const { readOnly, t } = useScmFormContext();
607
+ const nameWithPrefix = useScmFormPathContext();
608
+ const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix);
609
+ const { fields, remove } = useScmFormListContext();
610
+ const deleteLabel = t(`${prefixedNameWithoutIndices}.delete`) || defaultTranslate("delete.label");
611
+ const actionHeaderLabel = t(`${prefixedNameWithoutIndices}.action.label`) || defaultTranslate("headers.action.label");
612
+ if (!fields.length) {
401
613
  return null;
402
614
  }
403
- return /* @__PURE__ */ React18.createElement("div", {
404
- className: "notification is-success"
405
- }, /* @__PURE__ */ React18.createElement("button", {
406
- className: "delete",
407
- onClick: hide
408
- }), label);
615
+ return /* @__PURE__ */ React22.createElement("table", {
616
+ className: classNames8("table content is-hoverable", className)
617
+ }, /* @__PURE__ */ React22.createElement("thead", null, /* @__PURE__ */ React22.createElement("tr", null, React22.Children.map(children, (child) => /* @__PURE__ */ React22.createElement("th", null, t(`${prefixedNameWithoutIndices}.${child.props.name}.label`))), withDelete && !readOnly ? /* @__PURE__ */ React22.createElement("th", {
618
+ className: "has-text-right"
619
+ }, actionHeaderLabel) : null)), /* @__PURE__ */ React22.createElement("tbody", null, fields.map((value, index) => /* @__PURE__ */ React22.createElement(ScmFormPathContextProvider, {
620
+ key: value.id,
621
+ path: `${nameWithPrefix}.${index}`
622
+ }, /* @__PURE__ */ React22.createElement("tr", null, children, withDelete && !readOnly ? /* @__PURE__ */ React22.createElement("td", {
623
+ className: "has-text-right"
624
+ }, /* @__PURE__ */ React22.createElement(Button3, {
625
+ className: "px-4",
626
+ onClick: () => remove(index),
627
+ "aria-label": deleteLabel
628
+ }, /* @__PURE__ */ React22.createElement("span", {
629
+ className: "icon is-small"
630
+ }, /* @__PURE__ */ React22.createElement("i", {
631
+ className: "fas fa-trash"
632
+ })))) : null)))));
633
+ }
634
+ var ControlledTable_default = ControlledTable;
635
+
636
+ // src/table/ControlledColumn.tsx
637
+ import React23, { useMemo as useMemo3 } from "react";
638
+ import { useWatch } from "react-hook-form";
639
+ var ControlledColumn = ({ name, children, ...props }) => {
640
+ const { control } = useScmFormContext();
641
+ const formPathPrefix = useScmFormPathContext();
642
+ const nameWithPrefix = useMemo3(() => formPathPrefix ? `${formPathPrefix}.${name}` : name, [formPathPrefix, name]);
643
+ const value = useWatch({ control, name: nameWithPrefix, disabled: typeof children === "function" });
644
+ const allValues = useWatch({ control, name: formPathPrefix, disabled: typeof children !== "function" });
645
+ return /* @__PURE__ */ React23.createElement("td", {
646
+ ...props
647
+ }, typeof children === "function" ? children(allValues) : value);
409
648
  };
410
- function Form({
649
+ var ControlledColumn_default = ControlledColumn;
650
+
651
+ // src/AddListEntryForm.tsx
652
+ import React24, { useCallback as useCallback2, useEffect as useEffect2 } from "react";
653
+ import { useForm as useForm2 } from "react-hook-form";
654
+ import { Button as Button4 } from "@scm-manager/ui-buttons";
655
+ import { useTranslation as useTranslation4 } from "react-i18next";
656
+ import styled2 from "styled-components";
657
+ var StyledHr = styled2.hr`
658
+ &:last-child {
659
+ display: none;
660
+ }
661
+ `;
662
+ function AddListEntryForm({
411
663
  children,
412
- onSubmit,
413
664
  defaultValues,
414
- translationPath,
415
- readOnly,
416
- submitButtonTestId
665
+ submit,
666
+ disableSubmitWhenDirty = true
417
667
  }) {
418
- const form = useForm({
668
+ const [defaultTranslate] = useTranslation4("commons", { keyPrefix: "form" });
669
+ const { readOnly, t } = useScmFormContext();
670
+ const nameWithPrefix = useScmFormPathContext();
671
+ const prefixedNameWithoutIndices = prefixWithoutIndices(nameWithPrefix);
672
+ const { append, isNested } = useScmFormListContext();
673
+ const form = useForm2({
419
674
  mode: "onChange",
420
675
  defaultValues
421
676
  });
422
- const { formState, handleSubmit, reset } = form;
423
- const [ns, prefix] = translationPath;
424
- const { t } = useTranslation(ns, { keyPrefix: prefix });
425
- const { isDirty, isValid, isSubmitting, isSubmitSuccessful } = formState;
426
- const [error, setError] = useState();
427
- const [showSuccessNotification, setShowSuccessNotification] = useState(false);
428
- useEffect(() => {
677
+ const {
678
+ reset,
679
+ formState: { isSubmitSuccessful, isDirty, isValid }
680
+ } = form;
681
+ const translateWithExtraPrefix = useCallback2(
682
+ (key, ...args) => t(`${prefixedNameWithoutIndices}.${key}`, ...args),
683
+ [prefixedNameWithoutIndices, t]
684
+ );
685
+ const onSubmit = useCallback2((data) => submit ? submit(data, append) : append(data), [append, submit]);
686
+ const submitButtonLabel = translateWithExtraPrefix("add", {
687
+ defaultValue: defaultTranslate("list.add.label", { entity: translateWithExtraPrefix("entity") })
688
+ });
689
+ useEffect2(() => {
429
690
  if (isSubmitSuccessful) {
430
- setShowSuccessNotification(true);
431
- }
432
- }, [isSubmitSuccessful]);
433
- useEffect(() => reset(defaultValues), [defaultValues, reset]);
434
- useEffect(() => {
435
- if (isDirty) {
436
- setShowSuccessNotification(false);
691
+ reset(defaultValues);
437
692
  }
438
- }, [isDirty]);
439
- const translateWithFallback = useCallback(
440
- (key, ...args) => {
441
- const translation = t(key, ...args);
442
- if (translation === `${prefix}.${key}`) {
443
- return "";
444
- }
445
- return translation;
446
- },
447
- [prefix, t]
448
- );
449
- const submit = useCallback(
450
- async (data) => {
451
- setError(null);
452
- try {
453
- return await onSubmit(data);
454
- } catch (e) {
455
- if (e instanceof Error) {
456
- setError(e);
457
- } else {
458
- throw e;
459
- }
460
- }
461
- },
462
- [onSubmit]
463
- );
464
- return /* @__PURE__ */ React18.createElement(ScmFormContextProvider, {
693
+ }, [defaultValues, isSubmitSuccessful, reset]);
694
+ if (readOnly) {
695
+ return null;
696
+ }
697
+ return /* @__PURE__ */ React24.createElement(ScmFormContextProvider, {
465
698
  ...form,
466
- readOnly: isSubmitting || readOnly,
467
- t: translateWithFallback
468
- }, /* @__PURE__ */ React18.createElement("form", {
469
- onSubmit: handleSubmit(submit)
470
- }, showSuccessNotification ? /* @__PURE__ */ React18.createElement(SuccessNotification, {
471
- label: translateWithFallback("submit-success-notification"),
472
- hide: () => setShowSuccessNotification(false)
473
- }) : null, typeof children === "function" ? children(form) : children, error ? /* @__PURE__ */ React18.createElement(ErrorNotification, {
474
- error
475
- }) : null, !readOnly ? /* @__PURE__ */ React18.createElement(Level, {
476
- right: /* @__PURE__ */ React18.createElement(Button, {
477
- type: "submit",
478
- variant: "primary",
479
- testId: submitButtonTestId ?? "submit-button",
480
- disabled: !isDirty || !isValid,
481
- isLoading: isSubmitting
482
- }, t("submit"))
483
- }) : null));
699
+ t: translateWithExtraPrefix,
700
+ formId: nameWithPrefix
701
+ }, /* @__PURE__ */ React24.createElement(ScmFormPathContextProvider, {
702
+ path: ""
703
+ }, /* @__PURE__ */ React24.createElement("form", {
704
+ id: nameWithPrefix,
705
+ onSubmit: form.handleSubmit(onSubmit)
706
+ }), typeof children === "function" ? children(form) : children, /* @__PURE__ */ React24.createElement("div", {
707
+ className: "level-right"
708
+ }, /* @__PURE__ */ React24.createElement(Button4, {
709
+ form: nameWithPrefix,
710
+ type: "submit",
711
+ variant: isNested ? void 0 : "secondary",
712
+ disabled: disableSubmitWhenDirty && !isDirty || !isValid
713
+ }, submitButtonLabel)), /* @__PURE__ */ React24.createElement(StyledHr, null)));
484
714
  }
485
- var Form_default = Object.assign(Form, {
486
- Row: FormRow_default,
487
- Input: ControlledInputField_default,
488
- Checkbox: ControlledCheckboxField_default,
489
- SecretConfirmation: ControlledSecretConfirmationField,
490
- Select: ControlledSelectField_default
491
- });
715
+ var AddListEntryForm_default = AddListEntryForm;
492
716
 
493
717
  // src/ConfigurationForm.tsx
494
718
  import { useConfigLink } from "@scm-manager/ui-api";
495
719
  import { Loading } from "@scm-manager/ui-components";
496
- import React19 from "react";
720
+ import React25 from "react";
497
721
  function ConfigurationForm({ link, translationPath, children }) {
498
722
  const { initialConfiguration, isReadOnly, update, isLoading } = useConfigLink(link);
499
723
  if (isLoading || !initialConfiguration) {
500
- return /* @__PURE__ */ React19.createElement(Loading, null);
724
+ return /* @__PURE__ */ React25.createElement(Loading, null);
501
725
  }
502
- return /* @__PURE__ */ React19.createElement(Form_default, {
726
+ return /* @__PURE__ */ React25.createElement(Form_default, {
503
727
  onSubmit: update,
504
728
  translationPath,
505
729
  defaultValues: initialConfiguration,
@@ -593,9 +817,25 @@ var useDeleteResource = (idFactory, { collectionName: [entityQueryKey, collectio
593
817
  submissionResult: data
594
818
  };
595
819
  };
820
+
821
+ // src/index.ts
822
+ var Form2 = Object.assign(Form_default, {
823
+ Row: FormRow_default,
824
+ Input: ControlledInputField_default,
825
+ Checkbox: ControlledCheckboxField_default,
826
+ SecretConfirmation: ControlledSecretConfirmationField,
827
+ Select: ControlledSelectField_default,
828
+ PathContext: ScmNestedFormPathContextProvider,
829
+ ListContext: ScmFormListContextProvider,
830
+ List: ControlledList_default,
831
+ AddListEntryForm: AddListEntryForm_default,
832
+ Table: Object.assign(ControlledTable_default, {
833
+ Column: ControlledColumn_default
834
+ })
835
+ });
596
836
  export {
597
837
  ConfigurationForm_default as ConfigurationForm,
598
- Form_default as Form,
838
+ Form2 as Form,
599
839
  useCreateResource,
600
840
  useDeleteResource,
601
841
  useUpdateResource