@parallelworks/ui 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/README.md +86 -6
  2. package/dist/ai/adapter/memoryStore.d.ts +8 -0
  3. package/dist/ai/adapter/mock/index.d.ts +2 -0
  4. package/dist/ai/adapter/mock/index.js +81 -0
  5. package/dist/ai/adapter/mock/mockAdapter.d.ts +4 -0
  6. package/dist/ai/adapter/openai/createOpenAIStream.d.ts +9 -0
  7. package/dist/ai/adapter/openai/errors.d.ts +3 -0
  8. package/dist/ai/adapter/openai/index.d.ts +6 -0
  9. package/dist/ai/adapter/openai/index.js +334 -0
  10. package/dist/ai/adapter/openai/openaiAdapter.d.ts +9 -0
  11. package/dist/ai/adapter/openai/responses.d.ts +4 -0
  12. package/dist/ai/adapter/openai/sse.d.ts +24 -0
  13. package/dist/ai/adapter/openai/wire.d.ts +40 -0
  14. package/dist/ai/adapter/types.d.ts +131 -0
  15. package/dist/ai/agent/classifier.d.ts +2 -0
  16. package/dist/ai/agent/diff.d.ts +35 -0
  17. package/dist/ai/agent/diffHighlight.d.ts +6 -0
  18. package/dist/ai/agent/index.d.ts +6 -0
  19. package/dist/ai/agent/permissions.d.ts +11 -0
  20. package/dist/ai/agent/toolfmt.d.ts +18 -0
  21. package/dist/ai/agent/transcript.d.ts +38 -0
  22. package/dist/ai/ai.css +325 -0
  23. package/dist/ai/components/AllocationSelector.d.ts +4 -0
  24. package/dist/ai/components/AttachmentManager.d.ts +1 -0
  25. package/dist/ai/components/AttachmentUpload.d.ts +24 -0
  26. package/dist/ai/components/BlockedGroupBanner.d.ts +1 -0
  27. package/dist/ai/components/BranchNavigator.d.ts +8 -0
  28. package/dist/ai/components/ChatEmptyState.d.ts +3 -0
  29. package/dist/ai/components/ChatInput.d.ts +45 -0
  30. package/dist/ai/components/ChatLayout.d.ts +5 -0
  31. package/dist/ai/components/ChatMessage.d.ts +24 -0
  32. package/dist/ai/components/ChatMessageList.d.ts +35 -0
  33. package/dist/ai/components/ChatSidebar.d.ts +5 -0
  34. package/dist/ai/components/ChatThread.d.ts +3 -0
  35. package/dist/ai/components/ComposerChrome.d.ts +18 -0
  36. package/dist/ai/components/ComposerFrame.d.ts +16 -0
  37. package/dist/ai/components/DragOverlay.d.ts +5 -0
  38. package/dist/ai/components/ModelSelector.d.ts +6 -0
  39. package/dist/ai/components/ShareDialog.d.ts +11 -0
  40. package/dist/ai/components/SlashMenu.d.ts +28 -0
  41. package/dist/ai/components/agent/ActivityLine.d.ts +21 -0
  42. package/dist/ai/components/agent/AgentMessageParts.d.ts +54 -0
  43. package/dist/ai/components/agent/PermissionPicker.d.ts +11 -0
  44. package/dist/ai/components/attachmentKeys.d.ts +4 -0
  45. package/dist/ai/core/ChatProvider.d.ts +66 -0
  46. package/dist/ai/core/KeyboardShortcutsProvider.d.ts +5 -0
  47. package/dist/ai/core/chatReducer.d.ts +114 -0
  48. package/dist/ai/core/config.d.ts +47 -0
  49. package/dist/ai/core/events.d.ts +1 -0
  50. package/dist/ai/core/exportConversation.d.ts +4 -0
  51. package/dist/ai/core/greeting.d.ts +3 -0
  52. package/dist/ai/core/parts.d.ts +11 -0
  53. package/dist/ai/core/useDragDrop.d.ts +14 -0
  54. package/dist/ai/core/useKeyboardShortcuts.d.ts +12 -0
  55. package/dist/ai/core/useSSEStream.d.ts +34 -0
  56. package/dist/ai/index.d.ts +40 -0
  57. package/dist/ai/index.js +6645 -0
  58. package/dist/ai/strings.d.ts +167 -0
  59. package/dist/ai/types.d.ts +236 -0
  60. package/dist/ai/ui/Avatar.d.ts +8 -0
  61. package/dist/ai/ui/Dropdown.d.ts +31 -0
  62. package/dist/ai/ui/Markdown.d.ts +8 -0
  63. package/dist/ai/ui/Notice.d.ts +21 -0
  64. package/dist/ai/ui/buttons.d.ts +1 -0
  65. package/dist/ai/ui/codeBreaks.d.ts +16 -0
  66. package/dist/ai/ui/prose.d.ts +1 -0
  67. package/dist/ai/ui/sidebar.d.ts +81 -0
  68. package/dist/ai/utils.d.ts +4 -0
  69. package/dist/assets/{editor.worker-CuBRIwi-.js → editor.worker-BmHUIVG4.js} +194 -163
  70. package/dist/assets/{json.worker-8ytS02vz.js → json.worker-D8bJ92nw.js} +384 -338
  71. package/dist/assets/{yaml.worker-DJC-VUcv.js → yaml.worker-C4gyGAko.js} +15657 -13823
  72. package/dist/chunks/{CodePreviewEditor-4lx3kE-w.js → CodePreviewEditor-YqQBz1YL.js} +1 -1
  73. package/dist/chunks/ConfirmModal-aOR4O1QE.js +79 -0
  74. package/dist/chunks/{FieldWrapper-Bef01EWO.js → FieldWrapper-Cszwg-4B.js} +3 -7
  75. package/dist/chunks/{CreateModal-BY6DLmur.js → Markdown-CM-FenNE.js} +19 -91
  76. package/dist/chunks/Monaco-DJ8MW-Dq.js +110 -0
  77. package/dist/chunks/{PageHeader-l49p1nM9.js → PageHeader-0kG_iypP.js} +67 -65
  78. package/dist/chunks/{Provider-Ghua9PkN.js → Provider-CDYurraE.js} +54 -20
  79. package/dist/chunks/{SwitchToggle-CHPP8uZQ.js → SwitchToggle-DShNdcao.js} +135 -132
  80. package/dist/chunks/{Table-4OHjdKdO.js → Table-DU2MW8xz.js} +5 -3
  81. package/dist/chunks/{Tooltip-Crxo_eU_.js → Tooltip-K5HJr4_p.js} +31 -4
  82. package/dist/chunks/duration-BOv8Phl8.js +17 -0
  83. package/dist/chunks/{editor-9_uVtUyy.js → editor-Dk1rbV75.js} +32 -18
  84. package/dist/chunks/focus-D_iZJTW1.js +7 -0
  85. package/dist/chunks/{list-qrojZZNv.js → list-CJm875m7.js} +377 -370
  86. package/dist/chunks/logviewer-BTKuQWzw.js +828 -0
  87. package/dist/chunks/memoryStore-CC91S5LP.js +152 -0
  88. package/dist/chunks/types-Yy0NrerF.js +11 -0
  89. package/dist/components/Avatar.d.ts +1 -1
  90. package/dist/components/Breadcrumbs.d.ts +1 -1
  91. package/dist/components/Dropdown.d.ts +1 -1
  92. package/dist/components/EmptyState.d.ts +1 -1
  93. package/dist/components/Indicator.d.ts +2 -2
  94. package/dist/components/Input.d.ts +1 -1
  95. package/dist/components/LetterBadge.d.ts +1 -1
  96. package/dist/components/MarkdownImpl.d.ts +1 -1
  97. package/dist/components/Provider.d.ts +16 -65
  98. package/dist/components/SettingRow.d.ts +1 -1
  99. package/dist/components/SettingsGroup.d.ts +1 -1
  100. package/dist/components/focus.d.ts +2 -0
  101. package/dist/components/keys.d.ts +12 -0
  102. package/dist/duration.d.ts +3 -0
  103. package/dist/editor/Monaco.d.ts +1 -20
  104. package/dist/editor/index.d.ts +2 -1
  105. package/dist/editor/index.js +2 -2
  106. package/dist/editor/yaml.d.ts +5 -0
  107. package/dist/engine.d.ts +98 -0
  108. package/dist/file-explorer/FileExplorer.d.ts +11 -2
  109. package/dist/file-explorer/FileExplorerContext.d.ts +1 -1
  110. package/dist/file-explorer/FilePreview/CodePreviewEditor.d.ts +1 -1
  111. package/dist/file-explorer/FilePreview/previewType.d.ts +1 -1
  112. package/dist/file-explorer/FileRow.d.ts +1 -1
  113. package/dist/file-explorer/helpers.d.ts +4 -4
  114. package/dist/file-explorer/index.d.ts +3 -3
  115. package/dist/file-explorer/index.js +613 -647
  116. package/dist/file-explorer/lib/types.d.ts +6 -1
  117. package/dist/file-explorer/lib/utils.d.ts +1 -3
  118. package/dist/file-explorer/useNearEnd.d.ts +1 -1
  119. package/dist/form/FieldWrapper.d.ts +2 -2
  120. package/dist/form/Fields/ResourceOptionIcon.d.ts +1 -1
  121. package/dist/form/Form.d.ts +4 -21
  122. package/dist/form/index.d.ts +11 -12
  123. package/dist/form/index.js +1488 -1611
  124. package/dist/form/lib.d.ts +6 -1
  125. package/dist/form/types/fieldComponentTypes.d.ts +3 -2
  126. package/dist/form/types/fieldTypes.d.ts +6 -20
  127. package/dist/form/useParsedOpts.d.ts +1 -1
  128. package/dist/form/utils/{slurmDuration.d.ts → duration.d.ts} +2 -2
  129. package/dist/{workflow → graph}/MatrixGroup.d.ts +2 -2
  130. package/dist/{workflow → graph}/index.d.ts +1 -1
  131. package/dist/{workflow → graph}/index.js +215 -180
  132. package/dist/{workflow → graph}/types.d.ts +3 -3
  133. package/dist/icons.d.ts +20 -21
  134. package/dist/index.d.ts +38 -40
  135. package/dist/index.js +347 -346
  136. package/dist/list/ListTable.d.ts +1 -1
  137. package/dist/list/ListViewControls.d.ts +2 -2
  138. package/dist/list/index.d.ts +9 -9
  139. package/dist/list/index.js +2 -2
  140. package/dist/logviewer/LogViewer.d.ts +1 -1
  141. package/dist/logviewer/index.d.ts +1 -0
  142. package/dist/logviewer/index.js +2 -742
  143. package/dist/logviewer/segments.d.ts +9 -0
  144. package/dist/styles/fonts.css +4 -4
  145. package/dist/styles/theme.css +6 -4
  146. package/dist/styles.css +1 -1
  147. package/dist/theme/index.js +2 -2
  148. package/package.json +38 -14
  149. package/LICENSE +0 -202
  150. package/dist/chunks/Monaco-C1POOdVv.js +0 -1046
  151. package/dist/editor/usesGitlab.d.ts +0 -97
  152. package/dist/form/Fields/BucketReadOnlyField.d.ts +0 -7
  153. package/dist/form/types/workflow-schema.d.ts +0 -1145
  154. package/dist/form/utils/pwResolver.d.ts +0 -18
  155. package/dist/chunks/{MarkdownImpl-Bq4QhrtA.js → MarkdownImpl-CyeTVvSt.js} +1 -1
  156. /package/dist/{workflow → graph}/AnnotationBanner.d.ts +0 -0
  157. /package/dist/{workflow → graph}/Collapse.d.ts +0 -0
  158. /package/dist/{workflow → graph}/DependencyGraph.d.ts +0 -0
  159. /package/dist/{workflow → graph}/JobSummary.d.ts +0 -0
  160. /package/dist/{workflow → graph}/Reveal.d.ts +0 -0
  161. /package/dist/{workflow → graph}/TreeView.d.ts +0 -0
@@ -1,997 +1,844 @@
1
- import { l as useSlots, u as useStrings } from "../chunks/Provider-Ghua9PkN.js";
2
- import { AngleRightIcon, EyeIcon, EyeOffIcon, SuccessCheckmark, TrashIcon } from "../icons.js";
3
- import { n as TOOLTIP_ID, r as TooltipInfo } from "../chunks/Tooltip-Crxo_eU_.js";
1
+ import { d as useWorkflowEngine, l as useSlots, u as useStrings } from "../chunks/Provider-CDYurraE.js";
2
+ import { AngleRightIcon, EyeIcon, EyeOffIcon, TrashIcon } from "../icons.js";
3
+ import { r as TooltipInfo } from "../chunks/Tooltip-K5HJr4_p.js";
4
+ import { a as Dropdown, c as UncontrolledCollapsiblePanel, i as CustomListbox, n as SectionHeader, o as ControlledCollapsiblePanel, r as MultiSelectDropdown, t as SwitchToggle } from "../chunks/SwitchToggle-DShNdcao.js";
4
5
  import { c as isCategory, n as StatusDot } from "../chunks/StatusBadge-C1Blx_cW.js";
5
- import { a as UncontrolledCollapsiblePanel, c as Dropdown, n as SectionHeader, o as MultiSelectDropdown, r as ControlledCollapsiblePanel, s as CustomListbox, t as SwitchToggle } from "../chunks/SwitchToggle-CHPP8uZQ.js";
6
6
  import { t as Loader } from "../chunks/Loader-BXoXTZGc.js";
7
- import { n as Table } from "../chunks/Table-4OHjdKdO.js";
8
- import { a as initializeValues, c as findSecondaryOption, d as useFieldLabelledByProps, f as useFieldRequired, i as impureSetValueFromPath, l as FieldProvider, n as enforceOneMustBeTrue, o as resolvedFlag, r as flattenGroups, s as applySecondaryField, t as FieldWrapper, u as useFieldControlProps } from "../chunks/FieldWrapper-Bef01EWO.js";
7
+ import { n as Table } from "../chunks/Table-DU2MW8xz.js";
8
+ import { a as initializeValues, c as findSecondaryOption, d as useFieldLabelledByProps, f as useFieldRequired, i as impureSetValueFromPath, l as FieldProvider, n as enforceOneMustBeTrue, o as resolvedFlag, r as flattenGroups, s as applySecondaryField, t as FieldWrapper, u as useFieldControlProps } from "../chunks/FieldWrapper-Cszwg-4B.js";
9
9
  import cx from "classnames";
10
- import React, { Suspense, createContext, memo, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
10
+ import React, { Suspense, createContext, memo, useCallback, useContext, useEffect, useEffectEvent, useId, useMemo, useRef, useState } from "react";
11
11
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
12
  import deepEqual from "fast-deep-equal";
13
13
  import { Duration } from "luxon";
14
- import { callWhenParserInitialized, convertToDynamicForm, extractInputDeps, initializeParseStringsOfObj, parseStringsOfObj, parseStringsOfObjReady } from "@parallelworks/workflow-parser";
15
- import { Field, Form, Formik, getIn, useField, useFormikContext } from "formik";
14
+ import { Field, Form, Formik, useField, useFormikContext } from "formik";
16
15
  import { NumericFormat } from "react-number-format";
17
- //#region src/form/utils/getValueUsingPath.ts
18
- function getValueUsingPath(obj, path) {
19
- if (typeof path !== "string" || !path) return;
20
- const keys = path.split(".");
21
- let current = obj;
22
- for (const key of keys) {
23
- if (!current || typeof current !== "object") return;
24
- const container = current;
25
- if (key.includes("[")) {
26
- const [arrayKey = "", indexStr = ""] = key.split("[");
27
- const index = parseInt(indexStr.slice(0, -1), 10);
28
- const list = container[arrayKey];
29
- if (!Array.isArray(list) || isNaN(index)) return;
30
- current = list[index];
31
- } else {
32
- if (!(key in container)) return;
33
- current = container[key];
34
- }
35
- }
36
- return current;
16
+ //#region src/form/Fields/durationValidity.ts
17
+ var invalidPaths = /* @__PURE__ */ new Set();
18
+ function setDurationValidity(path, valid) {
19
+ if (valid) invalidPaths.delete(path);
20
+ else invalidPaths.add(path);
37
21
  }
38
- //#endregion
39
- //#region src/form/utils/checkShowIfObject.ts
40
- function checkShowIfObject(showIfObject, formValues, index) {
41
- const valueList = [];
42
- for (let [key, value] of Object.entries(showIfObject)) {
43
- if (index !== void 0) key = key.replace("[index]", `[${index}]`);
44
- const dependsOnValue = getValueUsingPath(formValues, key);
45
- if (typeof value === "boolean" || typeof value === "string" || typeof value === "number") valueList.push(dependsOnValue !== null && dependsOnValue !== void 0 && dependsOnValue.toString() === value.toString());
46
- else if (Array.isArray(value)) valueList.push(value.includes(dependsOnValue));
47
- }
48
- return valueList;
22
+ function getInvalidDurationPaths() {
23
+ return [...invalidPaths];
49
24
  }
50
25
  //#endregion
51
- //#region src/form/utils/getParentValue.ts
52
- function getParentValue(index, targetField, values, dependentPath, field) {
53
- let parentValue;
54
- if (typeof targetField === "object" && targetField !== null && !Array.isArray(targetField)) parentValue = !checkShowIfObject(targetField, values, index).includes(false);
55
- else if (field.depends_on) {
56
- dependentPath = field.depends_on;
57
- if (index !== void 0) dependentPath = field.depends_on.replace("[index]", `[${index}]`);
58
- const dependsOnValue = getValueUsingPath(values, dependentPath);
59
- const dependsOnText = dependsOnValue === null || dependsOnValue === void 0 ? void 0 : String(dependsOnValue);
60
- if (Array.isArray(targetField)) parentValue = targetField.includes(dependsOnValue) || targetField.includes(dependsOnText);
61
- else if (typeof targetField === "string") parentValue = dependsOnText !== void 0 && targetField.includes(dependsOnText);
62
- else parentValue = targetField === dependsOnValue;
63
- } else {
64
- dependentPath = typeof targetField === "string" ? targetField : "";
65
- if (index !== void 0 && typeof targetField === "string") dependentPath = targetField.replace("[index]", `[${index}]`);
66
- parentValue = getValueUsingPath(values, dependentPath);
67
- }
68
- return parentValue;
26
+ //#region src/form/SpecialTypes/FormikCustomDropdown.tsx
27
+ function toComparableOptions(options) {
28
+ return options?.map((option) => {
29
+ if (typeof option === "string") return option;
30
+ if (isCategory(option)) return {
31
+ category: option.category,
32
+ options: option.options.map((o) => {
33
+ const base = {
34
+ label: o.label,
35
+ value: o.value,
36
+ disabled: o.disabled
37
+ };
38
+ return o.description !== void 0 ? {
39
+ ...base,
40
+ description: o.description
41
+ } : base;
42
+ })
43
+ };
44
+ const base = {
45
+ label: option.label,
46
+ value: option.value,
47
+ disabled: option.disabled
48
+ };
49
+ return option.description !== void 0 ? {
50
+ ...base,
51
+ description: option.description
52
+ } : base;
53
+ });
69
54
  }
70
- //#endregion
71
- //#region src/form/useParsedOpts.ts
72
- var EMPTY_VARS = {};
73
- var EMPTY_KEYS = [];
74
- /**
75
- * useMemo won't work here — Formik values changes on every keystroke, so this hook extracts which inputs.* keys the schema references and re-parses only when those change. Defaults `userVars`/`secretKeys` to empty — callers in React scope should pass them from `useUserVars()` so the parser leaves `${{ var.SECRET }}` literal during template eval (otherwise the recursive substitution turns `"https://${{ var.PAT }}@host/"` into `"https://@host/"`).
76
- */
77
- function useParsedOpts(rawOptions, values, organizationVariables, userVars = EMPTY_VARS, secretKeys = EMPTY_KEYS, remoteVars = EMPTY_VARS) {
78
- const c = useRef({
79
- rawOptions: null,
80
- orgVars: void 0,
81
- userVars: null,
82
- secretKeys: null,
83
- remoteVars: null,
84
- converted: {},
85
- inputDeps: /* @__PURE__ */ new Set(),
86
- hasExpressions: false,
87
- relevantSnapshot: {},
88
- result: null
89
- }).current;
90
- if (rawOptions !== c.rawOptions) {
91
- c.rawOptions = rawOptions;
92
- c.converted = convertToDynamicForm(rawOptions);
93
- const { inputDeps, hasExpressions } = extractInputDeps(c.converted);
94
- c.inputDeps = inputDeps;
95
- c.hasExpressions = hasExpressions;
96
- c.result = null;
97
- }
98
- if (organizationVariables !== c.orgVars || userVars !== c.userVars || secretKeys !== c.secretKeys || remoteVars !== c.remoteVars) {
99
- c.orgVars = organizationVariables;
100
- c.userVars = userVars;
101
- c.secretKeys = secretKeys;
102
- c.remoteVars = remoteVars;
103
- c.result = null;
104
- }
105
- if (c.result !== null) {
106
- if (c.inputDeps.size === 0) return c.result;
107
- let inputsChanged = false;
108
- for (const key of c.inputDeps) if (!deepEqual(values[key], c.relevantSnapshot[key])) {
109
- inputsChanged = true;
110
- break;
55
+ function arePropsEqual(prev, next) {
56
+ for (const key of Object.keys(next)) if (key === "options") {
57
+ if (!deepEqual(toComparableOptions(prev.options), toComparableOptions(next.options))) return false;
58
+ } else if (prev[key] !== next[key]) return false;
59
+ return true;
60
+ }
61
+ var FormikCustomDropdown_default = memo(function FormikCustomDropdown({ name, ariaLabel, options, setFormDirty, onChange, parentValue, className, secondaryField, disabled = false, loading = false, invalid = false, autoselect, value, allowCustomValue = false, customValueLabel, placeholder, currentValue, setFieldValue, setFieldTouched }) {
62
+ const required = useFieldRequired();
63
+ const controlProps = useFieldControlProps();
64
+ const prevParentRef = useRef(parentValue);
65
+ const handleChange = (val) => {
66
+ if (secondaryField && typeof options[0] !== "string") {
67
+ const option = findSecondaryOption(options, val);
68
+ if (option) applySecondaryField(secondaryField, option.secondaryValue, setFieldValue);
69
+ }
70
+ if (onChange) onChange(val);
71
+ setFieldValue(name, val);
72
+ setFieldTouched(name, true);
73
+ setFormDirty(true);
74
+ };
75
+ useEffect(() => {
76
+ prevParentRef.current = parentValue;
77
+ });
78
+ const prevOptions = useRef(null);
79
+ const syncAutoselect = useEffectEvent((options, autoselect) => {
80
+ if (deepEqual(prevOptions.current, toComparableOptions(options))) return;
81
+ if (prevOptions.current === null && currentValue !== void 0 && currentValue !== null) {
82
+ prevOptions.current = toComparableOptions(options);
83
+ return;
84
+ }
85
+ prevOptions.current = toComparableOptions(options);
86
+ if (autoselect === false || !options?.length) return;
87
+ if (autoselect === void 0) {
88
+ if (options.length && isCategory(options[0])) {
89
+ let optionCount = 0;
90
+ for (const category of options) {
91
+ if (isCategory(category)) optionCount += category.options.length;
92
+ if (optionCount > 1) return;
93
+ }
94
+ } else if (options.length !== 1) return;
95
+ }
96
+ if (!options?.find((option) => {
97
+ if (typeof option === "string") return option === currentValue;
98
+ else if (isCategory(option)) return option.options.some((subOption) => {
99
+ return subOption.value === currentValue;
100
+ });
101
+ return option.value === currentValue;
102
+ }) || autoselect) {
103
+ let val;
104
+ for (const option of options) if (typeof option === "string") {
105
+ val = option;
106
+ break;
107
+ } else if (isCategory(option)) {
108
+ const enabled = option.options.find((o) => !o.disabled);
109
+ if (enabled) {
110
+ val = enabled.value;
111
+ break;
112
+ }
113
+ } else if (!option.disabled) {
114
+ val = option.value;
115
+ break;
116
+ }
117
+ if (val !== void 0) handleChange(val);
111
118
  }
112
- if (!inputsChanged) return c.result;
113
- }
114
- const parsed = parseStringsOfObj({
115
- inputs: values,
116
- obj: c.converted,
117
- orgVars: organizationVariables,
118
- userVars,
119
- secretKeys,
120
- remoteVars
121
119
  });
122
- c.result = parsed;
123
- c.relevantSnapshot = {};
124
- for (const key of c.inputDeps) c.relevantSnapshot[key] = values[key];
125
- return parsed;
126
- }
127
- //#endregion
128
- //#region src/form/usePrevious.ts
129
- function usePrevious(value) {
130
- const ref = useRef(void 0);
131
120
  useEffect(() => {
132
- ref.current = value;
121
+ syncAutoselect(options, autoselect);
122
+ }, [options, autoselect]);
123
+ return /* @__PURE__ */ jsx(Dropdown, {
124
+ ...controlProps,
125
+ textBoxClassName: cx(className, invalid && "invalid"),
126
+ ariaLabel,
127
+ required,
128
+ onChange: (val) => handleChange(val),
129
+ value: options?.length === 0 && !allowCustomValue ? value : currentValue,
130
+ options,
131
+ placeholder,
132
+ disabled,
133
+ loading,
134
+ allowCustomValue,
135
+ customValueLabel
133
136
  });
134
- return ref.current;
135
- }
137
+ }, arePropsEqual);
136
138
  //#endregion
137
- //#region src/form/Wizard/WizardNavigation.tsx
138
- function WizardNavigation({ currentStep, stepOrder, canGoToNext, canGoBack, isCurrentStepValid, nextLabel, prevLabel, submitLabel = "Submit", onNext, onPrevious, onSubmit }) {
139
- const [isLoading, setIsLoading] = useState(false);
140
- const [error, setError] = useState(null);
141
- const isLastStep = stepOrder.indexOf(currentStep) === stepOrder.length - 1;
142
- const handleNext = async () => {
143
- setIsLoading(true);
144
- setError(null);
139
+ //#region src/form/SpecialTypes/FormikCustomInput.tsx
140
+ var DEBOUNCE_MS$2 = 500;
141
+ function FormikCustomInput({ name, type, step, placeholder, onChange, disabled, min, max, sanitize, invalid = false, lowercase = false, minLength, maxLength, defaultValue = "", value }) {
142
+ const [field] = useField(name);
143
+ const required = useFieldRequired();
144
+ const controlProps = useFieldControlProps();
145
+ const { setFieldValue, setFieldTouched } = useFormikContext();
146
+ const [showPassword, setShowPassword] = useState(false);
147
+ const isPassword = type === "password";
148
+ const [localValue, setLocalValue] = useState(value ?? field.value);
149
+ const isFocused = useRef(false);
150
+ const debounceRef = useRef(null);
151
+ useEffect(() => {
152
+ if (!isFocused.current) setLocalValue(value ?? field.value);
153
+ }, [field.value, value]);
154
+ const sanitizeName = useCallback((val) => {
155
+ if (sanitize === void 0) return lowercase ? val.toLowerCase() : val;
145
156
  try {
146
- if (!await onNext()) setError("Please fix errors before proceeding");
147
- } catch (err) {
148
- setError(err instanceof Error ? err.message : "An error occurred");
149
- } finally {
150
- setIsLoading(false);
157
+ const regexp = new RegExp(sanitize);
158
+ if (lowercase) val = val.toLowerCase();
159
+ return val.replace(regexp, "");
160
+ } catch {
161
+ console.error("Invalid regex for sanitization:", sanitize);
151
162
  }
152
- };
153
- const handleSubmit = async () => {
154
- setIsLoading(true);
155
- setError(null);
156
- try {
157
- await onSubmit();
158
- } catch (err) {
159
- setError(err instanceof Error ? err.message : "An error occurred");
160
- } finally {
161
- setIsLoading(false);
163
+ return val;
164
+ }, [sanitize, lowercase]);
165
+ const flushToFormik = useCallback((val) => {
166
+ if (onChange) onChange(val);
167
+ setFieldTouched(name, true);
168
+ setFieldValue(name, val);
169
+ }, [
170
+ name,
171
+ onChange,
172
+ setFieldTouched,
173
+ setFieldValue
174
+ ]);
175
+ const handleChange = (e) => {
176
+ const text = sanitize ? sanitizeName(e.target.value) : e.target.value;
177
+ let val = text;
178
+ if (type === "number") {
179
+ const parsed = parseFloat(text);
180
+ val = isNaN(parsed) ? void 0 : parsed;
162
181
  }
182
+ if (type === "password" && val === "") val = defaultValue;
183
+ setLocalValue(val);
184
+ clearTimeout(debounceRef.current ?? void 0);
185
+ debounceRef.current = setTimeout(() => flushToFormik(val), DEBOUNCE_MS$2);
163
186
  };
164
- return /* @__PURE__ */ jsxs("div", { children: [error && /* @__PURE__ */ jsx("div", {
165
- className: "mb-4 p-3 bg-red-50 border border-red-200 rounded text-sm text-red-700",
166
- children: error
167
- }), /* @__PURE__ */ jsxs("div", {
168
- className: "flex gap-3 justify-between pt-4",
169
- children: [/* @__PURE__ */ jsx("button", {
170
- type: "button",
171
- onClick: onPrevious,
172
- disabled: !canGoBack || isLoading,
173
- className: "px-6 py-3 rounded-lg font-medium transition-all duration-200 border flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
174
- style: {
175
- color: "var(--theme-app)",
176
- borderColor: "var(--theme-border)",
177
- backgroundColor: "var(--theme-app-bg)"
178
- },
179
- onMouseEnter: (e) => {
180
- if (!(!canGoBack || isLoading)) e.currentTarget.style.backgroundColor = "var(--theme-hover)";
181
- },
182
- onMouseLeave: (e) => {
183
- e.currentTarget.style.backgroundColor = "var(--theme-app-bg)";
184
- },
185
- "aria-label": "Go to previous step",
186
- children: prevLabel || "← Previous"
187
- }), isLastStep ? /* @__PURE__ */ jsxs("button", {
188
- type: "button",
189
- onClick: handleSubmit,
190
- disabled: !isCurrentStepValid || isLoading,
191
- className: "px-8 py-3 rounded-lg font-medium transition-all duration-200 text-white flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-md hover:shadow-lg",
192
- style: { backgroundColor: "var(--theme-element)" },
193
- onMouseEnter: (e) => {
194
- if (!(!isCurrentStepValid || isLoading)) e.currentTarget.style.filter = "brightness(0.9)";
195
- },
196
- onMouseLeave: (e) => {
197
- e.currentTarget.style.filter = "brightness(1)";
198
- },
199
- "aria-label": submitLabel,
200
- children: [isLoading && /* @__PURE__ */ jsx(Loader, {
201
- size: 16,
202
- full: false
203
- }), submitLabel]
204
- }) : /* @__PURE__ */ jsxs("button", {
187
+ const handleFocus = () => {
188
+ isFocused.current = true;
189
+ };
190
+ const handleBlur = () => {
191
+ isFocused.current = false;
192
+ clearTimeout(debounceRef.current ?? void 0);
193
+ const current = localValue;
194
+ if (current === void 0 || current === null) return;
195
+ const text = current.toString();
196
+ let val = text;
197
+ if (type === "number") {
198
+ const parsed = parseFloat(text);
199
+ if (Number.isNaN(parsed)) val = void 0;
200
+ else if (min !== void 0 && parsed < min) val = min;
201
+ else if (max !== void 0 && parsed > max) val = max;
202
+ else val = parsed;
203
+ }
204
+ setLocalValue(val);
205
+ flushToFormik(val);
206
+ };
207
+ const displayValue = isPassword && localValue === defaultValue ? "" : localValue ?? "";
208
+ const resolvedType = isPassword ? showPassword ? "text" : "password" : type === "string" ? "text" : type;
209
+ const input = /* @__PURE__ */ jsx("input", {
210
+ ...controlProps,
211
+ "data-1p-ignore": true,
212
+ className: cx("w-full", disabled && "disabled", invalid && "invalid", isPassword && "pr-8"),
213
+ name,
214
+ value: displayValue,
215
+ type: resolvedType,
216
+ "aria-required": required || void 0,
217
+ disabled,
218
+ placeholder,
219
+ minLength,
220
+ maxLength,
221
+ onChange: handleChange,
222
+ onFocus: handleFocus,
223
+ onBlur: handleBlur,
224
+ min,
225
+ max,
226
+ step
227
+ });
228
+ if (!isPassword) return input;
229
+ return /* @__PURE__ */ jsxs("div", {
230
+ className: "relative w-full",
231
+ children: [input, /* @__PURE__ */ jsx("button", {
205
232
  type: "button",
206
- onClick: handleNext,
207
- disabled: !canGoToNext || isLoading,
208
- className: "px-8 py-3 rounded-lg font-medium transition-all duration-200 text-white flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-md hover:shadow-lg",
209
- style: { backgroundColor: "var(--theme-element)" },
210
- onMouseEnter: (e) => {
211
- if (!(!canGoToNext || isLoading)) e.currentTarget.style.filter = "brightness(0.9)";
212
- },
213
- onMouseLeave: (e) => {
214
- e.currentTarget.style.filter = "brightness(1)";
215
- },
216
- "aria-label": "Go to next step",
217
- children: [isLoading && /* @__PURE__ */ jsx(Loader, {
218
- size: 16,
219
- full: false
220
- }), nextLabel || "Next →"]
233
+ className: "absolute right-2 top-1/2 -translate-y-1/2 theme-muted-text",
234
+ onClick: () => setShowPassword((prev) => !prev),
235
+ disabled,
236
+ tabIndex: -1,
237
+ "aria-label": showPassword ? "Hide value" : "Show value",
238
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(EyeIcon, { className: "h-4 w-4" })
221
239
  })]
222
- })] });
223
- }
224
- //#endregion
225
- //#region src/form/Wizard/WizardStepContent.tsx
226
- function WizardStepContent({ currentStep, stepConfig, flatten = true, values, onValuesChange, setFormDirty, setFieldValue, setFieldTouched, ...fieldsProps }) {
227
- if (!stepConfig?.options) return null;
228
- return /* @__PURE__ */ jsxs("div", {
229
- className: "mb-4 w-full",
230
- children: [
231
- stepConfig.title && /* @__PURE__ */ jsx("h2", {
232
- className: "text-2xl font-bold mb-2",
233
- style: { color: "var(--theme-app)" },
234
- children: stepConfig.title
235
- }),
236
- stepConfig.description && /* @__PURE__ */ jsx("p", {
237
- className: "mb-4 leading-relaxed",
238
- style: { color: "var(--theme-muted-text-color)" },
239
- children: stepConfig.description
240
- }),
241
- /* @__PURE__ */ jsx(FieldsFromOptions, {
242
- options: stepConfig.options,
243
- values,
244
- setFormDirty,
245
- setFieldValue,
246
- setFieldTouched,
247
- ...flatten === false ? { parentInfo: {
248
- parentName: currentStep,
249
- fieldNamePrefix: `${currentStep}.`
250
- } } : {},
251
- ...fieldsProps
252
- }, currentStep)
253
- ]
254
240
  });
255
241
  }
256
242
  //#endregion
257
- //#region src/form/Wizard/WizardStepIndicator.tsx
258
- function WizardStepIndicator({ stepOrder, currentStep, steps, visitedSteps, invalidSteps, onStepClick, allowJump = false, hideStepNumbers = false }) {
259
- const currentIndex = useMemo(() => stepOrder.indexOf(currentStep), [stepOrder, currentStep]);
260
- const getStepStatus = (stepKey, index) => {
261
- if (stepKey === currentStep) return "active";
262
- if (index < currentIndex) return "completed";
263
- return "upcoming";
264
- };
265
- const handleStepClick = (stepKey) => {
266
- if (!allowJump || !visitedSteps.has(stepKey)) return;
267
- onStepClick?.(stepKey);
243
+ //#region src/form/SpecialTypes/FormikNumericInput.tsx
244
+ var DEBOUNCE_MS$1 = 500;
245
+ function FormikNumericInput({ name, placeholder, onChange, disabled, min, max, invalid = false }) {
246
+ const [field] = useField(name);
247
+ const required = useFieldRequired();
248
+ const controlProps = useFieldControlProps();
249
+ const { setFieldValue, setFieldTouched } = useFormikContext();
250
+ const [localValue, setLocalValue] = useState(field.value);
251
+ const isFocused = useRef(false);
252
+ const debounceRef = useRef(null);
253
+ useEffect(() => {
254
+ if (!isFocused.current) setLocalValue(field.value);
255
+ }, [field.value]);
256
+ const flush = (val) => {
257
+ onChange?.(val);
258
+ setFieldTouched(name, true);
259
+ setFieldValue(name, val);
268
260
  };
269
- const isClickable = (stepKey) => {
270
- return allowJump && visitedSteps.has(stepKey) && stepKey !== currentStep;
261
+ const handleBlur = () => {
262
+ isFocused.current = false;
263
+ clearTimeout(debounceRef.current ?? void 0);
264
+ let val = localValue;
265
+ if (val !== void 0) {
266
+ if (min !== void 0 && val < min) val = min;
267
+ if (max !== void 0 && val > max) val = max;
268
+ }
269
+ setLocalValue(val);
270
+ flush(val);
271
271
  };
272
- return /* @__PURE__ */ jsx("div", {
273
- className: "flex items-start justify-between w-full mb-2 gap-4 py-2",
274
- children: stepOrder.map((stepKey, index) => {
275
- const stepConfig = steps[stepKey];
276
- const status = getStepStatus(stepKey, index);
277
- const isInvalid = invalidSteps.has(stepKey);
278
- const isClickable_ = isClickable(stepKey);
279
- const isDotActive = status === "active";
280
- const isDotCompleted = status === "completed";
281
- const isDotUpcoming = status === "upcoming";
282
- return /* @__PURE__ */ jsxs("div", {
283
- className: "flex flex-col items-center flex-1 relative",
284
- children: [
285
- /* @__PURE__ */ jsxs("button", {
286
- type: "button",
287
- onClick: () => handleStepClick(stepKey),
288
- disabled: !isClickable_,
289
- className: cx("w-12 h-12 rounded-full border-3 flex items-center justify-center font-bold text-base transition-all duration-300 flex-shrink-0 relative", {
290
- "cursor-pointer": isClickable_,
291
- "cursor-not-allowed": !isClickable_
292
- }, {
293
- "text-white": isDotActive,
294
- "bg-green-500 border-green-600 text-white shadow-md": isDotCompleted && !isInvalid,
295
- "bg-(--theme-border) theme-border theme-muted-text shadow-sm": isDotUpcoming && !isInvalid,
296
- "bg-(--theme-muted-panel-bg) theme-border theme-muted-text shadow-sm": !isDotActive && !isDotCompleted && !isDotUpcoming && !isInvalid,
297
- "bg-red-50 border-red-500 text-red-600 shadow-sm": isInvalid && status !== "active"
298
- }),
299
- style: isDotActive ? {
300
- backgroundColor: "var(--theme-element)",
301
- borderColor: "var(--theme-element)",
302
- color: "var(--theme-element-text)",
303
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
304
- zIndex: 10
305
- } : { zIndex: 10 },
306
- "aria-label": `Step ${index + 1}: ${stepConfig?.title}`,
307
- title: stepConfig?.title,
308
- children: [!hideStepNumbers && /* @__PURE__ */ jsx("span", {
309
- className: cx({ hidden: status === "completed" }),
310
- children: index + 1
311
- }), status === "completed" && /* @__PURE__ */ jsx("svg", {
312
- "aria-hidden": "true",
313
- className: "w-4 h-4",
314
- fill: "none",
315
- stroke: "currentColor",
316
- viewBox: "0 0 24 24",
317
- children: /* @__PURE__ */ jsx("path", {
318
- strokeLinecap: "round",
319
- strokeLinejoin: "round",
320
- strokeWidth: 3,
321
- d: "M5 13l4 4L19 7"
322
- })
323
- })]
324
- }),
325
- index < stepOrder.length - 1 && /* @__PURE__ */ jsx("div", {
326
- className: cx("absolute h-1 transition-all duration-300", {
327
- "bg-green-500": index < currentIndex,
328
- "bg-(--theme-border)": index >= currentIndex,
329
- "opacity-25": index >= currentIndex
330
- }),
331
- style: {
332
- top: "24px",
333
- left: "calc(50% + 24px)",
334
- right: "-50%",
335
- boxShadow: index < currentIndex ? "0 1px 2px rgba(34, 197, 94, 0.2)" : "none"
336
- },
337
- "aria-hidden": "true"
338
- }),
339
- /* @__PURE__ */ jsxs("div", {
340
- className: "mt-2 text-center",
341
- children: [/* @__PURE__ */ jsx("div", {
342
- className: cx("text-sm text-center transition-colors duration-300 max-w-30", {
343
- "font-bold text-green-600": isDotCompleted && !isInvalid,
344
- "font-bold text-red-600": isInvalid && status !== "active",
345
- "font-semibold theme-muted-text": !isDotActive && !isDotCompleted && !isInvalid,
346
- "font-bold text-white": isDotActive
347
- }),
348
- style: isDotActive ? { color: "var(--theme-element)" } : {},
349
- children: stepConfig?.title
350
- }), stepConfig?.description && /* @__PURE__ */ jsx("div", {
351
- className: "text-xs theme-muted-text mt-1 text-center max-w-30",
352
- children: stepConfig.description
353
- })]
354
- })
355
- ]
356
- }, stepKey);
357
- })
272
+ return /* @__PURE__ */ jsx(NumericFormat, {
273
+ ...controlProps,
274
+ "data-1p-ignore": true,
275
+ className: cx("w-full", disabled && "disabled", invalid && "invalid"),
276
+ name,
277
+ ...localValue === void 0 ? {} : { value: localValue },
278
+ "aria-required": required || void 0,
279
+ thousandSeparator: ",",
280
+ decimalScale: 20,
281
+ allowNegative: true,
282
+ disabled,
283
+ placeholder,
284
+ onValueChange: (v) => {
285
+ setLocalValue(v.floatValue);
286
+ clearTimeout(debounceRef.current ?? void 0);
287
+ debounceRef.current = setTimeout(() => flush(v.floatValue), DEBOUNCE_MS$1);
288
+ },
289
+ onFocus: () => {
290
+ isFocused.current = true;
291
+ },
292
+ onBlur: handleBlur
358
293
  });
359
294
  }
360
295
  //#endregion
361
- //#region src/form/Wizard/useWizardState.ts
362
- function useWizardState({ stepOrder, steps, validateStep, initialStep }) {
363
- const firstStep = initialStep || stepOrder[0] || "";
364
- const [currentStep, setCurrentStep] = useState(firstStep);
365
- const [visitedSteps, setVisitedSteps] = useState(new Set([firstStep]));
366
- const [invalidSteps, setInvalidSteps] = useState(/* @__PURE__ */ new Set());
367
- const currentStepIndex = stepOrder.indexOf(currentStep);
368
- const isLastStep = currentStepIndex === stepOrder.length - 1;
369
- const canGoBack = currentStepIndex > 0;
370
- /**
371
- * Mark step as visited
372
- */
373
- const markStepVisited = useCallback((stepKey) => {
374
- setVisitedSteps((prev) => new Set([...prev, stepKey]));
375
- }, []);
376
- /**
377
- * Mark step as invalid
378
- */
379
- const markStepInvalid = useCallback((stepKey, invalid) => {
380
- setInvalidSteps((prev) => {
381
- const next = new Set(prev);
382
- if (invalid) next.add(stepKey);
383
- else next.delete(stepKey);
384
- return next;
385
- });
386
- }, []);
387
- const goToNext = useCallback(async () => {
388
- if (isLastStep) return false;
389
- if (steps[currentStep]?.validateOnNext !== false) {
390
- if (!await validateStep(currentStep)) {
391
- markStepInvalid(currentStep, true);
392
- return false;
393
- }
394
- }
395
- markStepInvalid(currentStep, false);
396
- const nextStepKey = stepOrder[currentStepIndex + 1];
397
- setCurrentStep(nextStepKey ?? "");
398
- markStepVisited(nextStepKey ?? "");
399
- return true;
400
- }, [
401
- currentStep,
402
- currentStepIndex,
403
- isLastStep,
404
- steps,
405
- stepOrder,
406
- validateStep,
407
- markStepInvalid,
408
- markStepVisited
409
- ]);
410
- const goToPrevious = useCallback(() => {
411
- if (!canGoBack) return;
412
- const prevStepKey = stepOrder[currentStepIndex - 1];
413
- setCurrentStep(prevStepKey ?? "");
414
- }, [
415
- canGoBack,
416
- currentStepIndex,
417
- stepOrder
418
- ]);
419
- const jumpToStep = useCallback((stepKey) => {
420
- if (!stepOrder.includes(stepKey)) return false;
421
- if (!visitedSteps.has(stepKey) && stepKey !== currentStep) return false;
422
- setCurrentStep(stepKey);
423
- return true;
424
- }, [
425
- stepOrder,
426
- visitedSteps,
427
- currentStep
428
- ]);
429
- /**
430
- * Reset wizard to initial state
431
- */
432
- const reset = useCallback(() => {
433
- setCurrentStep(firstStep);
434
- setVisitedSteps(new Set([firstStep]));
435
- setInvalidSteps(/* @__PURE__ */ new Set());
436
- }, [firstStep]);
437
- /**
438
- * Get current wizard state
439
- */
440
- const getState = useCallback(() => {
441
- return {
442
- currentStep,
443
- visitedSteps,
444
- invalidSteps,
445
- stepOrder,
446
- totalSteps: stepOrder.length,
447
- isLastStep,
448
- canProceedToNext: !isLastStep,
449
- canGoBack
450
- };
296
+ //#region src/form/utils/getPlaceholder.ts
297
+ /** `placeholder=""` (empty string) intentionally overrides the default; undefined does not. */
298
+ function getPlaceholder(placeholder, defaultValue) {
299
+ if (placeholder !== void 0) return placeholder;
300
+ if (defaultValue && (typeof defaultValue === "string" || typeof defaultValue === "number")) return defaultValue.toString();
301
+ return "";
302
+ }
303
+ //#endregion
304
+ //#region src/form/Fields/NumberField.tsx
305
+ function NumberField(props) {
306
+ const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact } = props;
307
+ const [fieldState] = useField(field.name);
308
+ const { setFieldValue } = useFormikContext();
309
+ useEffect(() => {
310
+ if (field.prefillDefault && field.default !== void 0 && (fieldState.value === void 0 || fieldState.value === null || fieldState.value === "")) setFieldValue(field.name, field.default);
451
311
  }, [
452
- currentStep,
453
- visitedSteps,
454
- invalidSteps,
455
- stepOrder,
456
- isLastStep,
457
- canGoBack
312
+ field.prefillDefault,
313
+ field.default,
314
+ field.name,
315
+ fieldState.value,
316
+ setFieldValue
458
317
  ]);
459
- return {
460
- currentStep,
461
- visitedSteps,
462
- invalidSteps,
463
- isLastStep,
464
- canGoBack,
465
- currentStepIndex,
466
- totalSteps: stepOrder.length,
467
- goToNext,
468
- goToPrevious,
469
- jumpToStep,
470
- reset,
471
- markStepVisited,
472
- markStepInvalid,
473
- getState
474
- };
318
+ return /* @__PURE__ */ jsx(FieldWrapper, {
319
+ optional: field.optional,
320
+ label,
321
+ tooltipComponent,
322
+ labelPosition,
323
+ spaceCompact,
324
+ children: field.thousandsSeparator ? /* @__PURE__ */ jsx(FormikNumericInput, {
325
+ invalid: missing,
326
+ name: field.name,
327
+ placeholder: getPlaceholder(field.placeholder, field.default),
328
+ disabled,
329
+ min: field.min,
330
+ max: field.max,
331
+ ...onChange && { onChange }
332
+ }, field.name) : /* @__PURE__ */ jsx(FormikCustomInput, {
333
+ invalid: missing,
334
+ name: field.name,
335
+ type: "number",
336
+ placeholder: getPlaceholder(field.placeholder, field.default),
337
+ disabled,
338
+ step: field.step,
339
+ min: field.min,
340
+ max: field.max,
341
+ defaultValue: field.default,
342
+ ...onChange && { onChange }
343
+ }, field.name)
344
+ });
475
345
  }
476
346
  //#endregion
477
- //#region src/form/Wizard/WizardContainer.tsx
478
- function hasNestedError(stepErrors, fieldName) {
479
- return typeof stepErrors === "object" && stepErrors !== null && fieldName in stepErrors;
347
+ //#region src/form/Fields/ResourceOptionIcon.tsx
348
+ function ResourceOptionIcon({ imageUrl, fallbackIcon, alt, variant = "success", dotSize = 6 }) {
349
+ return /* @__PURE__ */ jsxs("span", {
350
+ className: "flex items-center gap-1.5 shrink-0",
351
+ children: [/* @__PURE__ */ jsx(StatusDot, {
352
+ variant,
353
+ size: dotSize
354
+ }), /* @__PURE__ */ jsx("img", {
355
+ src: imageUrl || fallbackIcon,
356
+ alt,
357
+ className: "h-4 w-4 object-contain",
358
+ onError: (e) => {
359
+ e.currentTarget.src = fallbackIcon;
360
+ }
361
+ })]
362
+ });
480
363
  }
481
- function WizardContainer({ wizardConfig, values, onChange, onSubmit, className = "", labelPosition = "left", missingFields = [], spaceCompact = false, workflowForm = false, setFieldValue, setFieldTouched }) {
482
- const { validateForm, setTouched, touched } = useFormikContext();
483
- const { config, steps, stepOrder } = wizardConfig;
484
- const { currentStep, visitedSteps, invalidSteps, isLastStep, canGoBack, goToNext, goToPrevious, jumpToStep } = useWizardState({
485
- stepOrder,
486
- steps,
487
- validateStep: useCallback(async (stepKey) => {
488
- const stepConfig = steps[stepKey];
489
- if (!stepConfig?.options) return true;
490
- const stepFieldNames = Object.keys(stepConfig.options);
491
- const shouldFlatten = config.flatten !== false;
492
- const errors = await validateForm();
493
- const hasStepErrors = shouldFlatten ? stepFieldNames.some((fieldName) => errors[fieldName]) : stepFieldNames.some((fieldName) => hasNestedError(errors[stepKey], fieldName));
494
- if (hasStepErrors) {
495
- const touchedFields = stepFieldNames.reduce((acc, fieldName) => {
496
- acc[fieldName] = true;
497
- return acc;
498
- }, {});
499
- if (shouldFlatten) setTouched({
500
- ...touched,
501
- ...touchedFields
502
- }, false);
503
- else setTouched({
504
- ...touched,
505
- [stepKey]: touchedFields
506
- }, false);
507
- }
508
- return !hasStepErrors;
509
- }, [
510
- steps,
511
- validateForm,
512
- setTouched,
513
- touched
514
- ])
515
- });
516
- const currentStepConfig = steps[currentStep];
517
- const config_ = config.navigation || {};
518
- const handleGoToNext = useCallback(async () => {
519
- const success = await goToNext();
520
- if (success) onChange?.(values);
521
- return success;
522
- }, [
523
- goToNext,
524
- values,
525
- onChange
526
- ]);
527
- const handleJumpToStep = useCallback((stepKey) => {
528
- if (!config_.allowJump || !visitedSteps.has(stepKey)) return false;
529
- return jumpToStep(stepKey);
530
- }, [
531
- jumpToStep,
532
- config_.allowJump,
533
- visitedSteps
534
- ]);
535
- const handleSubmit = useCallback(async () => {
536
- if (!isLastStep) return;
537
- const errors = await validateForm();
538
- if (Object.keys(errors).length > 0) {
539
- const touchedFields = Object.keys(values).reduce((acc, fieldName) => {
540
- acc[fieldName] = true;
541
- return acc;
542
- }, {});
543
- setTouched({
544
- ...touched,
545
- ...touchedFields
546
- }, false);
547
- throw new Error("Please fix errors before submitting");
548
- }
549
- await onSubmit?.(values);
550
- }, [
551
- isLastStep,
552
- validateForm,
553
- values,
554
- setTouched,
555
- touched,
556
- onSubmit
557
- ]);
558
- return /* @__PURE__ */ jsxs("div", {
559
- className,
560
- children: [
561
- config_.showSteps !== false && /* @__PURE__ */ jsx(WizardStepIndicator, {
562
- stepOrder,
563
- currentStep,
564
- steps,
565
- visitedSteps,
566
- invalidSteps,
567
- onStepClick: handleJumpToStep,
568
- allowJump: config_.allowJump,
569
- hideStepNumbers: config_.hideStepNumbers
570
- }),
571
- currentStepConfig && /* @__PURE__ */ jsx("div", {
572
- className: "mb-4",
573
- children: /* @__PURE__ */ jsx(WizardStepContent, {
574
- currentStep,
575
- stepConfig: currentStepConfig,
576
- flatten: config.flatten,
577
- values,
578
- onValuesChange: onChange,
579
- labelPosition,
580
- missingFields,
581
- spaceCompact,
582
- setFormDirty: () => onChange?.(values),
583
- workflowForm,
584
- setFieldValue,
585
- setFieldTouched
586
- })
587
- }),
588
- /* @__PURE__ */ jsx(WizardNavigation, {
589
- currentStep,
590
- stepOrder,
591
- canGoToNext: !isLastStep,
592
- canGoBack,
593
- isCurrentStepValid: !invalidSteps.has(currentStep),
594
- nextLabel: currentStepConfig?.nextLabel,
595
- prevLabel: currentStepConfig?.prevLabel,
596
- submitLabel: config.submitLabel || "Execute",
597
- onNext: handleGoToNext,
598
- onPrevious: goToPrevious,
599
- onSubmit: handleSubmit
600
- })
601
- ]
364
+ //#endregion
365
+ //#region src/form/Fields/BooleanField.tsx
366
+ function BooleanField(props) {
367
+ const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty } = props;
368
+ const [fieldState] = useField(field.name);
369
+ const { setFieldValue, setFieldTouched } = useFormikContext();
370
+ const handleChange = (val) => {
371
+ if (disabled) return;
372
+ if (onChange) onChange(val);
373
+ setFieldTouched(field.name, true);
374
+ setFieldValue(field.name, val);
375
+ setFormDirty(true);
376
+ };
377
+ const value = fieldState.value;
378
+ const options = field.options ? {
379
+ onOption: String(field.options.onOption ?? true),
380
+ offOption: String(field.options.offOption ?? false)
381
+ } : void 0;
382
+ const displayLabel = (option) => option.charAt(0).toUpperCase() + option.slice(1).toLowerCase();
383
+ return /* @__PURE__ */ jsx(FieldWrapper, {
384
+ optional: true,
385
+ label,
386
+ tooltipComponent: /* @__PURE__ */ jsx("div", { children: tooltipComponent }),
387
+ description: field.description,
388
+ labelPosition,
389
+ spaceCompact,
390
+ children: ({ id, describedBy }) => options ? /* @__PURE__ */ jsx(SwitchToggle, {
391
+ id,
392
+ "aria-describedby": describedBy,
393
+ onChange: (on) => handleChange(on ? options.onOption : options.offOption),
394
+ value: value === true || value === options.onOption,
395
+ yesLabel: displayLabel(options.onOption),
396
+ noLabel: displayLabel(options.offOption),
397
+ disabled,
398
+ invalid: missing
399
+ }) : /* @__PURE__ */ jsx(SwitchToggle, {
400
+ id,
401
+ "aria-describedby": describedBy,
402
+ datatip: field.disabled_message,
403
+ onChange: handleChange,
404
+ value: value === true || value === "true",
405
+ disabled,
406
+ invalid: missing
407
+ })
602
408
  });
603
409
  }
604
410
  //#endregion
605
- //#region src/form/Wizard/utils.ts
606
- function isWizardMeta(value) {
607
- return typeof value === "object" && value !== null && "wizard" in value;
411
+ //#region src/form/Fields/CheckboxGroupField.tsx
412
+ function impliedFrom(values, implies = {}) {
413
+ const result = /* @__PURE__ */ new Set();
414
+ for (const value of values) for (const target of implies[value] ?? []) result.add(target);
415
+ return result;
608
416
  }
609
- function isStepField(field) {
610
- return typeof field === "object" && field !== null && "type" in field && field.type === "step";
417
+ function union(manual, forced) {
418
+ return [...manual, ...[...forced].filter((v) => !manual.includes(v))];
611
419
  }
612
- function parseWizardConfig(options) {
613
- const meta = options["$meta"];
614
- const wizardConfig = isWizardMeta(meta) ? meta.wizard : void 0;
615
- if (!wizardConfig || wizardConfig.mode !== "wizard") return null;
616
- const steps = {};
617
- const stepOrder = [];
618
- const otherFields = {};
619
- Object.entries(options).forEach(([key, field]) => {
620
- if (key === "$meta") return;
621
- if (isStepField(field)) {
622
- steps[key] = field;
623
- stepOrder.push(key);
624
- } else otherFields[key] = field;
420
+ function CheckboxGroupField({ field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty }) {
421
+ const [fieldState] = useField(field.name);
422
+ const { setFieldValue, setFieldTouched } = useFormikContext();
423
+ const implies = field.implies;
424
+ const [manual, setManual] = useState(() => {
425
+ const saved = fieldState.value || [];
426
+ const impliedBySaved = impliedFrom(saved, implies);
427
+ return saved.filter((v) => !impliedBySaved.has(v));
625
428
  });
626
- if (stepOrder.length === 0) {
627
- console.warn("Wizard mode enabled but no steps found");
628
- return null;
629
- }
630
- return {
631
- config: wizardConfig,
632
- steps,
633
- stepOrder,
634
- otherFields
429
+ const forced = impliedFrom(manual, implies);
430
+ const handleToggle = (value) => {
431
+ if (disabled) return;
432
+ const nextManual = manual.includes(value) ? manual.filter((v) => v !== value) : [...manual, value];
433
+ const next = union(nextManual, impliedFrom(nextManual, implies));
434
+ setManual(nextManual);
435
+ setFieldTouched(field.name, true);
436
+ setFieldValue(field.name, next);
437
+ if (onChange) onChange(next);
438
+ setFormDirty(true);
635
439
  };
440
+ return /* @__PURE__ */ jsx(FieldWrapper, {
441
+ optional: field.optional,
442
+ label,
443
+ tooltipComponent,
444
+ labelPosition,
445
+ spaceCompact,
446
+ children: ({ labelId, describedBy }) => /* @__PURE__ */ jsx("fieldset", {
447
+ "aria-labelledby": labelId,
448
+ "aria-describedby": describedBy,
449
+ className: "flex min-w-0 max-w-lg flex-col gap-1",
450
+ children: field.options?.map((option) => {
451
+ const locked = forced.has(option.value);
452
+ const optionDisabled = disabled || locked;
453
+ return /* @__PURE__ */ jsxs("label", {
454
+ className: cx("-mx-2 flex items-start gap-2.5 rounded-md px-2 py-1.5 transition-colors", optionDisabled ? "cursor-not-allowed opacity-60" : "cursor-pointer hover:bg-(--theme-hover) has-[:focus-visible]:bg-(--theme-hover)"),
455
+ children: [/* @__PURE__ */ jsx("input", {
456
+ type: "checkbox",
457
+ checked: manual.includes(option.value) || locked,
458
+ disabled: optionDisabled,
459
+ onChange: () => handleToggle(option.value),
460
+ className: cx("mt-px h-4 w-4 rounded accent-(--theme-link)", optionDisabled ? "cursor-not-allowed" : "cursor-pointer", missing && "invalid")
461
+ }), /* @__PURE__ */ jsxs("div", {
462
+ className: "flex min-w-0 flex-col",
463
+ children: [/* @__PURE__ */ jsx("span", {
464
+ className: "font-medium leading-tight theme-text",
465
+ children: option.label
466
+ }), option.description && /* @__PURE__ */ jsx("span", {
467
+ className: "mt-0.5 text-xs leading-snug theme-muted-text",
468
+ children: option.description
469
+ })]
470
+ })]
471
+ }, field.name + option.value);
472
+ })
473
+ })
474
+ });
636
475
  }
637
476
  //#endregion
638
- //#region src/form/SpecialTypes/FormikCustomDropdown.tsx
639
- function toComparableOptions(options) {
640
- return options?.map((option) => {
641
- if (typeof option === "string") return option;
642
- if (isCategory(option)) return {
643
- category: option.category,
644
- options: option.options.map((o) => {
645
- const base = {
646
- label: o.label,
647
- value: o.value,
648
- disabled: o.disabled
649
- };
650
- return o.description !== void 0 ? {
651
- ...base,
652
- description: o.description
653
- } : base;
654
- })
655
- };
656
- const base = {
657
- label: option.label,
658
- value: option.value,
659
- disabled: option.disabled
660
- };
661
- return option.description !== void 0 ? {
662
- ...base,
663
- description: option.description
664
- } : base;
665
- });
666
- }
667
- function arePropsEqual(prev, next) {
668
- for (const key of Object.keys(next)) if (key === "options") {
669
- if (!deepEqual(toComparableOptions(prev.options), toComparableOptions(next.options))) return false;
670
- } else if (prev[key] !== next[key]) return false;
671
- return true;
672
- }
673
- var FormikCustomDropdown_default = memo(function FormikCustomDropdown({ name, ariaLabel, options, setFormDirty, onChange, parentValue, className, secondaryField, disabled = false, loading = false, invalid = false, autoselect, value, allowCustomValue = false, customValueLabel, placeholder, currentValue, setFieldValue, setFieldTouched }) {
674
- const required = useFieldRequired();
675
- const controlProps = useFieldControlProps();
676
- const prevParentRef = useRef(parentValue);
677
- const handleChange = (val) => {
678
- if (secondaryField && typeof options[0] !== "string") {
679
- const option = findSecondaryOption(options, val);
680
- if (option) applySecondaryField(secondaryField, option.secondaryValue, setFieldValue);
681
- }
477
+ //#region src/form/Fields/ColorField.tsx
478
+ function ColorField(props) {
479
+ const { field, label, labelPosition, onChange, tooltipComponent, spaceCompact } = props;
480
+ const [fieldState] = useField(field.name);
481
+ const { setFieldValue, setFieldTouched } = useFormikContext();
482
+ const handleChange = (e) => {
483
+ const val = e.target.value;
682
484
  if (onChange) onChange(val);
683
- setFieldValue(name, val);
684
- setFieldTouched(name, true);
685
- setFormDirty(true);
485
+ setFieldTouched(field.name, true);
486
+ setFieldValue(field.name, val);
686
487
  };
687
- useEffect(() => {
688
- prevParentRef.current = parentValue;
488
+ return /* @__PURE__ */ jsx(FieldWrapper, {
489
+ optional: field.optional,
490
+ label,
491
+ tooltipComponent,
492
+ labelPosition,
493
+ spaceCompact,
494
+ children: ({ id }) => /* @__PURE__ */ jsx(Field, {
495
+ className: "theme-input h-8 w-18 rounded border border-neutral-400 placeholder:text-xs",
496
+ id,
497
+ name: field.name,
498
+ value: fieldState.value || "#fbbf24",
499
+ type: "color",
500
+ "aria-required": field.optional ? void 0 : true,
501
+ onChange: handleChange
502
+ })
689
503
  });
690
- const prevOptions = useRef(null);
691
- useEffect(() => {
692
- if (deepEqual(prevOptions.current, toComparableOptions(options))) return;
693
- if (prevOptions.current === null && currentValue !== void 0 && currentValue !== null) {
694
- prevOptions.current = toComparableOptions(options);
695
- return;
696
- }
697
- prevOptions.current = toComparableOptions(options);
698
- if (autoselect === false || !options?.length) return;
699
- if (autoselect === void 0) {
700
- if (options.length && isCategory(options[0])) {
701
- let optionCount = 0;
702
- for (const category of options) {
703
- if (isCategory(category)) optionCount += category.options.length;
704
- if (optionCount > 1) return;
705
- }
706
- } else if (options.length !== 1) return;
707
- }
708
- if (!options?.find((option) => {
709
- if (typeof option === "string") return option === currentValue;
710
- else if (isCategory(option)) return option.options.some((subOption) => {
711
- return subOption.value === currentValue;
712
- });
713
- return option.value === currentValue;
714
- }) || autoselect) {
715
- let val;
716
- for (const option of options) if (typeof option === "string") {
717
- val = option;
718
- break;
719
- } else if (isCategory(option)) {
720
- const enabled = option.options.find((o) => !o.disabled);
721
- if (enabled) {
722
- val = enabled.value;
723
- break;
724
- }
725
- } else if (!option.disabled) {
726
- val = option.value;
727
- break;
504
+ }
505
+ //#endregion
506
+ //#region src/form/Fields/DropdownField.tsx
507
+ function DropdownField(props) {
508
+ const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFieldValue, setFieldTouched, currentValue } = props;
509
+ let parentValue = field.parentValue;
510
+ let options = Array.isArray(field.options) && field.options || [];
511
+ if (field.depends_on) {
512
+ parentValue = field.depends_on;
513
+ if (!Array.isArray(field.options)) options = field.options[parentValue] ?? [];
514
+ }
515
+ const secondaryField = Array.isArray(field.secondaryField) ? field.secondaryField[0] : field.secondaryField;
516
+ return /* @__PURE__ */ jsx(FieldWrapper, {
517
+ optional: field.optional,
518
+ label,
519
+ tooltipComponent,
520
+ description: field.description,
521
+ labelPosition,
522
+ spaceCompact,
523
+ children: /* @__PURE__ */ jsx("div", {
524
+ className: "grow min-w-0",
525
+ children: /* @__PURE__ */ jsx(FormikCustomDropdown_default, {
526
+ invalid: missing,
527
+ className: "w-full",
528
+ name: field.name,
529
+ ariaLabel: label,
530
+ options,
531
+ setFormDirty: props.setFormDirty,
532
+ parentValue,
533
+ onChange,
534
+ secondaryField,
535
+ disabled,
536
+ resetOnChange: field.resetOnChange ? field.name : "",
537
+ loading: false,
538
+ autoselect: field.autoselect,
539
+ allowCustomValue: field.allowCustomValue,
540
+ customValueLabel: field.customValueLabel,
541
+ placeholder: getPlaceholder(field.placeholder, typeof field.default === "string" ? field.default : void 0),
542
+ currentValue,
543
+ setFieldValue,
544
+ setFieldTouched
545
+ }, field.name)
546
+ })
547
+ });
548
+ }
549
+ //#endregion
550
+ //#region src/form/utils/duration.ts
551
+ var WITH_DAYS = /^(\d+)-(\d{1,2})(?::(\d{1,2})(?::(\d{1,2}))?)?$/;
552
+ var WITHOUT_DAYS = /^(\d+):(\d{1,2}):(\d{1,2})$/;
553
+ function parseDuration(text) {
554
+ const trimmed = text.trim();
555
+ let days = 0;
556
+ let hours;
557
+ let minutes;
558
+ let seconds;
559
+ const withDays = trimmed.match(WITH_DAYS);
560
+ if (withDays) {
561
+ days = Number(withDays[1]);
562
+ hours = Number(withDays[2]);
563
+ minutes = Number(withDays[3] ?? 0);
564
+ seconds = Number(withDays[4] ?? 0);
565
+ if (hours > 23) return null;
566
+ } else {
567
+ const withoutDays = trimmed.match(WITHOUT_DAYS);
568
+ if (!withoutDays) return null;
569
+ hours = Number(withoutDays[1]);
570
+ minutes = Number(withoutDays[2]);
571
+ seconds = Number(withoutDays[3]);
572
+ }
573
+ if (minutes > 59 || seconds > 59) return null;
574
+ return days * 86400 + hours * 3600 + minutes * 60 + seconds;
575
+ }
576
+ function formatDuration(totalSeconds) {
577
+ if (!Number.isFinite(totalSeconds) || totalSeconds < 0) return "";
578
+ const parts = Duration.fromObject({ seconds: Math.floor(totalSeconds) }).shiftTo("days", "hours", "minutes", "seconds");
579
+ const pad = (n) => String(n).padStart(2, "0");
580
+ const time = `${pad(parts.hours)}:${pad(parts.minutes)}:${pad(parts.seconds)}`;
581
+ return parts.days > 0 ? `${parts.days}-${time}` : time;
582
+ }
583
+ var FLEX_MAX_DURATION_RANGE = {
584
+ min: 600,
585
+ max: 604800
586
+ };
587
+ var FLEX_WAIT_TIME_RANGE = {
588
+ min: 30,
589
+ max: 604800
590
+ };
591
+ var STANDARD_MAX_DURATION_RANGE = {
592
+ min: 30,
593
+ max: 10368e3
594
+ };
595
+ var DURATION_PATH = /^partitions\[\d+\]\.(maxDuration|flexStartWaitTime|suspendTime)$/;
596
+ function collectPartitionDurationIssues(partitions, invalidDurationPaths) {
597
+ const issues = new Set(invalidDurationPaths.filter((path) => DURATION_PATH.test(path)));
598
+ (partitions ?? []).forEach((partition, i) => {
599
+ const p = partition;
600
+ const check = (fieldName, range, required) => {
601
+ const raw = p?.[fieldName];
602
+ if (raw === void 0 || raw === null || raw === "") {
603
+ if (required) issues.add(`partitions[${i}].${fieldName}`);
604
+ return;
728
605
  }
729
- if (val !== void 0) handleChange(val);
730
- }
731
- }, [
732
- parentValue,
733
- options,
734
- autoselect
735
- ]);
736
- return /* @__PURE__ */ jsx(Dropdown, {
737
- ...controlProps,
738
- textBoxClassName: cx(className, invalid && "invalid"),
739
- ariaLabel,
740
- required,
741
- onChange: (val) => handleChange(val),
742
- value: options?.length === 0 && !allowCustomValue ? value : currentValue,
743
- options,
744
- placeholder,
745
- disabled,
746
- loading,
747
- allowCustomValue,
748
- customValueLabel
606
+ const value = Number(raw);
607
+ if (!Number.isFinite(value) || value < range.min || value > range.max) issues.add(`partitions[${i}].${fieldName}`);
608
+ };
609
+ if (p?.provisioningMode === "flex") {
610
+ check("maxDuration", FLEX_MAX_DURATION_RANGE, true);
611
+ check("flexStartWaitTime", FLEX_WAIT_TIME_RANGE, true);
612
+ } else check("maxDuration", STANDARD_MAX_DURATION_RANGE, false);
749
613
  });
750
- }, arePropsEqual);
614
+ return [...issues];
615
+ }
751
616
  //#endregion
752
- //#region src/form/SpecialTypes/FormikCustomInput.tsx
753
- var DEBOUNCE_MS$2 = 500;
754
- function FormikCustomInput({ name, type, step, placeholder, onChange, disabled, min, max, sanitize, invalid = false, lowercase = false, minLength, maxLength, defaultValue = "", value }) {
755
- const [field] = useField(name);
756
- const required = useFieldRequired();
757
- const controlProps = useFieldControlProps();
758
- const { setFieldValue, setFieldTouched } = useFormikContext();
759
- const [showPassword, setShowPassword] = useState(false);
760
- const isPassword = type === "password";
761
- const [localValue, setLocalValue] = useState(value ?? field.value);
762
- const isFocused = useRef(false);
617
+ //#region src/form/Fields/DurationField.tsx
618
+ var DEBOUNCE_MS = 500;
619
+ var isNumericString = (value) => typeof value === "string" && /^\d+$/.test(value.trim());
620
+ var toDisplay = (value) => {
621
+ if (typeof value === "number") return formatDuration(value);
622
+ if (isNumericString(value)) return formatDuration(Number(value));
623
+ return "";
624
+ };
625
+ var DurationField_default = memo(function DurationField({ field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty, currentValue, setFieldValue, setFieldTouched }) {
626
+ const { form: t } = useStrings();
627
+ const name = field.name;
628
+ const min = field.min !== void 0 ? Number(field.min) : Number.NEGATIVE_INFINITY;
629
+ const max = field.max !== void 0 ? Number(field.max) : Number.POSITIVE_INFINITY;
630
+ const disableValue = field.disableValue;
631
+ const hasDisableCheckbox = disableValue !== void 0 && field.disableLabel !== void 0;
632
+ const isSentinel = hasDisableCheckbox && currentValue !== "" && Number(currentValue) === disableValue;
633
+ const [text, setText] = useState(() => toDisplay(currentValue));
634
+ const focusedRef = useRef(false);
763
635
  const debounceRef = useRef(null);
764
636
  useEffect(() => {
765
- if (!isFocused.current) setLocalValue(value ?? field.value);
766
- }, [field.value, value]);
767
- const sanitizeName = useCallback((val) => {
768
- if (sanitize === void 0) return lowercase ? val.toLowerCase() : val;
769
- try {
770
- const regexp = new RegExp(sanitize);
771
- if (lowercase) val = val.toLowerCase();
772
- return val.replace(regexp, "");
773
- } catch {
774
- console.error("Invalid regex for sanitization:", sanitize);
775
- }
776
- return val;
777
- }, [sanitize, lowercase]);
778
- const flushToFormik = useCallback((val) => {
779
- if (onChange) onChange(val);
780
- setFieldTouched(name, true);
781
- setFieldValue(name, val);
782
- }, [
783
- name,
784
- onChange,
785
- setFieldTouched,
786
- setFieldValue
787
- ]);
788
- const handleChange = (e) => {
789
- const text = sanitize ? sanitizeName(e.target.value) : e.target.value;
790
- let val = text;
791
- if (type === "number") {
792
- const parsed = parseFloat(text);
793
- val = isNaN(parsed) ? void 0 : parsed;
794
- }
795
- if (type === "password" && val === "") val = defaultValue;
796
- setLocalValue(val);
637
+ if (isSentinel) {
638
+ if (typeof currentValue !== "number") setFieldValue?.(name, disableValue);
639
+ } else if (isNumericString(currentValue)) setFieldValue?.(name, Number(currentValue));
640
+ else if ((currentValue === void 0 || currentValue === null || currentValue === "") && typeof field.default === "number") setFieldValue?.(name, field.default);
641
+ }, []);
642
+ useEffect(() => {
643
+ if (!focusedRef.current) setText(toDisplay(currentValue));
644
+ }, [currentValue]);
645
+ const trimmed = text.trim();
646
+ const parsed = trimmed === "" ? null : parseDuration(trimmed);
647
+ let error = null;
648
+ if (!isSentinel && trimmed !== "") {
649
+ if (parsed === null) error = t.invalidDuration;
650
+ else if (parsed < min || parsed > max) error = Number.isFinite(max) ? t.durationOutOfRange(formatDuration(Math.max(min, 0)), formatDuration(max)) : t.durationBelowMin(formatDuration(Math.max(min, 0)));
651
+ }
652
+ useEffect(() => {
653
+ setDurationValidity(name, error === null);
654
+ }, [name, error]);
655
+ useEffect(() => () => {
656
+ setDurationValidity(name, true);
797
657
  clearTimeout(debounceRef.current ?? void 0);
798
- debounceRef.current = setTimeout(() => flushToFormik(val), DEBOUNCE_MS$2);
658
+ }, [name]);
659
+ const flushToFormik = (val) => {
660
+ setFieldValue?.(name, val);
661
+ onChange?.(val);
662
+ setFieldTouched?.(name, true);
799
663
  };
800
- const handleFocus = () => {
801
- isFocused.current = true;
664
+ const handleDisableToggle = (checked) => {
665
+ clearTimeout(debounceRef.current ?? void 0);
666
+ setFormDirty(true);
667
+ if (checked) {
668
+ setText("");
669
+ flushToFormik(disableValue);
670
+ } else flushToFormik(typeof field.default === "number" ? field.default : "");
802
671
  };
803
- const handleBlur = () => {
804
- isFocused.current = false;
672
+ const handleChange = (e) => {
673
+ const raw = e.target.value;
674
+ setText(raw);
675
+ setFormDirty(true);
805
676
  clearTimeout(debounceRef.current ?? void 0);
806
- const current = localValue;
807
- if (current === void 0 || current === null) return;
808
- const text = current.toString();
809
- let val = text;
810
- if (type === "number") {
811
- const parsed = parseFloat(text);
812
- if (Number.isNaN(parsed)) val = void 0;
813
- else if (min !== void 0 && parsed < min) val = min;
814
- else if (max !== void 0 && parsed > max) val = max;
815
- else val = parsed;
816
- }
817
- setLocalValue(val);
818
- flushToFormik(val);
677
+ const rawTrimmed = raw.trim();
678
+ const value = rawTrimmed === "" ? "" : parseDuration(rawTrimmed);
679
+ if (value === "" || typeof value === "number" && value >= min && value <= max) debounceRef.current = setTimeout(() => flushToFormik(value), DEBOUNCE_MS);
819
680
  };
820
- const displayValue = isPassword && localValue === defaultValue ? "" : localValue ?? "";
821
- const resolvedType = isPassword ? showPassword ? "text" : "password" : type === "string" ? "text" : type;
822
- const input = /* @__PURE__ */ jsx("input", {
823
- ...controlProps,
824
- "data-1p-ignore": true,
825
- className: cx("w-full", disabled && "disabled", invalid && "invalid", isPassword && "pr-8"),
826
- name,
827
- value: displayValue,
828
- type: resolvedType,
829
- "aria-required": required || void 0,
830
- disabled,
831
- placeholder,
832
- minLength,
833
- maxLength,
834
- onChange: handleChange,
835
- onFocus: handleFocus,
836
- onBlur: handleBlur,
837
- min,
838
- max,
839
- step
840
- });
841
- if (!isPassword) return input;
842
- return /* @__PURE__ */ jsxs("div", {
843
- className: "relative w-full",
844
- children: [input, /* @__PURE__ */ jsx("button", {
845
- type: "button",
846
- className: "absolute right-2 top-1/2 -translate-y-1/2 theme-muted-text",
847
- onClick: () => setShowPassword((prev) => !prev),
848
- disabled,
849
- tabIndex: -1,
850
- "aria-label": showPassword ? "Hide value" : "Show value",
851
- children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(EyeIcon, { className: "h-4 w-4" })
852
- })]
853
- });
854
- }
855
- //#endregion
856
- //#region src/form/SpecialTypes/FormikNumericInput.tsx
857
- var DEBOUNCE_MS$1 = 500;
858
- function FormikNumericInput({ name, placeholder, onChange, disabled, min, max, invalid = false }) {
859
- const [field] = useField(name);
860
- const required = useFieldRequired();
861
- const controlProps = useFieldControlProps();
862
- const { setFieldValue, setFieldTouched } = useFormikContext();
863
- const [localValue, setLocalValue] = useState(field.value);
864
- const isFocused = useRef(false);
865
- const debounceRef = useRef(null);
866
- useEffect(() => {
867
- if (!isFocused.current) setLocalValue(field.value);
868
- }, [field.value]);
869
- const flush = (val) => {
870
- onChange?.(val);
871
- setFieldTouched(name, true);
872
- setFieldValue(name, val);
681
+ const handleFocus = () => {
682
+ focusedRef.current = true;
873
683
  };
874
684
  const handleBlur = () => {
875
- isFocused.current = false;
685
+ focusedRef.current = false;
876
686
  clearTimeout(debounceRef.current ?? void 0);
877
- let val = localValue;
878
- if (val !== void 0) {
879
- if (min !== void 0 && val < min) val = min;
880
- if (max !== void 0 && val > max) val = max;
687
+ if (trimmed === "") {
688
+ setText("");
689
+ flushToFormik("");
690
+ } else if (parsed !== null && parsed >= min && parsed <= max) {
691
+ setText(formatDuration(parsed));
692
+ flushToFormik(parsed);
881
693
  }
882
- setLocalValue(val);
883
- flush(val);
884
694
  };
885
- return /* @__PURE__ */ jsx(NumericFormat, {
886
- ...controlProps,
887
- "data-1p-ignore": true,
888
- className: cx("w-full", disabled && "disabled", invalid && "invalid"),
889
- name,
890
- ...localValue === void 0 ? {} : { value: localValue },
891
- "aria-required": required || void 0,
892
- thousandSeparator: ",",
893
- decimalScale: 20,
894
- allowNegative: true,
895
- disabled,
896
- placeholder,
897
- onValueChange: (v) => {
898
- setLocalValue(v.floatValue);
899
- clearTimeout(debounceRef.current ?? void 0);
900
- debounceRef.current = setTimeout(() => flush(v.floatValue), DEBOUNCE_MS$1);
901
- },
902
- onFocus: () => {
903
- isFocused.current = true;
904
- },
905
- onBlur: handleBlur
695
+ return /* @__PURE__ */ jsx(FieldWrapper, {
696
+ optional: field.optional ?? false,
697
+ label,
698
+ tooltipComponent,
699
+ labelPosition,
700
+ spaceCompact: spaceCompact ?? false,
701
+ ...field.description !== void 0 && { description: field.description },
702
+ children: ({ id, describedBy }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
703
+ !isSentinel && /* @__PURE__ */ jsx("input", {
704
+ "data-1p-ignore": true,
705
+ className: cx("w-full", disabled && "disabled", (missing || error !== null) && "invalid"),
706
+ id,
707
+ "aria-describedby": describedBy,
708
+ name,
709
+ value: text,
710
+ type: "text",
711
+ "aria-required": field.optional ? void 0 : true,
712
+ disabled,
713
+ placeholder: field.placeholder ?? "DD-HH:MM:SS",
714
+ onChange: handleChange,
715
+ onFocus: handleFocus,
716
+ onBlur: handleBlur
717
+ }),
718
+ error && /* @__PURE__ */ jsx("div", {
719
+ className: "text-xs text-red-500 mt-1",
720
+ children: error
721
+ }),
722
+ hasDisableCheckbox && /* @__PURE__ */ jsxs("label", {
723
+ className: cx("flex items-center gap-2 mt-1", disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer"),
724
+ children: [/* @__PURE__ */ jsx("input", {
725
+ type: "checkbox",
726
+ checked: isSentinel,
727
+ disabled,
728
+ onChange: (e) => handleDisableToggle(e.target.checked),
729
+ className: cx("h-4 w-4 accent-(--theme-link)", disabled ? "cursor-not-allowed" : "cursor-pointer")
730
+ }), /* @__PURE__ */ jsx("span", {
731
+ className: "theme-text",
732
+ children: field.disableLabel
733
+ })]
734
+ })
735
+ ] })
906
736
  });
907
- }
737
+ });
908
738
  //#endregion
909
- //#region src/form/utils/getPlaceholder.ts
910
- /** `placeholder=""` (empty string) intentionally overrides the default; undefined does not. */
911
- function getPlaceholder(placeholder, defaultValue) {
912
- if (placeholder !== void 0) return placeholder;
913
- if (defaultValue && (typeof defaultValue === "string" || typeof defaultValue === "number")) return defaultValue.toString();
914
- return "";
739
+ //#region src/form/Fields/LabelField.tsx
740
+ function LabelField(props) {
741
+ const { field, label } = props;
742
+ return /* @__PURE__ */ jsx("div", {
743
+ className: "flex items-center w-full",
744
+ children: /* @__PURE__ */ jsx("h3", {
745
+ style: field.size ? {
746
+ fontSize: field.size.toString() + "px",
747
+ whiteSpace: "pre-wrap"
748
+ } : { whiteSpace: "pre-wrap" },
749
+ className: cx("relative", "text-md", field.bold !== false ? "font-bold" : ""),
750
+ children: field.text && field.text.toString().replace(/(\r)/g, "").replace(/(\\n)/g, "\n") || label
751
+ })
752
+ });
915
753
  }
916
754
  //#endregion
917
- //#region src/form/Fields/StringField.tsx
918
- var StringField_default = memo(function StringField(props) {
919
- const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, currentValue } = props;
920
- const value = field.prefillDefault ? currentValue : void 0;
755
+ //#region src/form/Fields/MultiDropdownField.tsx
756
+ function MultiDropdownField({ field, label, tooltipComponent, labelPosition, spaceCompact, disabled, missing, setFormDirty, onChange }) {
757
+ let parentValue = field.parentValue;
758
+ let options = field.options;
759
+ if (field.depends_on) {
760
+ parentValue = field.depends_on;
761
+ if (!Array.isArray(field.options)) options = field.options[parentValue] || [];
762
+ }
921
763
  return /* @__PURE__ */ jsx(FieldWrapper, {
922
764
  optional: field.optional,
923
765
  label,
924
766
  tooltipComponent,
925
- description: field.description,
926
767
  labelPosition,
927
768
  spaceCompact,
928
- children: /* @__PURE__ */ jsx(FormikCustomInput, {
929
- invalid: missing,
769
+ children: ({ id }) => disabled ? /* @__PURE__ */ jsx(Field, {
770
+ className: "theme-input h-7 w-full rounded border placeholder:text-xs",
771
+ id,
772
+ type: "search",
930
773
  name: field.name,
931
- type: field.type,
932
- lowercase: field.lowercase,
933
- placeholder: field.type === "password" && field.placeholder === void 0 ? "•".repeat((field.default ?? "").toString().length > 0 ? 16 : 0) : getPlaceholder(field.placeholder, field.default),
934
- disabled,
935
- step: field.step,
936
- min: field.min,
937
- max: field.max,
938
- minLength: field.minLength,
939
- maxLength: field.maxLength,
940
- sanitize: field.sanitize,
941
- defaultValue: field.default,
942
- value,
943
- ...onChange && { onChange }
944
- }, field.name)
945
- });
946
- });
947
- //#endregion
948
- //#region src/form/Fields/NumberField.tsx
949
- function NumberField(props) {
950
- const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact } = props;
951
- const [fieldState] = useField(field.name);
952
- const { setFieldValue } = useFormikContext();
953
- useEffect(() => {
954
- if (field.prefillDefault && field.default !== void 0 && (fieldState.value === void 0 || fieldState.value === null || fieldState.value === "")) setFieldValue(field.name, field.default);
955
- }, [
956
- field.prefillDefault,
957
- field.default,
958
- field.name,
959
- fieldState.value,
960
- setFieldValue
961
- ]);
962
- return /* @__PURE__ */ jsx(FieldWrapper, {
963
- optional: field.optional,
964
- label,
965
- tooltipComponent,
966
- labelPosition,
967
- spaceCompact,
968
- children: field.thousandsSeparator ? /* @__PURE__ */ jsx(FormikNumericInput, {
969
- invalid: missing,
970
- name: field.name,
971
- placeholder: getPlaceholder(field.placeholder, field.default),
972
- disabled,
973
- min: field.min,
974
- max: field.max,
975
- ...onChange && { onChange }
976
- }, field.name) : /* @__PURE__ */ jsx(FormikCustomInput, {
977
- invalid: missing,
774
+ disabled: true,
775
+ onChange
776
+ }, field.name) : /* @__PURE__ */ jsx(MultiSelectionDropdown, {
777
+ className: "themee-input h-7 w-full rounded border flex items-center px-3 py-2",
978
778
  name: field.name,
979
- type: "number",
980
- placeholder: getPlaceholder(field.placeholder, field.default),
981
- disabled,
982
- step: field.step,
983
- min: field.min,
984
- max: field.max,
985
- defaultValue: field.default,
986
- ...onChange && { onChange }
779
+ label: field.label || label,
780
+ options: Array.isArray(options) ? options : [],
781
+ setFormDirty,
782
+ parentValue,
783
+ onChange,
784
+ secondaryField: typeof field.secondaryField === "string" ? field.secondaryField : void 0,
785
+ invalid: missing
987
786
  }, field.name)
988
787
  });
989
788
  }
990
789
  //#endregion
991
- //#region src/form/Fields/BooleanField.tsx
992
- function BooleanField(props) {
790
+ //#region src/form/Fields/ObjectField.tsx
791
+ function ObjectField({ field, label, tooltipComponent, missingFields, setFormDirty, workflowForm, labelPosition, spaceCompact }) {
792
+ const [open, setOpen] = useState(field.collapsed !== void 0 ? !field.collapsed : true);
793
+ const { values, setFieldValue, setFieldTouched } = useFormikContext();
794
+ const meta = resolveMetaOverrides(field.options, labelPosition, spaceCompact ?? false);
795
+ const HeaderElement = field.noCollapse ? React.Fragment : ControlledCollapsiblePanel;
796
+ return /* @__PURE__ */ jsxs("div", {
797
+ className: "flex flex-col w-full",
798
+ children: [field.noCollapse && /* @__PURE__ */ jsx("div", {
799
+ className: "flex items-center w-full mb-[15px]",
800
+ children: /* @__PURE__ */ jsxs("h3", {
801
+ className: "flex items-center gap-1 text-md font-bold",
802
+ children: [
803
+ label,
804
+ " ",
805
+ tooltipComponent
806
+ ]
807
+ })
808
+ }), /* @__PURE__ */ jsx(HeaderElement, {
809
+ open,
810
+ setOpen,
811
+ title: /* @__PURE__ */ jsx(GroupHeader, {
812
+ open,
813
+ setOpen,
814
+ title: field.label || label
815
+ }),
816
+ children: /* @__PURE__ */ jsx("div", {
817
+ className: "w-full",
818
+ children: /* @__PURE__ */ jsx(FieldsFromOptions, {
819
+ options: field.options,
820
+ values,
821
+ setFormDirty,
822
+ setFieldValue,
823
+ setFieldTouched,
824
+ missingFields,
825
+ workflowForm,
826
+ labelPosition: meta.labelPosition,
827
+ spaceCompact: meta.spaceCompact,
828
+ parentInfo: {
829
+ parentName: field.name,
830
+ fieldNamePrefix: `${field.name}.`
831
+ }
832
+ })
833
+ })
834
+ })]
835
+ });
836
+ }
837
+ //#endregion
838
+ //#region src/form/Fields/RadioField.tsx
839
+ function RadioField(props) {
993
840
  const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty } = props;
994
- const [fieldState] = useField(field.name);
841
+ const uid = useId();
995
842
  const { setFieldValue, setFieldTouched } = useFormikContext();
996
843
  const handleChange = (val) => {
997
844
  if (disabled) return;
@@ -1000,106 +847,34 @@ function BooleanField(props) {
1000
847
  setFieldValue(field.name, val);
1001
848
  setFormDirty(true);
1002
849
  };
1003
- const value = fieldState.value;
1004
- const options = field.options ? {
1005
- onOption: String(field.options.onOption ?? true),
1006
- offOption: String(field.options.offOption ?? false)
1007
- } : void 0;
1008
- const displayLabel = (option) => option.charAt(0).toUpperCase() + option.slice(1).toLowerCase();
1009
- return /* @__PURE__ */ jsx(FieldWrapper, {
1010
- optional: true,
1011
- label,
1012
- tooltipComponent: /* @__PURE__ */ jsx("div", { children: tooltipComponent }),
1013
- description: field.description,
1014
- labelPosition,
1015
- spaceCompact,
1016
- children: ({ id, describedBy }) => options ? /* @__PURE__ */ jsx(SwitchToggle, {
1017
- id,
1018
- "aria-describedby": describedBy,
1019
- onChange: (on) => handleChange(on ? options.onOption : options.offOption),
1020
- value: value === true || value === options.onOption,
1021
- yesLabel: displayLabel(options.onOption),
1022
- noLabel: displayLabel(options.offOption),
1023
- disabled,
1024
- invalid: missing
1025
- }) : /* @__PURE__ */ jsx(SwitchToggle, {
1026
- id,
1027
- "aria-describedby": describedBy,
1028
- datatip: field.disabled_message,
1029
- onChange: handleChange,
1030
- value: value === true || value === "true",
1031
- disabled,
1032
- invalid: missing
1033
- })
1034
- });
1035
- }
1036
- //#endregion
1037
- //#region src/form/Fields/DropdownField.tsx
1038
- function DropdownField(props) {
1039
- const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFieldValue, setFieldTouched, currentValue } = props;
1040
- let parentValue = field.parentValue;
1041
- let options = Array.isArray(field.options) && field.options || [];
1042
- if (field.depends_on) {
1043
- parentValue = field.depends_on;
1044
- if (!Array.isArray(field.options)) options = field.options[parentValue] ?? [];
1045
- }
1046
- const secondaryField = Array.isArray(field.secondaryField) ? field.secondaryField[0] : field.secondaryField;
1047
- return /* @__PURE__ */ jsx(FieldWrapper, {
1048
- optional: field.optional,
1049
- label,
1050
- tooltipComponent,
1051
- description: field.description,
1052
- labelPosition,
1053
- spaceCompact,
1054
- children: /* @__PURE__ */ jsx("div", {
1055
- className: "grow min-w-0",
1056
- children: /* @__PURE__ */ jsx(FormikCustomDropdown_default, {
1057
- invalid: missing,
1058
- className: "w-full",
1059
- name: field.name,
1060
- ariaLabel: label,
1061
- options,
1062
- setFormDirty: props.setFormDirty,
1063
- parentValue,
1064
- onChange,
1065
- secondaryField,
1066
- disabled,
1067
- resetOnChange: field.resetOnChange ? field.name : "",
1068
- loading: false,
1069
- autoselect: field.autoselect,
1070
- allowCustomValue: field.allowCustomValue,
1071
- customValueLabel: field.customValueLabel,
1072
- placeholder: getPlaceholder(field.placeholder, typeof field.default === "string" ? field.default : void 0),
1073
- currentValue,
1074
- setFieldValue,
1075
- setFieldTouched
1076
- }, field.name)
1077
- })
1078
- });
1079
- }
1080
- //#endregion
1081
- //#region src/form/Fields/TextAreaField.tsx
1082
- function TextAreaField(props) {
1083
- const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact } = props;
1084
850
  return /* @__PURE__ */ jsx(FieldWrapper, {
1085
851
  optional: field.optional,
1086
852
  label,
1087
853
  tooltipComponent,
1088
- description: field.description,
1089
854
  labelPosition,
1090
855
  spaceCompact,
1091
- children: ({ id, describedBy }) => /* @__PURE__ */ jsx(Field, {
1092
- className: cx("theme-input h-[82px] w-full rounded leading-4 overflow-y-scroll", disabled && "disabled", missing && "border-red-600 focus:border-red-600 focus:ring-red-500"),
1093
- as: "textarea",
1094
- id,
856
+ children: ({ labelId, describedBy }) => /* @__PURE__ */ jsx("div", {
857
+ role: "radiogroup",
858
+ "aria-labelledby": labelId,
1095
859
  "aria-describedby": describedBy,
1096
860
  "aria-required": field.optional ? void 0 : true,
1097
- name: field.name,
1098
- type: "search",
1099
- placeholder: getPlaceholder(field.placeholder, field.default),
1100
- disabled,
1101
- ...onChange && { onChange }
1102
- }, field.name)
861
+ className: cx("flex", field.optionLabelPosition === "top" ? "flex-col" : "flex-row"),
862
+ children: field.options?.map((option) => /* @__PURE__ */ jsxs("label", {
863
+ htmlFor: `${uid}-${option}`,
864
+ className: "capitalize",
865
+ children: [/* @__PURE__ */ jsx(Field, {
866
+ id: `${uid}-${option}`,
867
+ className: cx("theme-input mr-1", disabled && "disabled", missing && "invalid"),
868
+ name: field.name,
869
+ type: "radio",
870
+ value: option,
871
+ disabled,
872
+ onChange: (e) => {
873
+ handleChange(e.target.value);
874
+ }
875
+ }), option]
876
+ }, field.name + option))
877
+ })
1103
878
  });
1104
879
  }
1105
880
  //#endregion
@@ -1156,513 +931,719 @@ function RangeField(props) {
1156
931
  });
1157
932
  }
1158
933
  //#endregion
1159
- //#region src/form/utils/slurmDuration.ts
1160
- var WITH_DAYS = /^(\d+)-(\d{1,2})(?::(\d{1,2})(?::(\d{1,2}))?)?$/;
1161
- var WITHOUT_DAYS = /^(\d+):(\d{1,2}):(\d{1,2})$/;
1162
- function parseSlurmDuration(text) {
1163
- const trimmed = text.trim();
1164
- let days = 0;
1165
- let hours;
1166
- let minutes;
1167
- let seconds;
1168
- const withDays = trimmed.match(WITH_DAYS);
1169
- if (withDays) {
1170
- days = Number(withDays[1]);
1171
- hours = Number(withDays[2]);
1172
- minutes = Number(withDays[3] ?? 0);
1173
- seconds = Number(withDays[4] ?? 0);
1174
- if (hours > 23) return null;
1175
- } else {
1176
- const withoutDays = trimmed.match(WITHOUT_DAYS);
1177
- if (!withoutDays) return null;
1178
- hours = Number(withoutDays[1]);
1179
- minutes = Number(withoutDays[2]);
1180
- seconds = Number(withoutDays[3]);
1181
- }
1182
- if (minutes > 59 || seconds > 59) return null;
1183
- return days * 86400 + hours * 3600 + minutes * 60 + seconds;
1184
- }
1185
- function formatSecondsAsSlurmDuration(totalSeconds) {
1186
- if (!Number.isFinite(totalSeconds) || totalSeconds < 0) return "";
1187
- const parts = Duration.fromObject({ seconds: Math.floor(totalSeconds) }).shiftTo("days", "hours", "minutes", "seconds");
1188
- const pad = (n) => String(n).padStart(2, "0");
1189
- const time = `${pad(parts.hours)}:${pad(parts.minutes)}:${pad(parts.seconds)}`;
1190
- return parts.days > 0 ? `${parts.days}-${time}` : time;
1191
- }
1192
- var FLEX_MAX_DURATION_RANGE = {
1193
- min: 600,
1194
- max: 604800
1195
- };
1196
- var FLEX_WAIT_TIME_RANGE = {
1197
- min: 30,
1198
- max: 604800
1199
- };
1200
- var STANDARD_MAX_DURATION_RANGE = {
1201
- min: 30,
1202
- max: 10368e3
1203
- };
1204
- var DURATION_PATH = /^partitions\[\d+\]\.(maxDuration|flexStartWaitTime|suspendTime)$/;
1205
- function collectPartitionDurationIssues(partitions, invalidDurationPaths) {
1206
- const issues = new Set(invalidDurationPaths.filter((path) => DURATION_PATH.test(path)));
1207
- (partitions ?? []).forEach((partition, i) => {
1208
- const p = partition;
1209
- const check = (fieldName, range, required) => {
1210
- const raw = p?.[fieldName];
1211
- if (raw === void 0 || raw === null || raw === "") {
1212
- if (required) issues.add(`partitions[${i}].${fieldName}`);
1213
- return;
1214
- }
1215
- const value = Number(raw);
1216
- if (!Number.isFinite(value) || value < range.min || value > range.max) issues.add(`partitions[${i}].${fieldName}`);
1217
- };
1218
- if (p?.provisioningMode === "flex") {
1219
- check("maxDuration", FLEX_MAX_DURATION_RANGE, true);
1220
- check("flexStartWaitTime", FLEX_WAIT_TIME_RANGE, true);
1221
- } else check("maxDuration", STANDARD_MAX_DURATION_RANGE, false);
1222
- });
1223
- return [...issues];
1224
- }
1225
- //#endregion
1226
- //#region src/form/Fields/durationValidity.ts
1227
- var invalidPaths = /* @__PURE__ */ new Set();
1228
- function setDurationValidity(path, valid) {
1229
- if (valid) invalidPaths.delete(path);
1230
- else invalidPaths.add(path);
1231
- }
1232
- function getInvalidDurationPaths() {
1233
- return [...invalidPaths];
934
+ //#region src/form/Fields/StepField.tsx
935
+ function StepField(_props) {
936
+ return null;
1234
937
  }
1235
938
  //#endregion
1236
- //#region src/form/Fields/DurationField.tsx
1237
- var DEBOUNCE_MS = 500;
1238
- var isNumericString = (value) => typeof value === "string" && /^\d+$/.test(value.trim());
1239
- var toDisplay = (value) => {
1240
- if (typeof value === "number") return formatSecondsAsSlurmDuration(value);
1241
- if (isNumericString(value)) return formatSecondsAsSlurmDuration(Number(value));
1242
- return "";
1243
- };
1244
- var DurationField_default = memo(function DurationField({ field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty, currentValue, setFieldValue, setFieldTouched }) {
1245
- const { form: t } = useStrings();
1246
- const name = field.name;
1247
- const min = field.min !== void 0 ? Number(field.min) : Number.NEGATIVE_INFINITY;
1248
- const max = field.max !== void 0 ? Number(field.max) : Number.POSITIVE_INFINITY;
1249
- const disableValue = field.disableValue;
1250
- const hasDisableCheckbox = disableValue !== void 0 && field.disableLabel !== void 0;
1251
- const isSentinel = hasDisableCheckbox && currentValue !== "" && Number(currentValue) === disableValue;
1252
- const [text, setText] = useState(() => toDisplay(currentValue));
1253
- const focusedRef = useRef(false);
1254
- const debounceRef = useRef(null);
1255
- useEffect(() => {
1256
- if (isSentinel) {
1257
- if (typeof currentValue !== "number") setFieldValue?.(name, disableValue);
1258
- } else if (isNumericString(currentValue)) setFieldValue?.(name, Number(currentValue));
1259
- else if ((currentValue === void 0 || currentValue === null || currentValue === "") && typeof field.default === "number") setFieldValue?.(name, field.default);
1260
- }, []);
1261
- useEffect(() => {
1262
- if (!focusedRef.current) setText(toDisplay(currentValue));
1263
- }, [currentValue]);
1264
- const trimmed = text.trim();
1265
- const parsed = trimmed === "" ? null : parseSlurmDuration(trimmed);
1266
- let error = null;
1267
- if (!isSentinel && trimmed !== "") {
1268
- if (parsed === null) error = t.invalidDuration;
1269
- else if (parsed < min || parsed > max) error = Number.isFinite(max) ? t.durationOutOfRange(formatSecondsAsSlurmDuration(Math.max(min, 0)), formatSecondsAsSlurmDuration(max)) : t.durationBelowMin(formatSecondsAsSlurmDuration(Math.max(min, 0)));
1270
- }
1271
- useEffect(() => {
1272
- setDurationValidity(name, error === null);
1273
- }, [name, error]);
1274
- useEffect(() => () => {
1275
- setDurationValidity(name, true);
1276
- clearTimeout(debounceRef.current ?? void 0);
1277
- }, [name]);
1278
- const flushToFormik = (val) => {
1279
- setFieldValue?.(name, val);
1280
- onChange?.(val);
1281
- setFieldTouched?.(name, true);
1282
- };
1283
- const handleDisableToggle = (checked) => {
1284
- clearTimeout(debounceRef.current ?? void 0);
1285
- setFormDirty(true);
1286
- if (checked) {
1287
- setText("");
1288
- flushToFormik(disableValue);
1289
- } else flushToFormik(typeof field.default === "number" ? field.default : "");
1290
- };
1291
- const handleChange = (e) => {
1292
- const raw = e.target.value;
1293
- setText(raw);
1294
- setFormDirty(true);
1295
- clearTimeout(debounceRef.current ?? void 0);
1296
- const rawTrimmed = raw.trim();
1297
- const value = rawTrimmed === "" ? "" : parseSlurmDuration(rawTrimmed);
1298
- if (value === "" || typeof value === "number" && value >= min && value <= max) debounceRef.current = setTimeout(() => flushToFormik(value), DEBOUNCE_MS);
1299
- };
1300
- const handleFocus = () => {
1301
- focusedRef.current = true;
1302
- };
1303
- const handleBlur = () => {
1304
- focusedRef.current = false;
1305
- clearTimeout(debounceRef.current ?? void 0);
1306
- if (trimmed === "") {
1307
- setText("");
1308
- flushToFormik("");
1309
- } else if (parsed !== null && parsed >= min && parsed <= max) {
1310
- setText(formatSecondsAsSlurmDuration(parsed));
1311
- flushToFormik(parsed);
1312
- }
1313
- };
939
+ //#region src/form/Fields/StringField.tsx
940
+ var StringField_default = memo(function StringField(props) {
941
+ const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, currentValue } = props;
942
+ const value = field.prefillDefault ? currentValue : void 0;
1314
943
  return /* @__PURE__ */ jsx(FieldWrapper, {
1315
- optional: field.optional ?? false,
944
+ optional: field.optional,
1316
945
  label,
1317
946
  tooltipComponent,
947
+ description: field.description,
1318
948
  labelPosition,
1319
- spaceCompact: spaceCompact ?? false,
1320
- ...field.description !== void 0 && { description: field.description },
1321
- children: ({ id, describedBy }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
1322
- !isSentinel && /* @__PURE__ */ jsx("input", {
1323
- "data-1p-ignore": true,
1324
- className: cx("w-full", disabled && "disabled", (missing || error !== null) && "invalid"),
1325
- id,
1326
- "aria-describedby": describedBy,
1327
- name,
1328
- value: text,
1329
- type: "text",
1330
- "aria-required": field.optional ? void 0 : true,
1331
- disabled,
1332
- placeholder: field.placeholder ?? "DD-HH:MM:SS",
1333
- onChange: handleChange,
1334
- onFocus: handleFocus,
1335
- onBlur: handleBlur
1336
- }),
1337
- error && /* @__PURE__ */ jsx("div", {
1338
- className: "text-xs text-red-500 mt-1",
1339
- children: error
1340
- }),
1341
- hasDisableCheckbox && /* @__PURE__ */ jsxs("label", {
1342
- className: cx("flex items-center gap-2 mt-1", disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer"),
1343
- children: [/* @__PURE__ */ jsx("input", {
1344
- type: "checkbox",
1345
- checked: isSentinel,
1346
- disabled,
1347
- onChange: (e) => handleDisableToggle(e.target.checked),
1348
- className: cx("h-4 w-4 accent-(--theme-link)", disabled ? "cursor-not-allowed" : "cursor-pointer")
1349
- }), /* @__PURE__ */ jsx("span", {
1350
- className: "theme-text",
1351
- children: field.disableLabel
1352
- })]
1353
- })
1354
- ] })
949
+ spaceCompact,
950
+ children: /* @__PURE__ */ jsx(FormikCustomInput, {
951
+ invalid: missing,
952
+ name: field.name,
953
+ type: field.type,
954
+ lowercase: field.lowercase,
955
+ placeholder: field.type === "password" && field.placeholder === void 0 ? "•".repeat((field.default ?? "").toString().length > 0 ? 16 : 0) : getPlaceholder(field.placeholder, field.default),
956
+ disabled,
957
+ step: field.step,
958
+ min: field.min,
959
+ max: field.max,
960
+ minLength: field.minLength,
961
+ maxLength: field.maxLength,
962
+ sanitize: field.sanitize,
963
+ defaultValue: field.default,
964
+ value,
965
+ ...onChange && { onChange }
966
+ }, field.name)
1355
967
  });
1356
968
  });
1357
969
  //#endregion
1358
- //#region src/form/Fields/RadioField.tsx
1359
- function RadioField(props) {
1360
- const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty } = props;
1361
- const uid = useId();
1362
- const { setFieldValue, setFieldTouched } = useFormikContext();
1363
- const handleChange = (val) => {
1364
- if (disabled) return;
1365
- if (onChange) onChange(val);
1366
- setFieldTouched(field.name, true);
1367
- setFieldValue(field.name, val);
1368
- setFormDirty(true);
1369
- };
970
+ //#region src/form/Fields/TextAreaField.tsx
971
+ function TextAreaField(props) {
972
+ const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact } = props;
1370
973
  return /* @__PURE__ */ jsx(FieldWrapper, {
1371
974
  optional: field.optional,
1372
975
  label,
1373
976
  tooltipComponent,
977
+ description: field.description,
1374
978
  labelPosition,
1375
979
  spaceCompact,
1376
- children: ({ labelId, describedBy }) => /* @__PURE__ */ jsx("div", {
1377
- role: "radiogroup",
1378
- "aria-labelledby": labelId,
980
+ children: ({ id, describedBy }) => /* @__PURE__ */ jsx(Field, {
981
+ className: cx("theme-input h-[82px] w-full rounded leading-4 overflow-y-scroll", disabled && "disabled", missing && "border-red-600 focus:border-red-600 focus:ring-red-500"),
982
+ as: "textarea",
983
+ id,
1379
984
  "aria-describedby": describedBy,
1380
985
  "aria-required": field.optional ? void 0 : true,
1381
- className: cx("flex", field.optionLabelPosition === "top" ? "flex-col" : "flex-row"),
1382
- children: field.options?.map((option) => /* @__PURE__ */ jsxs("label", {
1383
- htmlFor: `${uid}-${option}`,
1384
- className: "capitalize",
1385
- children: [/* @__PURE__ */ jsx(Field, {
1386
- id: `${uid}-${option}`,
1387
- className: cx("theme-input mr-1", disabled && "disabled", missing && "invalid"),
1388
- name: field.name,
1389
- type: "radio",
1390
- value: option,
1391
- disabled,
1392
- onChange: (e) => {
1393
- handleChange(e.target.value);
1394
- }
1395
- }), option]
1396
- }, field.name + option))
1397
- })
986
+ name: field.name,
987
+ type: "search",
988
+ placeholder: getPlaceholder(field.placeholder, field.default),
989
+ disabled,
990
+ ...onChange && { onChange }
991
+ }, field.name)
1398
992
  });
1399
993
  }
1400
994
  //#endregion
1401
- //#region src/form/Fields/LabelField.tsx
1402
- function LabelField(props) {
1403
- const { field, label } = props;
1404
- return /* @__PURE__ */ jsx("div", {
1405
- className: "flex items-center w-full",
1406
- children: /* @__PURE__ */ jsx("h3", {
1407
- style: field.size ? {
1408
- fontSize: field.size.toString() + "px",
1409
- whiteSpace: "pre-wrap"
1410
- } : { whiteSpace: "pre-wrap" },
1411
- className: cx("relative", "text-md", field.bold !== false ? "font-bold" : ""),
1412
- children: field.text && field.text.toString().replace(/(\r)/g, "").replace(/(\\n)/g, "\n") || label
1413
- })
995
+ //#region src/form/fieldRegistry.tsx
996
+ var CORE_FIELD_COMPONENTS = {
997
+ string: StringField_default,
998
+ password: StringField_default,
999
+ number: NumberField,
1000
+ boolean: BooleanField,
1001
+ dropdown: DropdownField,
1002
+ textarea: TextAreaField,
1003
+ range: RangeField,
1004
+ duration: DurationField_default,
1005
+ radio: RadioField,
1006
+ label: LabelField,
1007
+ color: ColorField,
1008
+ "multi-dropdown": MultiDropdownField,
1009
+ "checkbox-group": CheckboxGroupField,
1010
+ object: ObjectField,
1011
+ step: StepField
1012
+ };
1013
+ var FieldRegistryContext = createContext({});
1014
+ /** Returns null for field types no registry knows. Per-form `fields` win over
1015
+ * the provider's `slots.formFields`, which win over the core registry. */
1016
+ function Registry(props) {
1017
+ const injected = useContext(FieldRegistryContext);
1018
+ const { formFields } = useSlots();
1019
+ const Component = injected[props.field.type] ?? formFields?.[props.field.type] ?? CORE_FIELD_COMPONENTS[props.field.type];
1020
+ if (!Component) return null;
1021
+ return /* @__PURE__ */ jsx(Suspense, {
1022
+ fallback: null,
1023
+ children: /* @__PURE__ */ jsx(Component, { ...props })
1414
1024
  });
1415
1025
  }
1416
1026
  //#endregion
1417
- //#region src/form/Fields/ColorField.tsx
1418
- function ColorField(props) {
1419
- const { field, label, labelPosition, onChange, tooltipComponent, spaceCompact } = props;
1420
- const [fieldState] = useField(field.name);
1421
- const { setFieldValue, setFieldTouched } = useFormikContext();
1422
- const handleChange = (e) => {
1423
- const val = e.target.value;
1424
- if (onChange) onChange(val);
1425
- setFieldTouched(field.name, true);
1426
- setFieldValue(field.name, val);
1427
- };
1428
- return /* @__PURE__ */ jsx(FieldWrapper, {
1429
- optional: field.optional,
1430
- label,
1431
- tooltipComponent,
1432
- labelPosition,
1433
- spaceCompact,
1434
- children: ({ id }) => /* @__PURE__ */ jsx(Field, {
1435
- className: "theme-input h-8 w-18 rounded border border-neutral-400 placeholder:text-xs",
1436
- id,
1437
- name: field.name,
1438
- value: fieldState.value || "#fbbf24",
1439
- type: "color",
1440
- "aria-required": field.optional ? void 0 : true,
1441
- onChange: handleChange
1442
- })
1027
+ //#region src/form/useParsedOpts.ts
1028
+ var EMPTY_VARS = {};
1029
+ var EMPTY_KEYS = [];
1030
+ /**
1031
+ * useMemo won't work here — Formik values changes on every keystroke, so this hook extracts which inputs.* keys the schema references and re-parses only when those change. Defaults `userVars`/`secretKeys` to empty — callers in React scope should pass them from `useUserVars()` so the parser leaves `${{ var.SECRET }}` literal during template eval (otherwise the recursive substitution turns `"https://${{ var.PAT }}@host/"` into `"https://@host/"`).
1032
+ */
1033
+ function useParsedOpts(rawOptions, values, organizationVariables, userVars = EMPTY_VARS, secretKeys = EMPTY_KEYS, remoteVars = EMPTY_VARS) {
1034
+ const engine = useWorkflowEngine();
1035
+ const c = useRef({
1036
+ rawOptions: null,
1037
+ orgVars: void 0,
1038
+ userVars: null,
1039
+ secretKeys: null,
1040
+ remoteVars: null,
1041
+ converted: {},
1042
+ inputDeps: /* @__PURE__ */ new Set(),
1043
+ hasExpressions: false,
1044
+ relevantSnapshot: {},
1045
+ result: null
1046
+ }).current;
1047
+ if (rawOptions !== c.rawOptions) {
1048
+ c.rawOptions = rawOptions;
1049
+ c.converted = engine.convertInputs(rawOptions);
1050
+ const { inputDeps, hasExpressions } = engine.inputDependencies(c.converted);
1051
+ c.inputDeps = inputDeps;
1052
+ c.hasExpressions = hasExpressions;
1053
+ c.result = null;
1054
+ }
1055
+ if (organizationVariables !== c.orgVars || userVars !== c.userVars || secretKeys !== c.secretKeys || remoteVars !== c.remoteVars) {
1056
+ c.orgVars = organizationVariables;
1057
+ c.userVars = userVars;
1058
+ c.secretKeys = secretKeys;
1059
+ c.remoteVars = remoteVars;
1060
+ c.result = null;
1061
+ }
1062
+ if (c.result !== null) {
1063
+ if (c.inputDeps.size === 0) return c.result;
1064
+ let inputsChanged = false;
1065
+ for (const key of c.inputDeps) if (!deepEqual(values[key], c.relevantSnapshot[key])) {
1066
+ inputsChanged = true;
1067
+ break;
1068
+ }
1069
+ if (!inputsChanged) return c.result;
1070
+ }
1071
+ const parsed = engine.evaluate({
1072
+ inputs: values,
1073
+ obj: c.converted,
1074
+ orgVars: organizationVariables,
1075
+ userVars,
1076
+ secretKeys,
1077
+ remoteVars
1443
1078
  });
1079
+ c.result = parsed;
1080
+ c.relevantSnapshot = {};
1081
+ for (const key of c.inputDeps) c.relevantSnapshot[key] = values[key];
1082
+ return parsed;
1444
1083
  }
1445
1084
  //#endregion
1446
- //#region src/form/Fields/MultiDropdownField.tsx
1447
- function MultiDropdownField({ field, label, tooltipComponent, labelPosition, spaceCompact, disabled, missing, setFormDirty, onChange }) {
1448
- let parentValue = field.parentValue;
1449
- let options = field.options;
1450
- if (field.depends_on) {
1451
- parentValue = field.depends_on;
1452
- if (!Array.isArray(field.options)) options = field.options[parentValue] || [];
1453
- }
1454
- return /* @__PURE__ */ jsx(FieldWrapper, {
1455
- optional: field.optional,
1456
- label,
1457
- tooltipComponent,
1458
- labelPosition,
1459
- spaceCompact,
1460
- children: ({ id }) => disabled ? /* @__PURE__ */ jsx(Field, {
1461
- className: "theme-input h-7 w-full rounded border placeholder:text-xs",
1462
- id,
1463
- type: "search",
1464
- name: field.name,
1465
- disabled: true,
1466
- onChange
1467
- }, field.name) : /* @__PURE__ */ jsx(MultiSelectionDropdown, {
1468
- className: "themee-input h-7 w-full rounded border flex items-center px-3 py-2",
1469
- name: field.name,
1470
- label: field.label || label,
1471
- options: Array.isArray(options) ? options : [],
1472
- setFormDirty,
1473
- parentValue,
1474
- onChange,
1475
- secondaryField: typeof field.secondaryField === "string" ? field.secondaryField : void 0,
1476
- invalid: missing
1477
- }, field.name)
1085
+ //#region src/form/usePrevious.ts
1086
+ function usePrevious(value) {
1087
+ const ref = useRef(void 0);
1088
+ useEffect(() => {
1089
+ ref.current = value;
1478
1090
  });
1091
+ return ref.current;
1479
1092
  }
1480
1093
  //#endregion
1481
- //#region src/form/Fields/CheckboxGroupField.tsx
1482
- function impliedFrom(values, implies = {}) {
1483
- const result = /* @__PURE__ */ new Set();
1484
- for (const value of values) for (const target of implies[value] ?? []) result.add(target);
1485
- return result;
1094
+ //#region src/form/utils/getValueUsingPath.ts
1095
+ function getValueUsingPath(obj, path) {
1096
+ if (typeof path !== "string" || !path) return;
1097
+ const keys = path.split(".");
1098
+ let current = obj;
1099
+ for (const key of keys) {
1100
+ if (!current || typeof current !== "object") return;
1101
+ const container = current;
1102
+ if (key.includes("[")) {
1103
+ const [arrayKey = "", indexStr = ""] = key.split("[");
1104
+ const index = parseInt(indexStr.slice(0, -1), 10);
1105
+ const list = container[arrayKey];
1106
+ if (!Array.isArray(list) || isNaN(index)) return;
1107
+ current = list[index];
1108
+ } else {
1109
+ if (!(key in container)) return;
1110
+ current = container[key];
1111
+ }
1112
+ }
1113
+ return current;
1486
1114
  }
1487
- function union(manual, forced) {
1488
- return [...manual, ...[...forced].filter((v) => !manual.includes(v))];
1115
+ //#endregion
1116
+ //#region src/form/utils/checkShowIfObject.ts
1117
+ function checkShowIfObject(showIfObject, formValues, index) {
1118
+ const valueList = [];
1119
+ for (let [key, value] of Object.entries(showIfObject)) {
1120
+ if (index !== void 0) key = key.replace("[index]", `[${index}]`);
1121
+ const dependsOnValue = getValueUsingPath(formValues, key);
1122
+ if (typeof value === "boolean" || typeof value === "string" || typeof value === "number") valueList.push(dependsOnValue !== null && dependsOnValue !== void 0 && dependsOnValue.toString() === value.toString());
1123
+ else if (Array.isArray(value)) valueList.push(value.includes(dependsOnValue));
1124
+ }
1125
+ return valueList;
1489
1126
  }
1490
- function CheckboxGroupField({ field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty }) {
1491
- const [fieldState] = useField(field.name);
1492
- const { setFieldValue, setFieldTouched } = useFormikContext();
1493
- const implies = field.implies;
1494
- const [manual, setManual] = useState(() => {
1495
- const saved = fieldState.value || [];
1496
- const impliedBySaved = impliedFrom(saved, implies);
1497
- return saved.filter((v) => !impliedBySaved.has(v));
1127
+ //#endregion
1128
+ //#region src/form/utils/getParentValue.ts
1129
+ function getParentValue(index, targetField, values, dependentPath, field) {
1130
+ let parentValue;
1131
+ if (typeof targetField === "object" && targetField !== null && !Array.isArray(targetField)) parentValue = !checkShowIfObject(targetField, values, index).includes(false);
1132
+ else if (field.depends_on) {
1133
+ dependentPath = field.depends_on;
1134
+ if (index !== void 0) dependentPath = field.depends_on.replace("[index]", `[${index}]`);
1135
+ const dependsOnValue = getValueUsingPath(values, dependentPath);
1136
+ const dependsOnText = dependsOnValue === null || dependsOnValue === void 0 ? void 0 : String(dependsOnValue);
1137
+ if (Array.isArray(targetField)) parentValue = targetField.includes(dependsOnValue) || targetField.includes(dependsOnText);
1138
+ else if (typeof targetField === "string") parentValue = dependsOnText !== void 0 && targetField.includes(dependsOnText);
1139
+ else parentValue = targetField === dependsOnValue;
1140
+ } else {
1141
+ dependentPath = typeof targetField === "string" ? targetField : "";
1142
+ if (index !== void 0 && typeof targetField === "string") dependentPath = targetField.replace("[index]", `[${index}]`);
1143
+ parentValue = getValueUsingPath(values, dependentPath);
1144
+ }
1145
+ return parentValue;
1146
+ }
1147
+ //#endregion
1148
+ //#region src/form/Wizard/utils.ts
1149
+ function isWizardMeta(value) {
1150
+ return typeof value === "object" && value !== null && "wizard" in value;
1151
+ }
1152
+ function isStepField(field) {
1153
+ return typeof field === "object" && field !== null && "type" in field && field.type === "step";
1154
+ }
1155
+ function parseWizardConfig(options) {
1156
+ const meta = options["$meta"];
1157
+ const wizardConfig = isWizardMeta(meta) ? meta.wizard : void 0;
1158
+ if (!wizardConfig || wizardConfig.mode !== "wizard") return null;
1159
+ const steps = {};
1160
+ const stepOrder = [];
1161
+ const otherFields = {};
1162
+ Object.entries(options).forEach(([key, field]) => {
1163
+ if (key === "$meta") return;
1164
+ if (isStepField(field)) {
1165
+ steps[key] = field;
1166
+ stepOrder.push(key);
1167
+ } else otherFields[key] = field;
1498
1168
  });
1499
- const forced = impliedFrom(manual, implies);
1500
- const handleToggle = (value) => {
1501
- if (disabled) return;
1502
- const nextManual = manual.includes(value) ? manual.filter((v) => v !== value) : [...manual, value];
1503
- const next = union(nextManual, impliedFrom(nextManual, implies));
1504
- setManual(nextManual);
1505
- setFieldTouched(field.name, true);
1506
- setFieldValue(field.name, next);
1507
- if (onChange) onChange(next);
1508
- setFormDirty(true);
1169
+ if (stepOrder.length === 0) {
1170
+ console.warn("Wizard mode enabled but no steps found");
1171
+ return null;
1172
+ }
1173
+ return {
1174
+ config: wizardConfig,
1175
+ steps,
1176
+ stepOrder,
1177
+ otherFields
1509
1178
  };
1510
- return /* @__PURE__ */ jsx(FieldWrapper, {
1511
- optional: field.optional,
1512
- label,
1513
- tooltipComponent,
1514
- labelPosition,
1515
- spaceCompact,
1516
- children: ({ labelId, describedBy }) => /* @__PURE__ */ jsx("fieldset", {
1517
- "aria-labelledby": labelId,
1518
- "aria-describedby": describedBy,
1519
- className: "flex min-w-0 max-w-lg flex-col gap-1",
1520
- children: field.options?.map((option) => {
1521
- const locked = forced.has(option.value);
1522
- const optionDisabled = disabled || locked;
1523
- return /* @__PURE__ */ jsxs("label", {
1524
- className: cx("-mx-2 flex items-start gap-2.5 rounded-md px-2 py-1.5 transition-colors", optionDisabled ? "cursor-not-allowed opacity-60" : "cursor-pointer hover:bg-(--theme-hover) has-[:focus-visible]:bg-(--theme-hover)"),
1525
- children: [/* @__PURE__ */ jsx("input", {
1526
- type: "checkbox",
1527
- checked: manual.includes(option.value) || locked,
1528
- disabled: optionDisabled,
1529
- onChange: () => handleToggle(option.value),
1530
- className: cx("mt-px h-4 w-4 rounded accent-(--theme-link)", optionDisabled ? "cursor-not-allowed" : "cursor-pointer", missing && "invalid")
1531
- }), /* @__PURE__ */ jsxs("div", {
1532
- className: "flex min-w-0 flex-col",
1533
- children: [/* @__PURE__ */ jsx("span", {
1534
- className: "font-medium leading-tight theme-text",
1535
- children: option.label
1536
- }), option.description && /* @__PURE__ */ jsx("span", {
1537
- className: "mt-0.5 text-xs leading-snug theme-muted-text",
1538
- children: option.description
1539
- })]
1540
- })]
1541
- }, field.name + option.value);
1542
- })
1179
+ }
1180
+ //#endregion
1181
+ //#region src/form/Wizard/useWizardState.ts
1182
+ function useWizardState({ stepOrder, steps, validateStep, initialStep }) {
1183
+ const firstStep = initialStep || stepOrder[0] || "";
1184
+ const [currentStep, setCurrentStep] = useState(firstStep);
1185
+ const [visitedSteps, setVisitedSteps] = useState(/* @__PURE__ */ new Set([firstStep]));
1186
+ const [invalidSteps, setInvalidSteps] = useState(/* @__PURE__ */ new Set());
1187
+ const currentStepIndex = stepOrder.indexOf(currentStep);
1188
+ const isLastStep = currentStepIndex === stepOrder.length - 1;
1189
+ const canGoBack = currentStepIndex > 0;
1190
+ /**
1191
+ * Mark step as visited
1192
+ */
1193
+ const markStepVisited = useCallback((stepKey) => {
1194
+ setVisitedSteps((prev) => /* @__PURE__ */ new Set([...prev, stepKey]));
1195
+ }, []);
1196
+ /**
1197
+ * Mark step as invalid
1198
+ */
1199
+ const markStepInvalid = useCallback((stepKey, invalid) => {
1200
+ setInvalidSteps((prev) => {
1201
+ const next = new Set(prev);
1202
+ if (invalid) next.add(stepKey);
1203
+ else next.delete(stepKey);
1204
+ return next;
1205
+ });
1206
+ }, []);
1207
+ const goToNext = useCallback(async () => {
1208
+ if (isLastStep) return false;
1209
+ if (steps[currentStep]?.validateOnNext !== false) {
1210
+ if (!await validateStep(currentStep)) {
1211
+ markStepInvalid(currentStep, true);
1212
+ return false;
1213
+ }
1214
+ }
1215
+ markStepInvalid(currentStep, false);
1216
+ const nextStepKey = stepOrder[currentStepIndex + 1];
1217
+ setCurrentStep(nextStepKey ?? "");
1218
+ markStepVisited(nextStepKey ?? "");
1219
+ return true;
1220
+ }, [
1221
+ currentStep,
1222
+ currentStepIndex,
1223
+ isLastStep,
1224
+ steps,
1225
+ stepOrder,
1226
+ validateStep,
1227
+ markStepInvalid,
1228
+ markStepVisited
1229
+ ]);
1230
+ const goToPrevious = useCallback(() => {
1231
+ if (!canGoBack) return;
1232
+ const prevStepKey = stepOrder[currentStepIndex - 1];
1233
+ setCurrentStep(prevStepKey ?? "");
1234
+ }, [
1235
+ canGoBack,
1236
+ currentStepIndex,
1237
+ stepOrder
1238
+ ]);
1239
+ const jumpToStep = useCallback((stepKey) => {
1240
+ if (!stepOrder.includes(stepKey)) return false;
1241
+ if (!visitedSteps.has(stepKey) && stepKey !== currentStep) return false;
1242
+ setCurrentStep(stepKey);
1243
+ return true;
1244
+ }, [
1245
+ stepOrder,
1246
+ visitedSteps,
1247
+ currentStep
1248
+ ]);
1249
+ /**
1250
+ * Reset wizard to initial state
1251
+ */
1252
+ const reset = useCallback(() => {
1253
+ setCurrentStep(firstStep);
1254
+ setVisitedSteps(/* @__PURE__ */ new Set([firstStep]));
1255
+ setInvalidSteps(/* @__PURE__ */ new Set());
1256
+ }, [firstStep]);
1257
+ /**
1258
+ * Get current wizard state
1259
+ */
1260
+ const getState = useCallback(() => {
1261
+ return {
1262
+ currentStep,
1263
+ visitedSteps,
1264
+ invalidSteps,
1265
+ stepOrder,
1266
+ totalSteps: stepOrder.length,
1267
+ isLastStep,
1268
+ canProceedToNext: !isLastStep,
1269
+ canGoBack
1270
+ };
1271
+ }, [
1272
+ currentStep,
1273
+ visitedSteps,
1274
+ invalidSteps,
1275
+ stepOrder,
1276
+ isLastStep,
1277
+ canGoBack
1278
+ ]);
1279
+ return {
1280
+ currentStep,
1281
+ visitedSteps,
1282
+ invalidSteps,
1283
+ isLastStep,
1284
+ canGoBack,
1285
+ currentStepIndex,
1286
+ totalSteps: stepOrder.length,
1287
+ goToNext,
1288
+ goToPrevious,
1289
+ jumpToStep,
1290
+ reset,
1291
+ markStepVisited,
1292
+ markStepInvalid,
1293
+ getState
1294
+ };
1295
+ }
1296
+ //#endregion
1297
+ //#region src/form/Wizard/WizardNavigation.tsx
1298
+ function WizardNavigation({ currentStep, stepOrder, canGoToNext, canGoBack, isCurrentStepValid, nextLabel, prevLabel, submitLabel = "Submit", onNext, onPrevious, onSubmit }) {
1299
+ const [isLoading, setIsLoading] = useState(false);
1300
+ const [error, setError] = useState(null);
1301
+ const isLastStep = stepOrder.indexOf(currentStep) === stepOrder.length - 1;
1302
+ const handleNext = async () => {
1303
+ setIsLoading(true);
1304
+ setError(null);
1305
+ try {
1306
+ if (!await onNext()) setError("Please fix errors before proceeding");
1307
+ } catch (err) {
1308
+ setError(err instanceof Error ? err.message : "An error occurred");
1309
+ } finally {
1310
+ setIsLoading(false);
1311
+ }
1312
+ };
1313
+ const handleSubmit = async () => {
1314
+ setIsLoading(true);
1315
+ setError(null);
1316
+ try {
1317
+ await onSubmit();
1318
+ } catch (err) {
1319
+ setError(err instanceof Error ? err.message : "An error occurred");
1320
+ } finally {
1321
+ setIsLoading(false);
1322
+ }
1323
+ };
1324
+ return /* @__PURE__ */ jsxs("div", { children: [error && /* @__PURE__ */ jsx("div", {
1325
+ className: "mb-4 p-3 bg-red-50 border border-red-200 rounded text-sm text-red-700",
1326
+ children: error
1327
+ }), /* @__PURE__ */ jsxs("div", {
1328
+ className: "flex gap-3 justify-between pt-4",
1329
+ children: [/* @__PURE__ */ jsx("button", {
1330
+ type: "button",
1331
+ onClick: onPrevious,
1332
+ disabled: !canGoBack || isLoading,
1333
+ className: "px-6 py-3 rounded-lg font-medium transition-all duration-200 border flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed",
1334
+ style: {
1335
+ color: "var(--theme-app)",
1336
+ borderColor: "var(--theme-border)",
1337
+ backgroundColor: "var(--theme-app-bg)"
1338
+ },
1339
+ onMouseEnter: (e) => {
1340
+ if (!(!canGoBack || isLoading)) e.currentTarget.style.backgroundColor = "var(--theme-hover)";
1341
+ },
1342
+ onMouseLeave: (e) => {
1343
+ e.currentTarget.style.backgroundColor = "var(--theme-app-bg)";
1344
+ },
1345
+ "aria-label": "Go to previous step",
1346
+ children: prevLabel || "← Previous"
1347
+ }), isLastStep ? /* @__PURE__ */ jsxs("button", {
1348
+ type: "button",
1349
+ onClick: handleSubmit,
1350
+ disabled: !isCurrentStepValid || isLoading,
1351
+ className: "px-8 py-3 rounded-lg font-medium transition-all duration-200 text-white flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-md hover:shadow-lg",
1352
+ style: { backgroundColor: "var(--theme-element)" },
1353
+ onMouseEnter: (e) => {
1354
+ if (!(!isCurrentStepValid || isLoading)) e.currentTarget.style.filter = "brightness(0.9)";
1355
+ },
1356
+ onMouseLeave: (e) => {
1357
+ e.currentTarget.style.filter = "brightness(1)";
1358
+ },
1359
+ "aria-label": submitLabel,
1360
+ children: [isLoading && /* @__PURE__ */ jsx(Loader, {
1361
+ size: 16,
1362
+ full: false
1363
+ }), submitLabel]
1364
+ }) : /* @__PURE__ */ jsxs("button", {
1365
+ type: "button",
1366
+ onClick: handleNext,
1367
+ disabled: !canGoToNext || isLoading,
1368
+ className: "px-8 py-3 rounded-lg font-medium transition-all duration-200 text-white flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed shadow-md hover:shadow-lg",
1369
+ style: { backgroundColor: "var(--theme-element)" },
1370
+ onMouseEnter: (e) => {
1371
+ if (!(!canGoToNext || isLoading)) e.currentTarget.style.filter = "brightness(0.9)";
1372
+ },
1373
+ onMouseLeave: (e) => {
1374
+ e.currentTarget.style.filter = "brightness(1)";
1375
+ },
1376
+ "aria-label": "Go to next step",
1377
+ children: [isLoading && /* @__PURE__ */ jsx(Loader, {
1378
+ size: 16,
1379
+ full: false
1380
+ }), nextLabel || "Next →"]
1381
+ })]
1382
+ })] });
1383
+ }
1384
+ //#endregion
1385
+ //#region src/form/Wizard/WizardStepContent.tsx
1386
+ function WizardStepContent({ currentStep, stepConfig, flatten = true, values, onValuesChange, setFormDirty, setFieldValue, setFieldTouched, ...fieldsProps }) {
1387
+ if (!stepConfig?.options) return null;
1388
+ return /* @__PURE__ */ jsxs("div", {
1389
+ className: "mb-4 w-full",
1390
+ children: [
1391
+ stepConfig.title && /* @__PURE__ */ jsx("h2", {
1392
+ className: "text-2xl font-bold mb-2",
1393
+ style: { color: "var(--theme-app)" },
1394
+ children: stepConfig.title
1395
+ }),
1396
+ stepConfig.description && /* @__PURE__ */ jsx("p", {
1397
+ className: "mb-4 leading-relaxed",
1398
+ style: { color: "var(--theme-muted-text-color)" },
1399
+ children: stepConfig.description
1400
+ }),
1401
+ /* @__PURE__ */ jsx(FieldsFromOptions, {
1402
+ options: stepConfig.options,
1403
+ values,
1404
+ setFormDirty,
1405
+ setFieldValue,
1406
+ setFieldTouched,
1407
+ ...flatten === false ? { parentInfo: {
1408
+ parentName: currentStep,
1409
+ fieldNamePrefix: `${currentStep}.`
1410
+ } } : {},
1411
+ ...fieldsProps
1412
+ }, currentStep)
1413
+ ]
1414
+ });
1415
+ }
1416
+ //#endregion
1417
+ //#region src/form/Wizard/WizardStepIndicator.tsx
1418
+ function WizardStepIndicator({ stepOrder, currentStep, steps, visitedSteps, invalidSteps, onStepClick, allowJump = false, hideStepNumbers = false }) {
1419
+ const currentIndex = useMemo(() => stepOrder.indexOf(currentStep), [stepOrder, currentStep]);
1420
+ const getStepStatus = (stepKey, index) => {
1421
+ if (stepKey === currentStep) return "active";
1422
+ if (index < currentIndex) return "completed";
1423
+ return "upcoming";
1424
+ };
1425
+ const handleStepClick = (stepKey) => {
1426
+ if (!allowJump || !visitedSteps.has(stepKey)) return;
1427
+ onStepClick?.(stepKey);
1428
+ };
1429
+ const isClickable = (stepKey) => {
1430
+ return allowJump && visitedSteps.has(stepKey) && stepKey !== currentStep;
1431
+ };
1432
+ return /* @__PURE__ */ jsx("div", {
1433
+ className: "flex items-start justify-between w-full mb-2 gap-4 py-2",
1434
+ children: stepOrder.map((stepKey, index) => {
1435
+ const stepConfig = steps[stepKey];
1436
+ const status = getStepStatus(stepKey, index);
1437
+ const isInvalid = invalidSteps.has(stepKey);
1438
+ const isClickable_ = isClickable(stepKey);
1439
+ const isDotActive = status === "active";
1440
+ const isDotCompleted = status === "completed";
1441
+ const isDotUpcoming = status === "upcoming";
1442
+ return /* @__PURE__ */ jsxs("div", {
1443
+ className: "flex flex-col items-center flex-1 relative",
1444
+ children: [
1445
+ /* @__PURE__ */ jsxs("button", {
1446
+ type: "button",
1447
+ onClick: () => handleStepClick(stepKey),
1448
+ disabled: !isClickable_,
1449
+ className: cx("w-12 h-12 rounded-full border-3 flex items-center justify-center font-bold text-base transition-all duration-300 flex-shrink-0 relative", {
1450
+ "cursor-pointer": isClickable_,
1451
+ "cursor-not-allowed": !isClickable_
1452
+ }, {
1453
+ "text-white": isDotActive,
1454
+ "bg-green-500 border-green-600 text-white shadow-md": isDotCompleted && !isInvalid,
1455
+ "bg-(--theme-border) theme-border theme-muted-text shadow-sm": isDotUpcoming && !isInvalid,
1456
+ "bg-(--theme-muted-panel-bg) theme-border theme-muted-text shadow-sm": !isDotActive && !isDotCompleted && !isDotUpcoming && !isInvalid,
1457
+ "bg-red-50 border-red-500 text-red-600 shadow-sm": isInvalid && status !== "active"
1458
+ }),
1459
+ style: isDotActive ? {
1460
+ backgroundColor: "var(--theme-element)",
1461
+ borderColor: "var(--theme-element)",
1462
+ color: "var(--theme-element-text)",
1463
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
1464
+ zIndex: 10
1465
+ } : { zIndex: 10 },
1466
+ "aria-label": `Step ${index + 1}: ${stepConfig?.title}`,
1467
+ title: stepConfig?.title,
1468
+ children: [!hideStepNumbers && /* @__PURE__ */ jsx("span", {
1469
+ className: cx({ hidden: status === "completed" }),
1470
+ children: index + 1
1471
+ }), status === "completed" && /* @__PURE__ */ jsx("svg", {
1472
+ "aria-hidden": "true",
1473
+ className: "w-4 h-4",
1474
+ fill: "none",
1475
+ stroke: "currentColor",
1476
+ viewBox: "0 0 24 24",
1477
+ children: /* @__PURE__ */ jsx("path", {
1478
+ strokeLinecap: "round",
1479
+ strokeLinejoin: "round",
1480
+ strokeWidth: 3,
1481
+ d: "M5 13l4 4L19 7"
1482
+ })
1483
+ })]
1484
+ }),
1485
+ index < stepOrder.length - 1 && /* @__PURE__ */ jsx("div", {
1486
+ className: cx("absolute h-1 transition-all duration-300", {
1487
+ "bg-green-500": index < currentIndex,
1488
+ "bg-(--theme-border)": index >= currentIndex,
1489
+ "opacity-25": index >= currentIndex
1490
+ }),
1491
+ style: {
1492
+ top: "24px",
1493
+ left: "calc(50% + 24px)",
1494
+ right: "-50%",
1495
+ boxShadow: index < currentIndex ? "0 1px 2px rgba(34, 197, 94, 0.2)" : "none"
1496
+ },
1497
+ "aria-hidden": "true"
1498
+ }),
1499
+ /* @__PURE__ */ jsxs("div", {
1500
+ className: "mt-2 text-center",
1501
+ children: [/* @__PURE__ */ jsx("div", {
1502
+ className: cx("text-sm text-center transition-colors duration-300 max-w-30", {
1503
+ "font-bold text-green-600": isDotCompleted && !isInvalid,
1504
+ "font-bold text-red-600": isInvalid && status !== "active",
1505
+ "font-semibold theme-muted-text": !isDotActive && !isDotCompleted && !isInvalid,
1506
+ "font-bold text-white": isDotActive
1507
+ }),
1508
+ style: isDotActive ? { color: "var(--theme-element)" } : {},
1509
+ children: stepConfig?.title
1510
+ }), stepConfig?.description && /* @__PURE__ */ jsx("div", {
1511
+ className: "text-xs theme-muted-text mt-1 text-center max-w-30",
1512
+ children: stepConfig.description
1513
+ })]
1514
+ })
1515
+ ]
1516
+ }, stepKey);
1543
1517
  })
1544
1518
  });
1545
1519
  }
1546
1520
  //#endregion
1547
- //#region src/form/Fields/ObjectField.tsx
1548
- function ObjectField({ field, label, tooltipComponent, missingFields, setFormDirty, workflowForm, labelPosition, spaceCompact }) {
1549
- const [open, setOpen] = useState(field.collapsed !== void 0 ? !field.collapsed : true);
1550
- const { values, setFieldValue, setFieldTouched } = useFormikContext();
1551
- const meta = resolveMetaOverrides(field.options, labelPosition, spaceCompact ?? false);
1552
- const HeaderElement = field.noCollapse ? React.Fragment : ControlledCollapsiblePanel;
1521
+ //#region src/form/Wizard/WizardContainer.tsx
1522
+ function hasNestedError(stepErrors, fieldName) {
1523
+ return typeof stepErrors === "object" && stepErrors !== null && fieldName in stepErrors;
1524
+ }
1525
+ function WizardContainer({ wizardConfig, values, onChange, onSubmit, className = "", labelPosition = "left", missingFields = [], spaceCompact = false, workflowForm = false, setFieldValue, setFieldTouched }) {
1526
+ const { validateForm, setTouched, touched } = useFormikContext();
1527
+ const { config, steps, stepOrder } = wizardConfig;
1528
+ const { currentStep, visitedSteps, invalidSteps, isLastStep, canGoBack, goToNext, goToPrevious, jumpToStep } = useWizardState({
1529
+ stepOrder,
1530
+ steps,
1531
+ validateStep: useCallback(async (stepKey) => {
1532
+ const stepConfig = steps[stepKey];
1533
+ if (!stepConfig?.options) return true;
1534
+ const stepFieldNames = Object.keys(stepConfig.options);
1535
+ const shouldFlatten = config.flatten !== false;
1536
+ const errors = await validateForm();
1537
+ const hasStepErrors = shouldFlatten ? stepFieldNames.some((fieldName) => errors[fieldName]) : stepFieldNames.some((fieldName) => hasNestedError(errors[stepKey], fieldName));
1538
+ if (hasStepErrors) {
1539
+ const touchedFields = stepFieldNames.reduce((acc, fieldName) => {
1540
+ acc[fieldName] = true;
1541
+ return acc;
1542
+ }, {});
1543
+ if (shouldFlatten) setTouched({
1544
+ ...touched,
1545
+ ...touchedFields
1546
+ }, false);
1547
+ else setTouched({
1548
+ ...touched,
1549
+ [stepKey]: touchedFields
1550
+ }, false);
1551
+ }
1552
+ return !hasStepErrors;
1553
+ }, [
1554
+ steps,
1555
+ config,
1556
+ validateForm,
1557
+ setTouched,
1558
+ touched
1559
+ ])
1560
+ });
1561
+ const currentStepConfig = steps[currentStep];
1562
+ const config_ = config.navigation || {};
1563
+ const handleGoToNext = useCallback(async () => {
1564
+ const success = await goToNext();
1565
+ if (success) onChange?.(values);
1566
+ return success;
1567
+ }, [
1568
+ goToNext,
1569
+ values,
1570
+ onChange
1571
+ ]);
1572
+ const handleJumpToStep = useCallback((stepKey) => {
1573
+ if (!config_.allowJump || !visitedSteps.has(stepKey)) return false;
1574
+ return jumpToStep(stepKey);
1575
+ }, [
1576
+ jumpToStep,
1577
+ config_.allowJump,
1578
+ visitedSteps
1579
+ ]);
1580
+ const handleSubmit = useCallback(async () => {
1581
+ if (!isLastStep) return;
1582
+ const errors = await validateForm();
1583
+ if (Object.keys(errors).length > 0) {
1584
+ const touchedFields = Object.keys(values).reduce((acc, fieldName) => {
1585
+ acc[fieldName] = true;
1586
+ return acc;
1587
+ }, {});
1588
+ setTouched({
1589
+ ...touched,
1590
+ ...touchedFields
1591
+ }, false);
1592
+ throw new Error("Please fix errors before submitting");
1593
+ }
1594
+ await onSubmit?.(values);
1595
+ }, [
1596
+ isLastStep,
1597
+ validateForm,
1598
+ values,
1599
+ setTouched,
1600
+ touched,
1601
+ onSubmit
1602
+ ]);
1553
1603
  return /* @__PURE__ */ jsxs("div", {
1554
- className: "flex flex-col w-full",
1555
- children: [field.noCollapse && /* @__PURE__ */ jsx("div", {
1556
- className: "flex items-center w-full mb-[15px]",
1557
- children: /* @__PURE__ */ jsxs("h3", {
1558
- className: "flex items-center gap-1 text-md font-bold",
1559
- children: [
1560
- label,
1561
- " ",
1562
- tooltipComponent
1563
- ]
1564
- })
1565
- }), /* @__PURE__ */ jsx(HeaderElement, {
1566
- open,
1567
- setOpen,
1568
- title: /* @__PURE__ */ jsx(GroupHeader, {
1569
- open,
1570
- setOpen,
1571
- title: field.label || label
1604
+ className,
1605
+ children: [
1606
+ config_.showSteps !== false && /* @__PURE__ */ jsx(WizardStepIndicator, {
1607
+ stepOrder,
1608
+ currentStep,
1609
+ steps,
1610
+ visitedSteps,
1611
+ invalidSteps,
1612
+ onStepClick: handleJumpToStep,
1613
+ allowJump: config_.allowJump,
1614
+ hideStepNumbers: config_.hideStepNumbers
1572
1615
  }),
1573
- children: /* @__PURE__ */ jsx("div", {
1574
- className: "w-full",
1575
- children: /* @__PURE__ */ jsx(FieldsFromOptions, {
1576
- options: field.options,
1616
+ currentStepConfig && /* @__PURE__ */ jsx("div", {
1617
+ className: "mb-4",
1618
+ children: /* @__PURE__ */ jsx(WizardStepContent, {
1619
+ currentStep,
1620
+ stepConfig: currentStepConfig,
1621
+ flatten: config.flatten,
1577
1622
  values,
1578
- setFormDirty,
1579
- setFieldValue,
1580
- setFieldTouched,
1623
+ onValuesChange: onChange,
1624
+ labelPosition,
1581
1625
  missingFields,
1626
+ spaceCompact,
1627
+ setFormDirty: () => onChange?.(values),
1582
1628
  workflowForm,
1583
- labelPosition: meta.labelPosition,
1584
- spaceCompact: meta.spaceCompact,
1585
- parentInfo: {
1586
- parentName: field.name,
1587
- fieldNamePrefix: `${field.name}.`
1588
- }
1629
+ setFieldValue,
1630
+ setFieldTouched
1589
1631
  })
1632
+ }),
1633
+ /* @__PURE__ */ jsx(WizardNavigation, {
1634
+ currentStep,
1635
+ stepOrder,
1636
+ canGoToNext: !isLastStep,
1637
+ canGoBack,
1638
+ isCurrentStepValid: !invalidSteps.has(currentStep),
1639
+ nextLabel: currentStepConfig?.nextLabel,
1640
+ prevLabel: currentStepConfig?.prevLabel,
1641
+ submitLabel: config.submitLabel || "Execute",
1642
+ onNext: handleGoToNext,
1643
+ onPrevious: goToPrevious,
1644
+ onSubmit: handleSubmit
1590
1645
  })
1591
- })]
1592
- });
1593
- }
1594
- //#endregion
1595
- //#region src/form/Fields/StepField.tsx
1596
- function StepField(_props) {
1597
- return null;
1598
- }
1599
- //#endregion
1600
- //#region src/form/Fields/BucketReadOnlyField.tsx
1601
- function BucketReadOnlyField({ field, disabled, missing, labelPosition, setFormDirty }) {
1602
- const { form: t } = useStrings();
1603
- const [fieldState] = useField(field.name);
1604
- const { setFieldTouched, setFieldValue, values } = useFormikContext();
1605
- const accessFieldName = field.name?.replace(/readOnly$/, "bucketAccessWritable");
1606
- const forced = fieldState.value === true && !!accessFieldName && getIn(values, accessFieldName) === false;
1607
- return /* @__PURE__ */ jsx(FieldWrapper, {
1608
- optional: true,
1609
- label: t.readOnly,
1610
- description: t.readOnlyDescription,
1611
- labelPosition,
1612
- children: ({ id, describedBy }) => /* @__PURE__ */ jsxs("div", {
1613
- className: "flex flex-col gap-2",
1614
- children: [/* @__PURE__ */ jsx(SwitchToggle, {
1615
- id,
1616
- "aria-describedby": describedBy,
1617
- onChange: (value) => {
1618
- if (disabled) return;
1619
- setFieldTouched(field.name, true);
1620
- setFieldValue(field.name, value);
1621
- setFormDirty(true);
1622
- },
1623
- value: fieldState.value === true,
1624
- disabled,
1625
- invalid: missing,
1626
- yesLabel: t.readOnly,
1627
- noLabel: t.readWrite
1628
- }), /* @__PURE__ */ jsx("p", {
1629
- className: cx("theme-muted-text max-w-[65ch]", !forced && "hidden"),
1630
- children: t.readOnlyForced
1631
- })]
1632
- })
1633
- });
1634
- }
1635
- //#endregion
1636
- //#region src/form/fieldRegistry.tsx
1637
- var CORE_FIELD_COMPONENTS = {
1638
- string: StringField_default,
1639
- password: StringField_default,
1640
- number: NumberField,
1641
- boolean: BooleanField,
1642
- dropdown: DropdownField,
1643
- textarea: TextAreaField,
1644
- range: RangeField,
1645
- duration: DurationField_default,
1646
- radio: RadioField,
1647
- label: LabelField,
1648
- color: ColorField,
1649
- "multi-dropdown": MultiDropdownField,
1650
- "checkbox-group": CheckboxGroupField,
1651
- object: ObjectField,
1652
- step: StepField,
1653
- "bucket-read-only": BucketReadOnlyField
1654
- };
1655
- var FieldRegistryContext = createContext({});
1656
- /** Returns null for field types no registry knows. Per-form `fields` win over
1657
- * the provider's `slots.formFields`, which win over the core registry. */
1658
- function Registry(props) {
1659
- const injected = useContext(FieldRegistryContext);
1660
- const { formFields } = useSlots();
1661
- const Component = injected[props.field.type] ?? formFields?.[props.field.type] ?? CORE_FIELD_COMPONENTS[props.field.type];
1662
- if (!Component) return null;
1663
- return /* @__PURE__ */ jsx(Suspense, {
1664
- fallback: null,
1665
- children: /* @__PURE__ */ jsx(Component, { ...props })
1646
+ ]
1666
1647
  });
1667
1648
  }
1668
1649
  //#endregion
@@ -1759,15 +1740,16 @@ function defaultReferencesField(defaultValue, fieldPath) {
1759
1740
  function findSelfReferencingFieldNames(schema, values = {}) {
1760
1741
  return collectFieldsWithDefaults(schema, values).filter(({ name, defaultValue }) => defaultReferencesField(defaultValue, name)).map(({ name }) => name);
1761
1742
  }
1762
- /** Resolves schema defaults into form values as dependencies change. Store-agnostic so the legacy
1763
- * form (Formik) and the cluster wizard (its own store) share one implementation. */
1743
+ /** Resolves schema defaults into form values as dependencies change. Store-agnostic so the Formik
1744
+ * form and hosts with their own store share one implementation. */
1764
1745
  function useDynamicDefaultsSync({ options, values, setValue, isTouched }) {
1746
+ const engine = useWorkflowEngine();
1765
1747
  const listSig = useMemo(() => listLengthsSignature(options, values), [options, values]);
1766
1748
  const fieldsWithDefaults = useMemo(() => collectFieldsWithDefaults(options, values).map((f) => {
1767
1749
  if (typeof f.defaultValue !== "string" || !f.defaultValue.includes("${{")) return f;
1768
1750
  const lastRowIndex = [...f.name.matchAll(/\[(\d+)\]/g)].at(-1)?.[1];
1769
1751
  const index = lastRowIndex === void 0 ? -1 : Number(lastRowIndex);
1770
- const parsed = parseStringsOfObj({
1752
+ const parsed = engine.evaluate({
1771
1753
  inputs: values,
1772
1754
  obj: { v: f.defaultValue },
1773
1755
  orgVars: {},
@@ -1863,7 +1845,7 @@ function deleteFieldByPath(obj, path) {
1863
1845
  if (acc && typeof acc === "object") return acc[curr];
1864
1846
  return {};
1865
1847
  }, obj);
1866
- if (parent && typeof parent === "object" && Object.prototype.hasOwnProperty.call(parent, fieldToRemove)) delete parent[fieldToRemove];
1848
+ if (parent && typeof parent === "object" && Object.hasOwn(parent, fieldToRemove)) delete parent[fieldToRemove];
1867
1849
  }
1868
1850
  function getClosestParentIndex(parentPath) {
1869
1851
  for (let i = parentPath.length - 1; i >= 0; i--) if (parentPath[i] === "]") {
@@ -1872,33 +1854,6 @@ function getClosestParentIndex(parentPath) {
1872
1854
  return parseInt(parentPath.slice(j + 1, i));
1873
1855
  }
1874
1856
  }
1875
- function getInstanceTypesForForm(instances) {
1876
- return Object.entries(instances).map(([name, instance]) => {
1877
- const flexLabel = instance.isFlex ? ", Flex" : "";
1878
- const localDisk = instance.localDiskSizeGb ? ` , ${instance.localDiskSizeGb} GB Local Disk` : "";
1879
- return {
1880
- value: name,
1881
- label: `${name} (${instance.vcpus} vCPUs, ${instance.memoryGb} GB Memory, ${instance.arch}${localDisk}${flexLabel})`,
1882
- secondaryValue: [
1883
- instance.arch,
1884
- !!instance.acceleratedNetworking,
1885
- !!instance.acceleratedNetworking,
1886
- !!instance.localDiskSizeGb,
1887
- !!instance.isFlex
1888
- ]
1889
- };
1890
- });
1891
- }
1892
- function StatusBlock({ status }) {
1893
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("div", {
1894
- "data-tooltip-id": TOOLTIP_ID,
1895
- "data-tooltip-content": status ? "Storage has been attached." : "Attaching the storage to cluster...",
1896
- children: status ? /* @__PURE__ */ jsx(SuccessCheckmark, {}) : /* @__PURE__ */ jsx(Loader, {
1897
- size: 13,
1898
- full: false
1899
- })
1900
- }) });
1901
- }
1902
1857
  function MultiSelectionDropdown({ name, label = "", invalid = false, options: initialOptions = [], setFormDirty, onChange, parentValue }) {
1903
1858
  const [field] = useField(name);
1904
1859
  const required = useFieldRequired();
@@ -1925,14 +1880,20 @@ function MultiSelectionDropdown({ name, label = "", invalid = false, options: in
1925
1880
  };
1926
1881
  const t = useStrings().common;
1927
1882
  const handleRemoveOption = (key) => {
1928
- setFieldValue(name, [...field.value.filter((item) => item !== key)]);
1883
+ const selected = [...field.value.filter((item) => item !== key)];
1884
+ setFieldValue(name, selected);
1929
1885
  if (onChange) onChange(options);
1930
1886
  setFormDirty(true);
1931
1887
  };
1932
1888
  const prevParent = usePrevious(parentValue);
1933
1889
  useEffect(() => {
1934
1890
  if (prevParent && parentValue && prevParent !== parentValue) setFieldValue(name, []);
1935
- }, [parentValue]);
1891
+ }, [
1892
+ parentValue,
1893
+ prevParent,
1894
+ name,
1895
+ setFieldValue
1896
+ ]);
1936
1897
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(CustomListbox, {
1937
1898
  ...controlProps,
1938
1899
  options,
@@ -1942,7 +1903,7 @@ function MultiSelectionDropdown({ name, label = "", invalid = false, options: in
1942
1903
  className: "mb-3",
1943
1904
  invalid,
1944
1905
  closeOnSelect: false
1945
- }), /* @__PURE__ */ jsx(Table, { children: field.value && field.value.length > 0 ? options.filter((option) => option.selected).map((option, index) => {
1906
+ }), /* @__PURE__ */ jsx(Table, { children: field.value && field.value.length > 0 ? options.filter((option) => option.selected).map((option) => {
1946
1907
  return /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx(Table.Item, {
1947
1908
  className: "w-full",
1948
1909
  children: option?.label
@@ -1952,7 +1913,7 @@ function MultiSelectionDropdown({ name, label = "", invalid = false, options: in
1952
1913
  className: "inline-flex bg-transparent p-0 cursor-pointer",
1953
1914
  onClick: () => handleRemoveOption(option.value),
1954
1915
  children: /* @__PURE__ */ jsx(TrashIcon, { className: "link mr-4" })
1955
- }) })] }, index);
1916
+ }) })] }, String(option.value));
1956
1917
  }) : /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs(Table.Item, { children: [
1957
1918
  "No ",
1958
1919
  label !== "" ? label.toLowerCase() : "options",
@@ -1974,11 +1935,12 @@ var InputField = React.memo(function InputField({ label, field, setFormDirty, on
1974
1935
  text: typeof fieldObj.tooltip === "string" ? fieldObj.tooltip.replace(/\r/g, "") : fieldObj.tooltip.filter(Boolean).join("\n")
1975
1936
  });
1976
1937
  }, [fieldObj.tooltip]);
1938
+ const missing = missingFields.includes(fieldObj.name ?? "");
1977
1939
  return /* @__PURE__ */ jsx(Registry, {
1978
1940
  field: fieldObj,
1979
1941
  label,
1980
1942
  labelPosition,
1981
- missing: missingFields.includes(fieldObj.name ?? ""),
1943
+ missing,
1982
1944
  missingFields,
1983
1945
  values,
1984
1946
  setFormDirty,
@@ -2020,6 +1982,7 @@ function DirtyStateBridge({ setFormDirty }) {
2020
1982
  }
2021
1983
  /** Three-tier cache: no expressions → raw pass-through; input-dep expressions → re-parse on dep change; org-var/path expressions → parse once. */
2022
1984
  function useFieldParse(optionsField, values, arrayIndex) {
1985
+ const engine = useWorkflowEngine();
2023
1986
  const c = useRef({
2024
1987
  lastOptField: null,
2025
1988
  convertedField: null,
@@ -2031,8 +1994,8 @@ function useFieldParse(optionsField, values, arrayIndex) {
2031
1994
  }).current;
2032
1995
  if (optionsField !== c.lastOptField) {
2033
1996
  c.lastOptField = optionsField;
2034
- c.convertedField = convertToDynamicForm(asRecord(optionsField));
2035
- const { inputDeps, hasExpressions } = extractInputDeps(c.convertedField);
1997
+ c.convertedField = engine.convertInputs(asRecord(optionsField));
1998
+ const { inputDeps, hasExpressions } = engine.inputDependencies(c.convertedField);
2036
1999
  c.inputDeps = inputDeps;
2037
2000
  c.hasExpressions = hasExpressions;
2038
2001
  c.result = null;
@@ -2051,7 +2014,7 @@ function useFieldParse(optionsField, values, arrayIndex) {
2051
2014
  }
2052
2015
  if (!changed) return c.result;
2053
2016
  }
2054
- c.result = parseStringsOfObj({
2017
+ c.result = engine.evaluate({
2055
2018
  inputs: values,
2056
2019
  obj: c.convertedField,
2057
2020
  orgVars: {},
@@ -2088,8 +2051,10 @@ var FormField = React.memo(function FormField({ id, optionsField, fieldName, val
2088
2051
  }
2089
2052
  if (field.show_if !== void 0 || field.show_if_not !== void 0) {
2090
2053
  let index;
2091
- if (parentInfo) if (!parentInfo?.arrayIndex) index = getClosestParentIndex(parentInfo.fieldNamePrefix);
2092
- else index = parentInfo.arrayIndex;
2054
+ if (parentInfo) {
2055
+ if (!parentInfo?.arrayIndex) index = getClosestParentIndex(parentInfo.fieldNamePrefix);
2056
+ else index = parentInfo.arrayIndex;
2057
+ }
2093
2058
  let shouldShowField = true;
2094
2059
  if (field.show_if !== void 0) shouldShowField = !!getParentValue(index, field.show_if, values, "", field);
2095
2060
  if (shouldShowField && field.show_if_not !== void 0) shouldShowField = !getParentValue(index, field.show_if_not, values, "", field);
@@ -2161,8 +2126,10 @@ var FormField = React.memo(function FormField({ id, optionsField, fieldName, val
2161
2126
  if (prevParentValue !== void 0 && prevParentValue !== parentValue) deleteFieldByPath(values, fieldObj["name"]);
2162
2127
  field.prevParentValue = parentValue;
2163
2128
  }
2164
- if (field.secondaryField) if (Array.isArray(field.secondaryField)) fieldObj["secondaryField"] = field.secondaryField.map((secondaryField) => fieldNamePrefix + secondaryField);
2165
- else fieldObj["secondaryField"] = fieldNamePrefix + field.secondaryField;
2129
+ if (field.secondaryField) {
2130
+ if (Array.isArray(field.secondaryField)) fieldObj["secondaryField"] = field.secondaryField.map((secondaryField) => fieldNamePrefix + secondaryField);
2131
+ else fieldObj["secondaryField"] = fieldNamePrefix + field.secondaryField;
2132
+ }
2166
2133
  return /* @__PURE__ */ jsx("div", {
2167
2134
  className: cx("flex w-full", labelPosition === "left" ? "mb-[15px]" : "mb-[5px]"),
2168
2135
  children: /* @__PURE__ */ jsx(InputField, {
@@ -2206,7 +2173,7 @@ function FieldsFromOptions({ options = {}, values, setFormDirty, setFieldValue,
2206
2173
  missingFields,
2207
2174
  spaceCompact,
2208
2175
  workflowForm
2209
- }, idx));
2176
+ }, fieldName));
2210
2177
  }
2211
2178
  var noop = () => {};
2212
2179
  function DynamicFormContent({ options, setValues, setFormDirty, labelPosition, missingFields, spaceCompact, organizationVariables, remoteVars, workflowForm, onSubmit, submitLabel, values, contextKey }) {
@@ -2252,13 +2219,14 @@ function DynamicFormContent({ options, setValues, setFormDirty, labelPosition, m
2252
2219
  }
2253
2220
  var NO_FIELDS = {};
2254
2221
  function DynamicForm({ formJSONs: options = {}, reinitialize = false, initialValues, setValues, setFormDirty = noop, formikRef, labelPosition = "left", missingFields = [], spaceCompact = false, organizationVariables, remoteVars, needsOrganizationVariables = false, onSubmit = void 0, submitLabel = "Submit", className = "w-full", workflowForm = false, contextKey, skipValueParse = false, fields = NO_FIELDS }) {
2255
- const [parseReady, setParseReady] = useState(parseStringsOfObjReady);
2222
+ const engine = useWorkflowEngine();
2223
+ const [parseReady, setParseReady] = useState(() => engine.isReady());
2256
2224
  useEffect(() => {
2257
- initializeParseStringsOfObj();
2258
- return callWhenParserInitialized(setParseReady);
2259
- }, []);
2225
+ engine.init();
2226
+ return engine.onReady(setParseReady);
2227
+ }, [engine]);
2260
2228
  if (!parseReady) return /* @__PURE__ */ jsx(Loader, {});
2261
- const parsedInitVals = skipValueParse ? initialValues : initialValues && parseStringsOfObj({
2229
+ const parsedInitVals = skipValueParse ? initialValues : initialValues && engine.evaluate({
2262
2230
  inputs: initialValues,
2263
2231
  obj: initialValues,
2264
2232
  orgVars: organizationVariables,
@@ -2351,95 +2319,4 @@ function FormikMultiSelectDropdown({ name, options, setFormDirty, onChange, disa
2351
2319
  });
2352
2320
  }
2353
2321
  //#endregion
2354
- //#region src/form/utils/pwResolver.ts
2355
- function resolvePwString(raw, options, fallbackUsername, opts) {
2356
- let inputForm = null;
2357
- let owner;
2358
- let name;
2359
- if (typeof raw === "string" && raw.startsWith("pw://")) {
2360
- const parts = raw.slice(5).split("/");
2361
- if (parts.length === 1 && parts[0]) {
2362
- inputForm = "shorthand";
2363
- name = parts[0];
2364
- owner = fallbackUsername;
2365
- } else if (parts.length === 2 && parts[0] && parts[1]) {
2366
- inputForm = "full";
2367
- owner = parts[0];
2368
- name = parts[1];
2369
- }
2370
- } else if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
2371
- const obj = raw;
2372
- if (typeof obj["uri"] === "string" && obj["uri"].startsWith("pw://")) {
2373
- const parts = obj["uri"].slice(5).split("/");
2374
- if (parts.length === 1 && parts[0]) {
2375
- inputForm = "shorthand";
2376
- name = parts[0];
2377
- owner = fallbackUsername;
2378
- } else if (parts.length === 2 && parts[0] && parts[1]) {
2379
- inputForm = "full";
2380
- owner = parts[0];
2381
- name = parts[1];
2382
- }
2383
- } else if (typeof obj["name"] === "string") {
2384
- name = obj["name"];
2385
- const ownerVal = typeof obj["user"] === "string" ? obj["user"] : typeof obj["namespace"] === "string" ? obj["namespace"] : void 0;
2386
- if (obj["type"] === "managed-cluster" || !ownerVal) {
2387
- inputForm = "shorthand";
2388
- owner = fallbackUsername;
2389
- } else {
2390
- inputForm = "full";
2391
- owner = ownerVal;
2392
- }
2393
- } else if (typeof obj["id"] === "string" && obj["id"]) {
2394
- inputForm = "shorthand";
2395
- name = obj["id"];
2396
- owner = fallbackUsername;
2397
- }
2398
- }
2399
- if (!inputForm || !name || !owner) return raw;
2400
- const special = opts.specialCase?.(name, owner);
2401
- if (special !== void 0) return special;
2402
- const find = (pred) => {
2403
- for (const category of options) for (const opt of category.options) if (opt.value && pred(opt.value)) return opt.value;
2404
- };
2405
- if (inputForm === "full") return find((v) => v.name === name && v[opts.ownerKey] === owner) ?? raw;
2406
- const managed = find((v) => v.name === name && v.type === "managed-cluster");
2407
- if (managed) return managed;
2408
- const ownedByCaller = find((v) => v.name === name && v[opts.ownerKey] === fallbackUsername);
2409
- if (ownedByCaller) return ownedByCaller;
2410
- const sharedWithCaller = find((v) => v.name === name && typeof v[opts.ownerKey] === "string" && v[opts.ownerKey] !== fallbackUsername);
2411
- if (sharedWithCaller) return sharedWithCaller;
2412
- return raw;
2413
- }
2414
- function resolvePwValue(currentValue, options, fallbackUsername, opts) {
2415
- if (Array.isArray(currentValue)) {
2416
- let changed = false;
2417
- const next = currentValue.map((v) => {
2418
- const resolved = resolvePwString(v, options, fallbackUsername, opts);
2419
- if (resolved !== v) changed = true;
2420
- return resolved;
2421
- });
2422
- return changed ? next : currentValue;
2423
- }
2424
- return resolvePwString(currentValue, options, fallbackUsername, opts);
2425
- }
2426
- //#endregion
2427
- //#region src/form/Fields/ResourceOptionIcon.tsx
2428
- function ResourceOptionIcon({ imageUrl, fallbackIcon, alt, variant = "success", dotSize = 6 }) {
2429
- return /* @__PURE__ */ jsxs("span", {
2430
- className: "flex items-center gap-1.5 shrink-0",
2431
- children: [/* @__PURE__ */ jsx(StatusDot, {
2432
- variant,
2433
- size: dotSize
2434
- }), /* @__PURE__ */ jsx("img", {
2435
- src: imageUrl || fallbackIcon,
2436
- alt,
2437
- className: "h-4 w-4 object-contain",
2438
- onError: (e) => {
2439
- e.currentTarget.src = fallbackIcon;
2440
- }
2441
- })]
2442
- });
2443
- }
2444
- //#endregion
2445
- export { DropdownWrapper, DynamicDefaultsSync, DynamicForm, FLEX_MAX_DURATION_RANGE, FLEX_WAIT_TIME_RANGE, FieldProvider, FieldRegistryContext, FieldWrapper, FieldsFromOptions, FormikCustomDropdown_default as FormikCustomDropdown, FormikCustomInput, FormikMultiSelectDropdown, FormikNumericInput, GroupHeader, MultiSelectionDropdown, NumberField, Registry, ResourceOptionIcon, STANDARD_MAX_DURATION_RANGE, StatusBlock, applySecondaryField, collectFieldsWithDefaults, collectPartitionDurationIssues, enforceOneMustBeTrue, findSecondaryOption, findSelfReferencingFieldNames, flattenGroups, formatSecondsAsSlurmDuration, getInstanceTypesForForm, getInvalidDurationPaths, getPlaceholder, impureSetValueFromPath, initializeValues, parseSlurmDuration, resolveMetaOverrides, resolvePwString, resolvePwValue, resolvedFlag, setDurationValidity, useDynamicDefaultsSync, useFieldControlProps, useFieldLabelledByProps, useFieldRequired, useParsedOpts };
2322
+ export { DropdownWrapper, DynamicDefaultsSync, DynamicForm, FLEX_MAX_DURATION_RANGE, FLEX_WAIT_TIME_RANGE, FieldProvider, FieldRegistryContext, FieldWrapper, FieldsFromOptions, FormikCustomDropdown_default as FormikCustomDropdown, FormikCustomInput, FormikMultiSelectDropdown, FormikNumericInput, GroupHeader, MultiSelectionDropdown, NumberField, Registry, ResourceOptionIcon, STANDARD_MAX_DURATION_RANGE, applySecondaryField, collectFieldsWithDefaults, collectPartitionDurationIssues, enforceOneMustBeTrue, findSecondaryOption, findSelfReferencingFieldNames, flattenGroups, formatDuration, getInvalidDurationPaths, getPlaceholder, impureSetValueFromPath, initializeValues, parseDuration, resolveMetaOverrides, resolvedFlag, setDurationValidity, useDynamicDefaultsSync, useFieldControlProps, useFieldLabelledByProps, useFieldRequired, useParsedOpts };