@qoretechnologies/reqraft 0.10.53 → 0.10.54-pr.115.g9bc96ed
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +25 -0
- package/.tasks/INDEX.md +1 -0
- package/.tasks/STORY_NETWORK_ISOLATION.md +98 -0
- package/README.md +1 -1
- package/design/FORM_ENGINE_EXPRESSIONS.md +110 -0
- package/design/FORM_ENGINE_FIELD_MIGRATION.md +56 -1
- package/design/RENDER_EXPRESSION_TRANSPORT.md +76 -10
- package/design/SMART_EDITOR_UX.md +21 -0
- package/dist/components/dpqlEditor/DpqlEditor.d.ts.map +1 -1
- package/dist/components/dpqlEditor/DpqlEditor.js +3 -3
- package/dist/components/dpqlEditor/DpqlEditor.js.map +1 -1
- package/dist/components/dpqlEditor/dpqlHelpers.d.ts +6 -0
- package/dist/components/dpqlEditor/dpqlHelpers.d.ts.map +1 -1
- package/dist/components/dpqlEditor/dpqlHelpers.js +21 -2
- package/dist/components/dpqlEditor/dpqlHelpers.js.map +1 -1
- package/dist/components/dpqlEditor/dpqlProbe.d.ts +37 -0
- package/dist/components/dpqlEditor/dpqlProbe.d.ts.map +1 -0
- package/dist/components/dpqlEditor/dpqlProbe.js +203 -0
- package/dist/components/dpqlEditor/dpqlProbe.js.map +1 -0
- package/dist/components/dpqlEditor/dpqlTags.d.ts +8 -0
- package/dist/components/dpqlEditor/dpqlTags.d.ts.map +1 -1
- package/dist/components/dpqlEditor/dpqlTags.js +5 -1
- package/dist/components/dpqlEditor/dpqlTags.js.map +1 -1
- package/dist/components/dpqlEditor/index.d.ts +4 -0
- package/dist/components/dpqlEditor/index.d.ts.map +1 -1
- package/dist/components/dpqlEditor/index.js +5 -1
- package/dist/components/dpqlEditor/index.js.map +1 -1
- package/dist/components/dpqlEditor/types.d.ts +42 -2
- package/dist/components/dpqlEditor/types.d.ts.map +1 -1
- package/dist/components/dpqlEditor/types.js +0 -4
- package/dist/components/dpqlEditor/types.js.map +1 -1
- package/dist/components/dpqlEditor/useDpqlProbe.d.ts +4 -0
- package/dist/components/dpqlEditor/useDpqlProbe.d.ts.map +1 -0
- package/dist/components/dpqlEditor/useDpqlProbe.js +29 -0
- package/dist/components/dpqlEditor/useDpqlProbe.js.map +1 -0
- package/dist/components/dpqlEditor/useDpqlSession.d.ts +5 -2
- package/dist/components/dpqlEditor/useDpqlSession.d.ts.map +1 -1
- package/dist/components/dpqlEditor/useDpqlSession.js +8 -2
- package/dist/components/dpqlEditor/useDpqlSession.js.map +1 -1
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +192 -45
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
- package/dist/components/form/engine/CompactToolbar.js +5 -2
- package/dist/components/form/engine/CompactToolbar.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts +160 -12
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +376 -75
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/FormFieldsSkeleton.d.ts +48 -0
- package/dist/components/form/engine/FormFieldsSkeleton.d.ts.map +1 -0
- package/dist/components/form/engine/FormFieldsSkeleton.js +38 -0
- package/dist/components/form/engine/FormFieldsSkeleton.js.map +1 -0
- package/dist/components/form/engine/OptionFieldMessages.d.ts +17 -2
- package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
- package/dist/components/form/engine/OptionFieldMessages.js +61 -11
- package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.js +7 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
- package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
- package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.d.ts +42 -1
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +74 -7
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/engine/rendererTypes.d.ts +13 -0
- package/dist/components/form/engine/rendererTypes.d.ts.map +1 -1
- package/dist/components/form/engine/rendererTypes.js.map +1 -1
- package/dist/components/form/engine/rowMenuContext.d.ts +78 -0
- package/dist/components/form/engine/rowMenuContext.d.ts.map +1 -0
- package/dist/components/form/engine/rowMenuContext.js +103 -0
- package/dist/components/form/engine/rowMenuContext.js.map +1 -0
- package/dist/components/form/engine/typeChoices.d.ts +37 -0
- package/dist/components/form/engine/typeChoices.d.ts.map +1 -0
- package/dist/components/form/engine/typeChoices.js +25 -0
- package/dist/components/form/engine/typeChoices.js.map +1 -0
- package/dist/components/form/expressions/DpqlRendering.d.ts +26 -0
- package/dist/components/form/expressions/DpqlRendering.d.ts.map +1 -0
- package/dist/components/form/expressions/DpqlRendering.js +53 -0
- package/dist/components/form/expressions/DpqlRendering.js.map +1 -0
- package/dist/components/form/expressions/ExpressionField.d.ts +7 -1
- package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
- package/dist/components/form/expressions/ExpressionField.js +202 -45
- package/dist/components/form/expressions/ExpressionField.js.map +1 -1
- package/dist/components/form/expressions/argumentPresence.d.ts +97 -0
- package/dist/components/form/expressions/argumentPresence.d.ts.map +1 -0
- package/dist/components/form/expressions/argumentPresence.js +203 -0
- package/dist/components/form/expressions/argumentPresence.js.map +1 -0
- package/dist/components/form/expressions/builder/argumentLabel.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/argumentLabel.js +9 -4
- package/dist/components/form/expressions/builder/argumentLabel.js.map +1 -1
- package/dist/components/form/expressions/builder/index.d.ts +15 -2
- package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/index.js +20 -58
- package/dist/components/form/expressions/builder/index.js.map +1 -1
- package/dist/components/form/expressions/builder/renderTemplate.d.ts +4 -1
- package/dist/components/form/expressions/builder/renderTemplate.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/renderTemplate.js +10 -67
- package/dist/components/form/expressions/builder/renderTemplate.js.map +1 -1
- package/dist/components/form/expressions/dpqlMockLanguage.d.ts +76 -0
- package/dist/components/form/expressions/dpqlMockLanguage.d.ts.map +1 -0
- package/dist/components/form/expressions/dpqlMockLanguage.js +525 -0
- package/dist/components/form/expressions/dpqlMockLanguage.js.map +1 -0
- package/dist/components/form/expressions/dpqlMockLsp.d.ts +31 -2
- package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +1 -1
- package/dist/components/form/expressions/dpqlMockLsp.js +86 -80
- package/dist/components/form/expressions/dpqlMockLsp.js.map +1 -1
- package/dist/components/form/expressions/mockExpressions.d.ts.map +1 -1
- package/dist/components/form/expressions/mockExpressions.js +8 -4
- package/dist/components/form/expressions/mockExpressions.js.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.js +95 -19
- package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
- package/dist/components/form/expressions/types.d.ts +7 -0
- package/dist/components/form/expressions/types.d.ts.map +1 -1
- package/dist/components/form/expressions/useRenderExpression.d.ts +7 -16
- package/dist/components/form/expressions/useRenderExpression.d.ts.map +1 -1
- package/dist/components/form/expressions/useRenderExpression.js +50 -126
- package/dist/components/form/expressions/useRenderExpression.js.map +1 -1
- package/dist/components/form/fields/Field.d.ts.map +1 -1
- package/dist/components/form/fields/Field.js +3 -1
- package/dist/components/form/fields/Field.js.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +11 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.js +14 -2
- package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.js +32 -8
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/dist/components/form/fields/date/Date.d.ts +1 -1
- package/dist/components/form/fields/date/Date.d.ts.map +1 -1
- package/dist/components/form/fields/date/Date.js +16 -2
- package/dist/components/form/fields/date/Date.js.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts +21 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.js +140 -8
- package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts +2 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts.map +1 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.js +3 -2
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.js.map +1 -1
- package/dist/components/form/fields/template/TemplateField.d.ts +31 -1
- package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
- package/dist/components/form/fields/template/TemplateField.js +528 -50
- package/dist/components/form/fields/template/TemplateField.js.map +1 -1
- package/dist/components/form/index.d.ts +1 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +1 -0
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/smartEditor/SmartEditor.d.ts.map +1 -1
- package/dist/components/smartEditor/SmartEditor.js +29 -8
- package/dist/components/smartEditor/SmartEditor.js.map +1 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +8 -0
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +1 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js +53 -10
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +1 -1
- package/dist/components/smartEditor/useLspSession.d.ts +5 -1
- package/dist/components/smartEditor/useLspSession.d.ts.map +1 -1
- package/dist/components/smartEditor/useLspSession.js +16 -2
- package/dist/components/smartEditor/useLspSession.js.map +1 -1
- package/dist/helpers/dpqlDetection.d.ts +58 -0
- package/dist/helpers/dpqlDetection.d.ts.map +1 -0
- package/dist/helpers/dpqlDetection.js +164 -0
- package/dist/helpers/dpqlDetection.js.map +1 -0
- package/dist/helpers/optionUiTypes.d.ts +3 -3
- package/dist/helpers/options.d.ts +37 -0
- package/dist/helpers/options.d.ts.map +1 -1
- package/dist/helpers/options.js +78 -1
- package/dist/helpers/options.js.map +1 -1
- package/dist/helpers/templateItems.d.ts +47 -0
- package/dist/helpers/templateItems.d.ts.map +1 -0
- package/dist/helpers/templateItems.js +88 -0
- package/dist/helpers/templateItems.js.map +1 -0
- package/dist/helpers/templateText.d.ts +37 -0
- package/dist/helpers/templateText.d.ts.map +1 -0
- package/dist/helpers/templateText.js +64 -0
- package/dist/helpers/templateText.js.map +1 -0
- package/dist/helpers/templates.d.ts +19 -11
- package/dist/helpers/templates.d.ts.map +1 -1
- package/dist/helpers/templates.js +46 -29
- package/dist/helpers/templates.js.map +1 -1
- package/dist/helpers/validations.d.ts.map +1 -1
- package/dist/helpers/validations.js +56 -9
- package/dist/helpers/validations.js.map +1 -1
- package/dist/hooks/useArgSchema.d.ts +2 -1
- package/dist/hooks/useArgSchema.d.ts.map +1 -1
- package/dist/hooks/useFetch/useFetch.d.ts +3 -1
- package/dist/hooks/useFetch/useFetch.d.ts.map +1 -1
- package/dist/hooks/useFetch/useFetch.js +47 -9
- package/dist/hooks/useFetch/useFetch.js.map +1 -1
- package/dist/hooks/useTemplates.d.ts +20 -0
- package/dist/hooks/useTemplates.d.ts.map +1 -1
- package/dist/hooks/useTemplates.js +34 -2
- package/dist/hooks/useTemplates.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/stories/storyNetwork.d.ts +36 -0
- package/dist/stories/storyNetwork.d.ts.map +1 -0
- package/dist/stories/storyNetwork.js +82 -0
- package/dist/stories/storyNetwork.js.map +1 -0
- package/dist/utils/fetch.d.ts +151 -1
- package/dist/utils/fetch.d.ts.map +1 -1
- package/dist/utils/fetch.js +256 -44
- package/dist/utils/fetch.js.map +1 -1
- package/dist/utils/lspClient.d.ts +6 -0
- package/dist/utils/lspClient.d.ts.map +1 -1
- package/dist/utils/lspClient.js +62 -11
- package/dist/utils/lspClient.js.map +1 -1
- package/dist/utils/websocket.d.ts +6 -0
- package/dist/utils/websocket.d.ts.map +1 -1
- package/dist/utils/websocket.js +15 -7
- package/dist/utils/websocket.js.map +1 -1
- package/package.json +2 -2
- package/src/components/dpqlEditor/DpqlEditor.stories.tsx +3 -119
- package/src/components/dpqlEditor/DpqlEditor.tsx +5 -2
- package/src/components/dpqlEditor/dpqlHelpers.ts +20 -1
- package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
- package/src/components/dpqlEditor/dpqlTags.ts +13 -0
- package/src/components/dpqlEditor/index.ts +4 -0
- package/src/components/dpqlEditor/types.ts +38 -2
- package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
- package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
- package/src/components/form/engine/CompactRow.tsx +240 -51
- package/src/components/form/engine/CompactToolbar.tsx +10 -3
- package/src/components/form/engine/FormEngine.stories.tsx +423 -120
- package/src/components/form/engine/FormEngine.tsx +504 -87
- package/src/components/form/engine/FormFieldsSkeleton.tsx +76 -0
- package/src/components/form/engine/OptionFieldMessages.tsx +89 -11
- package/src/components/form/engine/_structuredData/SchemaDataView.tsx +11 -0
- package/src/components/form/engine/compactToolbarContext.ts +1 -1
- package/src/components/form/engine/readFirst.ts +86 -7
- package/src/components/form/engine/rendererTypes.ts +12 -0
- package/src/components/form/engine/rowMenuContext.ts +159 -0
- package/src/components/form/engine/typeChoices.ts +55 -0
- package/src/components/form/expressions/DpqlRendering.tsx +85 -0
- package/src/components/form/expressions/ExpressionField.stories.tsx +826 -48
- package/src/components/form/expressions/ExpressionField.tsx +265 -54
- package/src/components/form/expressions/argumentPresence.ts +200 -0
- package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +20 -4
- package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
- package/src/components/form/expressions/builder/index.tsx +39 -77
- package/src/components/form/expressions/builder/renderTemplate.tsx +23 -56
- package/src/components/form/expressions/dpqlMockLanguage.ts +565 -0
- package/src/components/form/expressions/dpqlMockLsp.ts +109 -83
- package/src/components/form/expressions/mockExpressions.ts +8 -4
- package/src/components/form/expressions/renderExpressionToText.ts +112 -22
- package/src/components/form/expressions/types.ts +7 -0
- package/src/components/form/expressions/useRenderExpression.ts +43 -127
- package/src/components/form/fields/Field.tsx +3 -2
- package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -2
- package/src/components/form/fields/auto/AutoFormField.stories.tsx +27 -20
- package/src/components/form/fields/auto/AutoFormField.tsx +33 -9
- package/src/components/form/fields/date/Date.tsx +17 -1
- package/src/components/form/fields/rich-text/RichText.tsx +199 -22
- package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
- package/src/components/form/fields/template/TemplateField.stories.tsx +31 -9
- package/src/components/form/fields/template/TemplateField.tsx +714 -122
- package/src/components/form/index.tsx +1 -0
- package/src/components/log/Log.stories.tsx +2 -1
- package/src/components/smartEditor/SmartEditor.tsx +41 -9
- package/src/components/smartEditor/__fixtures__/mockLspServer.ts +52 -3
- package/src/components/smartEditor/useLspSession.ts +21 -3
- package/src/helpers/dpqlDetection.ts +209 -0
- package/src/helpers/options.ts +70 -0
- package/src/helpers/templateItems.ts +81 -0
- package/src/helpers/templateText.ts +92 -0
- package/src/helpers/templates.ts +47 -37
- package/src/helpers/validations.ts +59 -9
- package/src/hooks/useFetch/useFetch.stories.tsx +6 -5
- package/src/hooks/useFetch/useFetch.tsx +53 -7
- package/src/hooks/useStorage/useStorage.stories.tsx +2 -1
- package/src/hooks/useTemplates.ts +41 -0
- package/src/hooks/useWebSocket/useWebsocket.stories.tsx +2 -1
- package/src/index.tsx +14 -1
- package/src/stores/currentUser/currentUser.stories.tsx +2 -1
- package/src/stories/storyNetwork.ts +92 -0
- package/src/utils/fetch.ts +311 -21
- package/src/utils/lspClient.ts +68 -11
- package/src/utils/websocket.ts +20 -9
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
ReqoreIcon,
|
|
7
7
|
ReqoreMessage,
|
|
8
8
|
ReqoreP,
|
|
9
|
-
ReqoreSkeleton,
|
|
10
9
|
ReqoreTag,
|
|
11
10
|
ReqoreTagGroup,
|
|
12
11
|
ReqoreVerticalSpacer,
|
|
@@ -17,6 +16,7 @@ import { IReqoreCollectionProps } from '@qoretechnologies/reqore/dist/components
|
|
|
17
16
|
import { IReqoreCollectionItemProps } from '@qoretechnologies/reqore/dist/components/Collection/item';
|
|
18
17
|
import { IReqorePanelProps } from '@qoretechnologies/reqore/dist/components/Panel';
|
|
19
18
|
import { IReqoreFormTemplates } from '@qoretechnologies/reqore/dist/components/Textarea';
|
|
19
|
+
import { TFieldWithOwnTemplates } from './rendererTypes';
|
|
20
20
|
import { TReqoreIntent } from '@qoretechnologies/reqore/dist/constants/theme';
|
|
21
21
|
import {
|
|
22
22
|
changeDarkness,
|
|
@@ -38,6 +38,8 @@ import {
|
|
|
38
38
|
TQorusFormOperatorValue,
|
|
39
39
|
TQorusType,
|
|
40
40
|
} from '@qoretechnologies/ts-toolkit';
|
|
41
|
+
import { shouldMarkAsExpression } from '../expressions/argumentPresence';
|
|
42
|
+
import { offersTypeChoices } from './typeChoices';
|
|
41
43
|
import { resolveOptionActions, TOptionActions } from './optionActions';
|
|
42
44
|
import { createRendererOnlyUiTypeCheck, isRendererOnlyUiType } from './rendererTypes';
|
|
43
45
|
import { cloneDeep, findKey, flatten, forEach, isEqual, isPlainObject, last } from 'lodash';
|
|
@@ -55,10 +57,14 @@ import {
|
|
|
55
57
|
insertAtIndex,
|
|
56
58
|
richtextToString,
|
|
57
59
|
} from '../../../helpers/common';
|
|
58
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
getRequiredOptionMessage,
|
|
62
|
+
resolveDegenerateRequiredGroups,
|
|
63
|
+
} from '../../../helpers/options';
|
|
59
64
|
import {
|
|
60
65
|
IValidationResult,
|
|
61
66
|
hasAllDependenciesFullfilled,
|
|
67
|
+
parseDependency,
|
|
62
68
|
validateField,
|
|
63
69
|
validateFieldWithResult,
|
|
64
70
|
} from '../../../helpers/validations';
|
|
@@ -75,6 +81,7 @@ import {
|
|
|
75
81
|
isValueTemplate,
|
|
76
82
|
} from '../fields/template/TemplateField';
|
|
77
83
|
import { CompactRow } from './CompactRow';
|
|
84
|
+
import { FormFieldsSkeleton } from './FormFieldsSkeleton';
|
|
78
85
|
import { CompactRowContext, ICompactRowContext, TCodePreviewRenderer } from './compactRowContext';
|
|
79
86
|
import {
|
|
80
87
|
GROUP_INDENT,
|
|
@@ -257,9 +264,9 @@ const StyledCompactWrap = styled.div<{ $ownScroll?: boolean }>`
|
|
|
257
264
|
toolbar silently stops pinning. \`clip\` keeps the horizontal guard without
|
|
258
265
|
that coercion. */
|
|
259
266
|
${({ $ownScroll }) =>
|
|
260
|
-
$ownScroll
|
|
261
|
-
|
|
262
|
-
|
|
267
|
+
$ownScroll ?
|
|
268
|
+
css`
|
|
269
|
+
/* 'own': we are the scroller. The sticky toolbar pins to whatever
|
|
263
270
|
scrolls, so a host whose scrollport carries top padding would
|
|
264
271
|
resolve sticky \`top: 0\` against its padding box and leave an
|
|
265
272
|
unblurred strip above the toolbar. Scrolling in this unpadded box
|
|
@@ -268,22 +275,22 @@ const StyledCompactWrap = styled.div<{ $ownScroll?: boolean }>`
|
|
|
268
275
|
|
|
269
276
|
\`min-height: 0\` is what lets a flex item shrink below its content
|
|
270
277
|
so there is something to scroll; it belongs to this branch only. */
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
+
min-height: 0;
|
|
279
|
+
max-height: 100%;
|
|
280
|
+
overflow-y: auto;
|
|
281
|
+
overflow-x: hidden;
|
|
282
|
+
`
|
|
283
|
+
: css`
|
|
284
|
+
/* \`min-height: auto\` is load-bearing, not a default: inside a column
|
|
278
285
|
flex parent the shrink allowance above collapses this box to zero
|
|
279
286
|
height while its rows paint outside it, so the form looks right but
|
|
280
287
|
contributes nothing to layout — the host cannot size or scroll to
|
|
281
288
|
it and anything below it stacks against nothing. */
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
289
|
+
min-height: auto;
|
|
290
|
+
max-height: none;
|
|
291
|
+
overflow-y: visible;
|
|
292
|
+
overflow-x: clip;
|
|
293
|
+
`}
|
|
287
294
|
|
|
288
295
|
/* Option logos (e.g. language images) render as <img> inside ReqoreIcon's
|
|
289
296
|
square box; constrain them so portrait PNGs don't overflow the row. */
|
|
@@ -355,17 +362,59 @@ const getOptionSchemaStorageType = (
|
|
|
355
362
|
: isRendererOnly(option?.ui_type) ? option?.type || option?.ui_type
|
|
356
363
|
: option?.ui_type || option?.type) || 'any') as TQorusType;
|
|
357
364
|
|
|
358
|
-
|
|
365
|
+
/**
|
|
366
|
+
* Whether an option currently holds an expression, in EITHER shape it comes in.
|
|
367
|
+
*
|
|
368
|
+
* At runtime the editor writes the flag onto the option itself
|
|
369
|
+
* (`{ type, is_expression: true, value }`), but a value loaded from a saved
|
|
370
|
+
* draft carries the envelope NESTED (`{ type, value: { is_expression: true,
|
|
371
|
+
* value } }`). Same thing, two shapes, and only the flat one was ever checked —
|
|
372
|
+
* so a freshly typed expression behaved while a reloaded one did not, which is
|
|
373
|
+
* exactly how this survived several fixes: every test used the runtime shape.
|
|
374
|
+
*/
|
|
375
|
+
const optionHoldsExpression = (option: unknown): boolean => {
|
|
376
|
+
const o = option as { is_expression?: unknown; value?: { is_expression?: unknown } } | undefined;
|
|
377
|
+
return !!(o?.is_expression || o?.value?.is_expression);
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Which type an option resolves to — the schema's, or the one stored on the
|
|
382
|
+
* value.
|
|
383
|
+
*
|
|
384
|
+
* Exported because this single decision is what every option field is rendered
|
|
385
|
+
* with, and what an expression's return type is checked against. It deserves
|
|
386
|
+
* to be assertable without standing up a form.
|
|
387
|
+
*/
|
|
388
|
+
export const getOptionFieldStorageType = (
|
|
359
389
|
optionName: string,
|
|
360
390
|
fieldType: TQorusType | TQorusType[] | undefined,
|
|
361
391
|
schema?: IQorusFormSchema,
|
|
362
392
|
operators?: IOperatorsSchema,
|
|
363
393
|
operatorData?: TOperatorValue,
|
|
364
|
-
isRendererOnly: TRendererOnlyCheck = isRendererOnlyUiType
|
|
394
|
+
isRendererOnly: TRendererOnlyCheck = isRendererOnlyUiType,
|
|
395
|
+
/** Whether the stored value is an expression — see below. */
|
|
396
|
+
isExpression?: boolean
|
|
365
397
|
): TQorusType => {
|
|
366
398
|
const schemaOption = schema?.[optionName];
|
|
399
|
+
/* A STORED type does not describe an expression.
|
|
400
|
+
|
|
401
|
+
An expression value is `{ is_expression: true, value: {...} }`, so the type
|
|
402
|
+
recorded beside it describes that envelope — `hash` — and not what the
|
|
403
|
+
field holds. Letting it win means the SCHEMA's declared type is ignored and
|
|
404
|
+
the wrong answer is handed to the expression editor as the return type to
|
|
405
|
+
check against: `1 + 2` on a field declared `"type": "auto"` came back
|
|
406
|
+
"The expression returns int, and this field holds hash". A literal `1` was
|
|
407
|
+
fine, because a literal is stored as a plain value whose type matches.
|
|
408
|
+
|
|
409
|
+
`auto`/`any` accept anything, which is exactly why nothing should be
|
|
410
|
+
checked against them; a concrete declared type still is. The schema is the
|
|
411
|
+
only party that knows what the field accepts. */
|
|
367
412
|
const storedType =
|
|
368
|
-
|
|
413
|
+
(
|
|
414
|
+
!isExpression &&
|
|
415
|
+
fieldType &&
|
|
416
|
+
!(fieldType === schemaOption?.ui_type && isRendererOnly(fieldType))
|
|
417
|
+
) ?
|
|
369
418
|
fieldType
|
|
370
419
|
: getOptionSchemaStorageType(schemaOption, isRendererOnly);
|
|
371
420
|
|
|
@@ -557,6 +606,85 @@ const normalizeEmptyFieldValues = (fields: TQorusForm | TQorusFlatForm | undefin
|
|
|
557
606
|
{} as TQorusForm
|
|
558
607
|
);
|
|
559
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Does this rebuild say anything the parent does not already hold?
|
|
611
|
+
*
|
|
612
|
+
* `false` means the rebuild is a MIRROR: `fixOptions` found no default to
|
|
613
|
+
* materialise, so the result repeats the `value` the parent handed down. The
|
|
614
|
+
* compare is empty-normalized because `fixOptions` round-trips a required-empty
|
|
615
|
+
* field between `{ value: '' }` and no `value` key, and that cosmetic
|
|
616
|
+
* difference would otherwise read as new information forever.
|
|
617
|
+
*/
|
|
618
|
+
export const isMirrorOfValue = (
|
|
619
|
+
fixedValue: TQorusForm | TQorusFlatForm | undefined,
|
|
620
|
+
value: TQorusForm | TQorusFlatForm | undefined
|
|
621
|
+
): boolean => isEqual(normalizeEmptyFieldValues(fixedValue), normalizeEmptyFieldValues(value));
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* What this rebuild lets the form agree with the parent on, if anything.
|
|
625
|
+
*
|
|
626
|
+
* Returns the rebuild when it mirrors `value`, and `undefined` when it
|
|
627
|
+
* contributed something the parent has yet to learn.
|
|
628
|
+
*
|
|
629
|
+
* Adopting the parent's value IS agreement, and saying so matters as much as
|
|
630
|
+
* staying quiet about it. The sync-down skips only when the parent echoed what
|
|
631
|
+
* this form last sent; if adopting a value never updates that record, it points
|
|
632
|
+
* at an older state forever, the skip can never fire again, and the form
|
|
633
|
+
* rebuilds on every single change. Holding the mirror back WITHOUT recording
|
|
634
|
+
* the agreement was measured making the starvation worse, not better — 8 s
|
|
635
|
+
* became a 45 s timeout — which is why the two belong together.
|
|
636
|
+
*/
|
|
637
|
+
export const agreementFromRebuild = (
|
|
638
|
+
fixedValue: TQorusForm | TQorusFlatForm | undefined,
|
|
639
|
+
value: TQorusForm | TQorusFlatForm | undefined
|
|
640
|
+
): TQorusForm | TQorusFlatForm | undefined =>
|
|
641
|
+
isMirrorOfValue(fixedValue, value) ? fixedValue : undefined;
|
|
642
|
+
|
|
643
|
+
/** The state the emit decision is made against. */
|
|
644
|
+
export interface IEmitDecision {
|
|
645
|
+
/** The form's own copy of the value, as it stands now. */
|
|
646
|
+
localValue: TQorusForm | TQorusFlatForm | undefined;
|
|
647
|
+
/** The `value` prop, read at the moment the decision is made. */
|
|
648
|
+
value: TQorusForm | TQorusFlatForm | undefined;
|
|
649
|
+
/**
|
|
650
|
+
* The last rebuild that mirrored `value` without adding anything, if the most
|
|
651
|
+
* recent one did. `undefined` when the last rebuild contributed something.
|
|
652
|
+
*/
|
|
653
|
+
mirroredValue: TQorusForm | TQorusFlatForm | undefined;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Should the form report `localValue` to its parent as an answer?
|
|
658
|
+
*
|
|
659
|
+
* Extracted and pure because the situation it exists for cannot be reproduced
|
|
660
|
+
* through a rendered form: it needs the emit to be decided against a `value`
|
|
661
|
+
* NEWER than the `localValue` built from it, which only happens when two
|
|
662
|
+
* components' effects land in one React commit — and a test renderer flushes
|
|
663
|
+
* effects between updates, so the two never fall out of step. The states below
|
|
664
|
+
* were recorded from a live instrumented run instead, and this is what makes
|
|
665
|
+
* them assertable.
|
|
666
|
+
*
|
|
667
|
+
* Two ways the answer is no:
|
|
668
|
+
*
|
|
669
|
+
* 1. `localValue` is a mirror — a rebuild of the parent's own value that added
|
|
670
|
+
* nothing. Repeating it back is not an answer. **Checked first**, because
|
|
671
|
+
* the comparison in (2) reads whatever `value` has become by now, which,
|
|
672
|
+
* when a nested engine shares the parent value, is already that engine's
|
|
673
|
+
* newer write; comparing against it would find a difference this form never
|
|
674
|
+
* made and emit the older mirror over the newer write. That is the loop.
|
|
675
|
+
* 2. `localValue` already equals `value` — there is nothing to report.
|
|
676
|
+
*/
|
|
677
|
+
export const shouldEmitLocalValue = ({
|
|
678
|
+
localValue,
|
|
679
|
+
value,
|
|
680
|
+
mirroredValue,
|
|
681
|
+
}: IEmitDecision): boolean => {
|
|
682
|
+
if (mirroredValue !== undefined && isEqual(localValue, mirroredValue)) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
return !isEqual(localValue, value);
|
|
686
|
+
};
|
|
687
|
+
|
|
560
688
|
export const flattenOptions = (options: TQorusForm): TQorusFlatForm => {
|
|
561
689
|
return reduce(
|
|
562
690
|
options,
|
|
@@ -653,6 +781,13 @@ export interface IFormEngineProps extends Omit<IReqoreCollectionProps, 'onChange
|
|
|
653
781
|
recordRequiresSearchOptions?: boolean;
|
|
654
782
|
readOnly?: boolean;
|
|
655
783
|
allowTemplates?: boolean;
|
|
784
|
+
/**
|
|
785
|
+
* The form's SHARED template vocabulary — config items, system properties —
|
|
786
|
+
* offered to every field that supports templates.
|
|
787
|
+
*
|
|
788
|
+
* A field may also declare `templates` of its own in the schema, and that
|
|
789
|
+
* narrower list wins for that field: see {@link TFieldWithOwnTemplates}.
|
|
790
|
+
*/
|
|
656
791
|
stringTemplates?: IReqoreFormTemplates;
|
|
657
792
|
/** Opt-in: fetch global templates from `system/getContextData` for this context. */
|
|
658
793
|
interfaceContext?: string;
|
|
@@ -727,13 +862,29 @@ export interface IFormEngineProps extends Omit<IReqoreCollectionProps, 'onChange
|
|
|
727
862
|
* the reader is choosing WHICH to open rather than reading this one, e.g. a
|
|
728
863
|
* template's per-action option forms stacked in a drawer.
|
|
729
864
|
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
865
|
+
* One escape is not negotiable: a box always opens while a search query is
|
|
866
|
+
* running (`ReqorePanel` unmounts collapsed content, so a match inside a
|
|
867
|
+
* closed box would be unreachable).
|
|
868
|
+
*
|
|
869
|
+
* A second applies only by DEFAULT: the Optional box stays open when every
|
|
870
|
+
* row is optional, because a form that collapsed its only box would render a
|
|
871
|
+
* card that looks empty and broken. Passing this prop explicitly waives it —
|
|
872
|
+
* a host that supplies its own heading and explanation around the form (a
|
|
873
|
+
* run-options panel, say) has already told the reader what the card is, and
|
|
874
|
+
* asked for the settings to start folded away.
|
|
735
875
|
*/
|
|
736
876
|
compactCollapsedGroups?: TFormEngineBoxKey[];
|
|
877
|
+
/**
|
|
878
|
+
* Compact mode only: rename the status boxes.
|
|
879
|
+
*
|
|
880
|
+
* The three are `Needs attention`, `Set` and `Optional`, which read correctly
|
|
881
|
+
* when the form is the page's subject. Inside a host that already named the
|
|
882
|
+
* subject — a panel headed "Change a setting for this run" — a bare
|
|
883
|
+
* `Optional` names a category rather than the things in it, and
|
|
884
|
+
* `Optional settings` is what the reader is looking at. Only the labels
|
|
885
|
+
* change; bucketing, icons and intents are untouched.
|
|
886
|
+
*/
|
|
887
|
+
compactBoxLabels?: Partial<Record<TFormEngineBoxKey, string>>;
|
|
737
888
|
/**
|
|
738
889
|
* Compact mode only: which parts of the form's toolbar to show. `true`
|
|
739
890
|
* (default) shows all of it; `false` hides the whole thing.
|
|
@@ -781,6 +932,37 @@ export interface IFormEngineProps extends Omit<IReqoreCollectionProps, 'onChange
|
|
|
781
932
|
* a consumer injecting many actions can never blow out the row.
|
|
782
933
|
*/
|
|
783
934
|
optionActionsCollapse?: 'auto' | 'always' | 'never';
|
|
935
|
+
/**
|
|
936
|
+
* The HOST is still loading something this form needs.
|
|
937
|
+
*
|
|
938
|
+
* Declared because it was already read (`rest.skeleton`) and honoured by the
|
|
939
|
+
* loading gate, but undeclared props are invisible to the people who need
|
|
940
|
+
* them — and the reason to reach for this is not obvious.
|
|
941
|
+
*
|
|
942
|
+
* A host that resolves anything before it can render a form — an option list
|
|
943
|
+
* fetched by `get_message`, a schema id resolved over the wire — has a wait of
|
|
944
|
+
* its own that ends just as this component's begins. Drawing its own
|
|
945
|
+
* placeholder for that first half and then handing over produces TWO
|
|
946
|
+
* placeholders in series for one load, from two different components: the DOM
|
|
947
|
+
* node is replaced, the geometry can differ, and the page relayouts in the
|
|
948
|
+
* middle of waiting. Measured on the Qorus alert rule, whose three sections
|
|
949
|
+
* each did this — six placeholder mounts where three would do.
|
|
950
|
+
*
|
|
951
|
+
* Passing the host's own wait in here instead makes ONE component own the
|
|
952
|
+
* whole wait, from the host's first fetch to this form's last, with one
|
|
953
|
+
* placeholder and one DOM node.
|
|
954
|
+
*/
|
|
955
|
+
skeleton?: boolean;
|
|
956
|
+
/**
|
|
957
|
+
* What the HOST is waiting for, when it hands its wait down via `skeleton`.
|
|
958
|
+
*
|
|
959
|
+
* Without it every host wait reads as the same word and the reading stops at
|
|
960
|
+
* the package boundary — measured on the live alert rule, three sections all
|
|
961
|
+
* reported `host` and the next step was guessing which of the host's own five
|
|
962
|
+
* conditions had fired. Surfaced as `data-wait` on the placeholder.
|
|
963
|
+
*/
|
|
964
|
+
skeletonReason?: string;
|
|
965
|
+
|
|
784
966
|
/**
|
|
785
967
|
* SEAM (reqraft): consumer-injected field editors for types reqraft doesn't
|
|
786
968
|
* ship (IDE domain fields). Keyed by field `type`/`ui_type`; forwarded through
|
|
@@ -817,6 +999,28 @@ export interface IFormEngineProps extends Omit<IReqoreCollectionProps, 'onChange
|
|
|
817
999
|
* consumer's new editor no longer needs a reqraft release to be handled.
|
|
818
1000
|
*/
|
|
819
1001
|
rendererOnlyUiTypes?: string[];
|
|
1002
|
+
/**
|
|
1003
|
+
* The `ui_type` names whose own editor renders templates as chips INLINE.
|
|
1004
|
+
*
|
|
1005
|
+
* For these the template selector is suppressed and the field's editor is
|
|
1006
|
+
* shown instead — the selector would replace a control the author can type in
|
|
1007
|
+
* with one they can only pick from. Merged with reqraft's built-in list
|
|
1008
|
+
* (`richtext`), the same way `rendererOnlyUiTypes` is.
|
|
1009
|
+
*/
|
|
1010
|
+
templateAwareUiTypes?: string[];
|
|
1011
|
+
/**
|
|
1012
|
+
* Extra `ui_type` names that mean "a value of any type", for the purpose of
|
|
1013
|
+
* offering the per-type choices in the row's menu.
|
|
1014
|
+
*
|
|
1015
|
+
* A field that pins no concrete type offers "enter a value of type X" for
|
|
1016
|
+
* each type the form knows. That was gated on the schema saying
|
|
1017
|
+
* `ui_type: 'any'` alone, which misses two cases: an option that declares its
|
|
1018
|
+
* untypedness as `type: 'auto'` and carries no `ui_type` at all (the server
|
|
1019
|
+
* spells an assertion's Expected Value exactly that way), and an option whose
|
|
1020
|
+
* `ui_type` is a HOST's own untyped editor. Merged with reqraft's built-ins
|
|
1021
|
+
* (`any`, `auto`), the same way `templateAwareUiTypes` is.
|
|
1022
|
+
*/
|
|
1023
|
+
anyLikeUiTypes?: string[];
|
|
820
1024
|
|
|
821
1025
|
/**
|
|
822
1026
|
* Bag of values forwarded from an outer FormEngine scope, used as a
|
|
@@ -878,12 +1082,17 @@ export interface IFormEngineProps extends Omit<IReqoreCollectionProps, 'onChange
|
|
|
878
1082
|
* for the pane to exist at all — so a pasted or reloaded link lands on a
|
|
879
1083
|
* collapsed form.
|
|
880
1084
|
*
|
|
881
|
-
* Applied once, on the first render
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
886
|
-
*
|
|
1085
|
+
* Applied once, on the first render that actually CONTAINS one of the named
|
|
1086
|
+
* rows — not merely the first render with any rows — so a schema arriving in
|
|
1087
|
+
* pieces still gets its address applied rather than spending the one shot on
|
|
1088
|
+
* a form the field had not reached yet.
|
|
1089
|
+
*
|
|
1090
|
+
* It never re-expands a row the user has since collapsed, and it does not
|
|
1091
|
+
* participate in `expandMode: 'single'` accordion collapsing — the caller is
|
|
1092
|
+
* naming a starting point, not driving the state. That holds against the
|
|
1093
|
+
* engine's own openers too: `expandFirstRequired` / `autoFocusFirstRequired`
|
|
1094
|
+
* open their target ALONGSIDE these rows in single mode instead of replacing
|
|
1095
|
+
* them. Only a remount re-arms it. No-op in classic (non-compact) mode.
|
|
887
1096
|
*/
|
|
888
1097
|
initialExpandedOptions?: string[];
|
|
889
1098
|
|
|
@@ -941,7 +1150,8 @@ const FormEngineImpl = ({
|
|
|
941
1150
|
compactNested = false,
|
|
942
1151
|
compactScroll = 'host',
|
|
943
1152
|
compactPanelProps,
|
|
944
|
-
compactCollapsedGroups
|
|
1153
|
+
compactCollapsedGroups: compactCollapsedGroupsProp,
|
|
1154
|
+
compactBoxLabels,
|
|
945
1155
|
compactToolbar = true,
|
|
946
1156
|
commitMode = 'immediate',
|
|
947
1157
|
expandMode = 'single',
|
|
@@ -959,6 +1169,8 @@ const FormEngineImpl = ({
|
|
|
959
1169
|
// onto a DOM node
|
|
960
1170
|
markdownRenderer: _markdownRenderer, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
961
1171
|
rendererOnlyUiTypes,
|
|
1172
|
+
templateAwareUiTypes,
|
|
1173
|
+
anyLikeUiTypes,
|
|
962
1174
|
inheritedFromParent,
|
|
963
1175
|
autoFocusFirstRequired,
|
|
964
1176
|
expandFirstRequired,
|
|
@@ -987,7 +1199,17 @@ const FormEngineImpl = ({
|
|
|
987
1199
|
optionActionsCollapse === 'always' ? true
|
|
988
1200
|
: optionActionsCollapse === 'never' ? false
|
|
989
1201
|
: !isHoverCapable || !!isMobile;
|
|
990
|
-
|
|
1202
|
+
/* The schema, as the form should ASK it — not always as it was served.
|
|
1203
|
+
`resolveDegenerateRequiredGroups` turns a one-of group that has a single
|
|
1204
|
+
member in this schema back into a plain required field, because a choice
|
|
1205
|
+
with one option is not a choice. Applied here, at the one place the schema
|
|
1206
|
+
enters the component, so the header, the chips, the messages, the meter and
|
|
1207
|
+
the read-first summary cannot disagree about it: every one of them reads
|
|
1208
|
+
`options`, and none of them has to know the rule. */
|
|
1209
|
+
const [servedOptions, setOptions] = useState<IQorusFormSchema | undefined>(
|
|
1210
|
+
rest?.options || undefined
|
|
1211
|
+
);
|
|
1212
|
+
const options = useMemo(() => resolveDegenerateRequiredGroups(servedOptions), [servedOptions]);
|
|
991
1213
|
// optionsLoader lifecycle: loading feeds the skeleton gate, error the banner.
|
|
992
1214
|
const [optionsLoading, setOptionsLoading] = useState<boolean>(!!optionsLoader && !rest?.options);
|
|
993
1215
|
const [optionsError, setOptionsError] = useState<string | undefined>();
|
|
@@ -1198,6 +1420,32 @@ const FormEngineImpl = ({
|
|
|
1198
1420
|
// Track the last value we emitted via onChange so we can skip re-applying fixOptions
|
|
1199
1421
|
// when the parent echoes it back as the new value prop (controlled component loop prevention)
|
|
1200
1422
|
const lastEmittedValue = useRef<TQorusForm | TQorusFlatForm | undefined>(value);
|
|
1423
|
+
/**
|
|
1424
|
+
* A `localValue` that is only a MIRROR of the incoming `value`, and so must
|
|
1425
|
+
* never be emitted back as though this form had answered something.
|
|
1426
|
+
*
|
|
1427
|
+
* `localValue` is derived state: the sync-down effect below rebuilds it from
|
|
1428
|
+
* the `value` prop. When that rebuild adds nothing (`fixOptions` had no
|
|
1429
|
+
* default to materialise), the result says exactly what the parent already
|
|
1430
|
+
* holds, and emitting it is not a change — it is this form repeating the
|
|
1431
|
+
* parent's own words back at it.
|
|
1432
|
+
*
|
|
1433
|
+
* That repetition is harmless in a form that owns its state outright and
|
|
1434
|
+
* fatal in one that shares a parent with a NESTED engine. The two write to
|
|
1435
|
+
* the same parent value, so each sees the other's write arrive as an external
|
|
1436
|
+
* change; `lastEmittedValue` is then always one step behind and its guard
|
|
1437
|
+
* never fires. The engines alternate between two states forever, several
|
|
1438
|
+
* times a second, and the newer write is repeatedly overwritten by the older
|
|
1439
|
+
* one. Measured in the test editor: the row's summary flipped between two
|
|
1440
|
+
* readings indefinitely and `setTimeout` was starved past a 45-second
|
|
1441
|
+
* deadline.
|
|
1442
|
+
*
|
|
1443
|
+
* Only a mirror that CHANGES nothing is held back. A rebuild that restores a
|
|
1444
|
+
* required or preselected field's default still emits, because that default
|
|
1445
|
+
* is genuinely new information the parent has to be told about — which is the
|
|
1446
|
+
* whole reason the sync-down runs `fixOptions` at all.
|
|
1447
|
+
*/
|
|
1448
|
+
const mirroredValue = useRef<TQorusForm | TQorusFlatForm | undefined>(undefined);
|
|
1201
1449
|
|
|
1202
1450
|
if (originalValue.current === undefined && size(value)) {
|
|
1203
1451
|
originalValue.current = localValue.fields;
|
|
@@ -1208,7 +1456,13 @@ const FormEngineImpl = ({
|
|
|
1208
1456
|
const templates = useTemplates(allowTemplates, rest.stringTemplates, interfaceContext);
|
|
1209
1457
|
|
|
1210
1458
|
useEffect(() => {
|
|
1211
|
-
if (
|
|
1459
|
+
if (
|
|
1460
|
+
!shouldEmitLocalValue({
|
|
1461
|
+
localValue: localValue.fields,
|
|
1462
|
+
value,
|
|
1463
|
+
mirroredValue: mirroredValue.current,
|
|
1464
|
+
})
|
|
1465
|
+
) {
|
|
1212
1466
|
return;
|
|
1213
1467
|
}
|
|
1214
1468
|
|
|
@@ -1371,9 +1625,12 @@ const FormEngineImpl = ({
|
|
|
1371
1625
|
// Note: compare fixedValue against value, not localValue.fields — localValue may have been
|
|
1372
1626
|
// updated by nested FormEngine emissions, so comparing against it would never skip.
|
|
1373
1627
|
const normalizedValue = normalizeEmptyFieldValues(value);
|
|
1628
|
+
/* What this rebuild lets us agree with the parent on — `undefined` when it
|
|
1629
|
+
contributed something the parent has yet to learn. */
|
|
1630
|
+
const agreed = agreementFromRebuild(fixedValue, value);
|
|
1374
1631
|
if (
|
|
1375
1632
|
isEqual(normalizedValue, normalizeEmptyFieldValues(lastEmittedValue.current)) &&
|
|
1376
|
-
|
|
1633
|
+
agreed !== undefined
|
|
1377
1634
|
) {
|
|
1378
1635
|
return;
|
|
1379
1636
|
}
|
|
@@ -1382,11 +1639,39 @@ const FormEngineImpl = ({
|
|
|
1382
1639
|
originalValue.current = fixedValue;
|
|
1383
1640
|
}
|
|
1384
1641
|
|
|
1642
|
+
/* Remember a mirror so the emit effect can tell one from an answer, and
|
|
1643
|
+
record the agreement it represents. A rebuild that DID add something
|
|
1644
|
+
leaves both alone — the parent has to learn about it. */
|
|
1645
|
+
mirroredValue.current = agreed;
|
|
1646
|
+
if (agreed !== undefined) {
|
|
1647
|
+
lastEmittedValue.current = agreed;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1385
1650
|
setLocalValue?.({ fields: fixedValue, meta: undefined });
|
|
1386
1651
|
}, [JSON.stringify(options), JSON.stringify(value), isRendererOnly]);
|
|
1387
1652
|
|
|
1653
|
+
/**
|
|
1654
|
+
* Fields the reader has actually edited in this instance.
|
|
1655
|
+
*
|
|
1656
|
+
* "This field is required" is an ERROR message, and an error is a report that
|
|
1657
|
+
* something went wrong. Under a field nobody has been in yet it reports
|
|
1658
|
+
* nothing — the form is empty because it is new — while the requirement is
|
|
1659
|
+
* already stated by the asterisk, by the Needs-attention box the row sits in,
|
|
1660
|
+
* and by the completion meter. A form whose first act is to accuse the reader
|
|
1661
|
+
* of a mistake they have not made teaches them to discount its warnings.
|
|
1662
|
+
*
|
|
1663
|
+
* So the message waits for a touch. Being IN the field and leaving it empty
|
|
1664
|
+
* is a real gap, and that one shows. A ref, not state: every edit re-renders
|
|
1665
|
+
* anyway, so the following render reads the current set with no second pass.
|
|
1666
|
+
*/
|
|
1667
|
+
const touchedOptionsRef = useRef<Set<string>>(new Set());
|
|
1668
|
+
|
|
1388
1669
|
const handleValueChange = useCallback(
|
|
1389
1670
|
(optionName: string, val?: any, _type?: string, isFunction?: boolean) => {
|
|
1671
|
+
// Every route into this handler is a person acting: typing, picking from a
|
|
1672
|
+
// menu, clearing the value, adding an optional field. Marked here rather
|
|
1673
|
+
// than inside the updater below, which React may invoke twice.
|
|
1674
|
+
touchedOptionsRef.current.add(optionName);
|
|
1390
1675
|
setLocalValue(({ fields = {} }) => {
|
|
1391
1676
|
const schemaType = getOptionSchemaStorageType(options?.[optionName], isRendererOnly);
|
|
1392
1677
|
const isAnyLike = schemaType === 'any' || schemaType === 'auto';
|
|
@@ -1441,7 +1726,19 @@ const FormEngineImpl = ({
|
|
|
1441
1726
|
},
|
|
1442
1727
|
};
|
|
1443
1728
|
|
|
1444
|
-
|
|
1729
|
+
/* The flag belongs to the VALUE, so a change that said nothing about it
|
|
1730
|
+
does not throw it away — `undefined` is no opinion, not a denial.
|
|
1731
|
+
Deleting it unconditionally left the AST on the row with nothing
|
|
1732
|
+
marking it as one: an assertion's Expected Value came back from a
|
|
1733
|
+
saved draft drawn as a two-field hash of `exp` and `args`, because
|
|
1734
|
+
merely opening the row and reverting had stripped the flag. */
|
|
1735
|
+
if (
|
|
1736
|
+
shouldMarkAsExpression(
|
|
1737
|
+
isFunction,
|
|
1738
|
+
val,
|
|
1739
|
+
((fields as TQorusForm)[optionName] as { is_expression?: boolean })?.is_expression
|
|
1740
|
+
)
|
|
1741
|
+
) {
|
|
1445
1742
|
(updatedValue[optionName] as { is_expression?: boolean }).is_expression = true;
|
|
1446
1743
|
} else {
|
|
1447
1744
|
delete updatedValue[optionName].is_expression;
|
|
@@ -1455,9 +1752,24 @@ const FormEngineImpl = ({
|
|
|
1455
1752
|
val !== (fields as TQorusForm)[optionName]?.value
|
|
1456
1753
|
) {
|
|
1457
1754
|
forEach(options, (option, depName) => {
|
|
1755
|
+
/* A dependency is `name`, `name=value` or `name!=value`, so the
|
|
1756
|
+
entry has to be PARSED to find which option it names. Compared as
|
|
1757
|
+
a whole string, only the bare spelling ever matched — so a field
|
|
1758
|
+
declared `depends_on: ['subject_iface_kind=workflow']` was never
|
|
1759
|
+
cleared when that option changed.
|
|
1760
|
+
|
|
1761
|
+
What that cost: a test whose subject was switched from a workflow
|
|
1762
|
+
to a service kept the workflow's `Subject Interface Version`.
|
|
1763
|
+
Services and jobs are versioned but only the latest is testable,
|
|
1764
|
+
so the field no longer applied, yet it stayed on the form as a
|
|
1765
|
+
locked control in front of a value the author could not clear —
|
|
1766
|
+
the value being there is exactly what stops the field being
|
|
1767
|
+
withheld outright. */
|
|
1458
1768
|
if (
|
|
1459
1769
|
option.depends_on &&
|
|
1460
|
-
flatten(option.depends_on).
|
|
1770
|
+
flatten(option.depends_on).some(
|
|
1771
|
+
(dependency) => parseDependency(dependency as string).name === optionName
|
|
1772
|
+
) &&
|
|
1461
1773
|
updatedValue[depName]
|
|
1462
1774
|
) {
|
|
1463
1775
|
updatedValue[depName].value = undefined;
|
|
@@ -1637,6 +1949,7 @@ const FormEngineImpl = ({
|
|
|
1637
1949
|
return newValue;
|
|
1638
1950
|
}
|
|
1639
1951
|
|
|
1952
|
+
|
|
1640
1953
|
const rendererType = getType(
|
|
1641
1954
|
(options[optionName].ui_type || options[optionName].type) as TQorusType,
|
|
1642
1955
|
operators,
|
|
@@ -1648,7 +1961,8 @@ const FormEngineImpl = ({
|
|
|
1648
1961
|
options,
|
|
1649
1962
|
operators,
|
|
1650
1963
|
(option as IQorusFormField)?.op,
|
|
1651
|
-
isRendererOnly
|
|
1964
|
+
isRendererOnly,
|
|
1965
|
+
optionHoldsExpression(option)
|
|
1652
1966
|
);
|
|
1653
1967
|
|
|
1654
1968
|
if (!isPlainObject(option)) {
|
|
@@ -1793,7 +2107,8 @@ const FormEngineImpl = ({
|
|
|
1793
2107
|
options,
|
|
1794
2108
|
operators,
|
|
1795
2109
|
(option as IQorusFormField).op,
|
|
1796
|
-
isRendererOnly
|
|
2110
|
+
isRendererOnly,
|
|
2111
|
+
optionHoldsExpression(option)
|
|
1797
2112
|
);
|
|
1798
2113
|
const optionValue = (option as IQorusFormField).value;
|
|
1799
2114
|
|
|
@@ -1864,7 +2179,8 @@ const FormEngineImpl = ({
|
|
|
1864
2179
|
options,
|
|
1865
2180
|
operators,
|
|
1866
2181
|
(option as IQorusFormField).op,
|
|
1867
|
-
isRendererOnly
|
|
2182
|
+
isRendererOnly,
|
|
2183
|
+
optionHoldsExpression(option)
|
|
1868
2184
|
),
|
|
1869
2185
|
(option as IQorusFormField).value
|
|
1870
2186
|
)
|
|
@@ -1913,7 +2229,8 @@ const FormEngineImpl = ({
|
|
|
1913
2229
|
options,
|
|
1914
2230
|
operators,
|
|
1915
2231
|
(availableOptions as TQorusForm)?.[name]?.op,
|
|
1916
|
-
isRendererOnly
|
|
2232
|
+
isRendererOnly,
|
|
2233
|
+
optionHoldsExpression((availableOptions as TQorusForm)?.[name])
|
|
1917
2234
|
);
|
|
1918
2235
|
const value = (availableOptions as TQorusForm)?.[name]?.value;
|
|
1919
2236
|
// A schema-declared hash whose every entry is a materialised-but-unset
|
|
@@ -2064,11 +2381,16 @@ const FormEngineImpl = ({
|
|
|
2064
2381
|
if (!names.length) {
|
|
2065
2382
|
return;
|
|
2066
2383
|
}
|
|
2067
|
-
hasAppliedInitialExpansionRef.current = true;
|
|
2068
2384
|
const toExpand = initialExpandedOptions.filter((name) => names.includes(name));
|
|
2069
2385
|
if (!toExpand.length) {
|
|
2386
|
+
// Not "there is no such row" — "not YET". A server-driven schema arrives
|
|
2387
|
+
// in pieces, and the row the caller named can be in a later one (a test's
|
|
2388
|
+
// `cases` field materialises once its step-kind catalogue lands). Latching
|
|
2389
|
+
// here would spend the one shot on a form that did not yet contain the
|
|
2390
|
+
// field, and the address would never be applied at all.
|
|
2070
2391
|
return;
|
|
2071
2392
|
}
|
|
2393
|
+
hasAppliedInitialExpansionRef.current = true;
|
|
2072
2394
|
setExpandedOptions((prev) => [...prev, ...toExpand.filter((name) => !prev.includes(name))]);
|
|
2073
2395
|
}, [initialExpandedOptions, compact, availableOptions]);
|
|
2074
2396
|
|
|
@@ -2159,7 +2481,13 @@ const FormEngineImpl = ({
|
|
|
2159
2481
|
setExpandedOptions((prev) =>
|
|
2160
2482
|
prev.includes(target) ? prev
|
|
2161
2483
|
: expandMode === 'multi' ? [...prev, target]
|
|
2162
|
-
|
|
2484
|
+
// `expandMode: 'single'` is a rule about what a CLICK does: opening a
|
|
2485
|
+
// row closes the one the reader last opened. It is not a licence to
|
|
2486
|
+
// close a row the HOST named through `initialExpandedOptions`. That
|
|
2487
|
+
// is an address — a statement about what the page is FOR, made before
|
|
2488
|
+
// the reader did anything — and this scan is a convenience layered on
|
|
2489
|
+
// top of it. Keep the named rows; replace only what a click could.
|
|
2490
|
+
: [...prev.filter((name) => initialExpandedOptions?.includes(name)), target]
|
|
2163
2491
|
);
|
|
2164
2492
|
}
|
|
2165
2493
|
}, [
|
|
@@ -2172,13 +2500,18 @@ const FormEngineImpl = ({
|
|
|
2172
2500
|
dependencyLockedNames,
|
|
2173
2501
|
getOptionBucket,
|
|
2174
2502
|
expandMode,
|
|
2503
|
+
initialExpandedOptions,
|
|
2175
2504
|
]);
|
|
2176
2505
|
|
|
2177
2506
|
// Read-first completion summary (how many shown options have a value set),
|
|
2178
2507
|
// surfaced as a progress meter at the top of the compact form.
|
|
2179
2508
|
const readFirstCompletion = useMemo(
|
|
2180
|
-
() =>
|
|
2181
|
-
|
|
2509
|
+
() =>
|
|
2510
|
+
getReadFirstCompletion(
|
|
2511
|
+
availableOptions as Record<string, IQorusFormField | undefined>,
|
|
2512
|
+
(name) => getOptionBucket(name) === 'attention'
|
|
2513
|
+
),
|
|
2514
|
+
[JSON.stringify(availableOptions), getOptionBucket]
|
|
2182
2515
|
);
|
|
2183
2516
|
|
|
2184
2517
|
const optionalFields = useMemo(
|
|
@@ -2273,12 +2606,58 @@ const FormEngineImpl = ({
|
|
|
2273
2606
|
const optionSchema = options?.[optionName];
|
|
2274
2607
|
const schemaUiType = optionSchema?.ui_type as TQorusType;
|
|
2275
2608
|
const uiTypeIsAnyLike = schemaUiType === 'any' || schemaUiType === 'auto';
|
|
2609
|
+
/* An EXPRESSION's stored type is skipped in this chain.
|
|
2610
|
+
|
|
2611
|
+
The value is `{ is_expression: true, value: {...} }`, so the type saved
|
|
2612
|
+
beside it describes that envelope — `hash` — not what the field holds.
|
|
2613
|
+
With no `ui_type` to win first, that `hash` beat the schema's `auto`
|
|
2614
|
+
and was handed to the expression editor as the return type to check
|
|
2615
|
+
against: `1 + 2` on a field declared `auto` came back "This does not
|
|
2616
|
+
fit. The expression returns int, and this field holds hash". A literal
|
|
2617
|
+
`1` was fine, because a literal is stored as a plain value whose type
|
|
2618
|
+
matches.
|
|
2619
|
+
|
|
2620
|
+
Dropping the stored type here lets the schema decide, which is the
|
|
2621
|
+
only party that knows what the field accepts. This is the row
|
|
2622
|
+
renderer's OWN resolution and does not go through
|
|
2623
|
+
`getOptionFieldStorageType`, so guarding that one alone left this
|
|
2624
|
+
untouched. */
|
|
2625
|
+
const storedRowType = optionHoldsExpression(other) ? undefined : type;
|
|
2626
|
+
|
|
2627
|
+
/* A RELOADED expression is handed to the editor in the shape the editor
|
|
2628
|
+
was written against.
|
|
2629
|
+
|
|
2630
|
+
An expression is written as `{ type, is_expression: true, value: ast }`
|
|
2631
|
+
and saved as `{ type, value: { is_expression: true, value: ast } }` —
|
|
2632
|
+
the flag moves ONTO the value. The editor was only ever given
|
|
2633
|
+
`other.is_expression`, which is absent in the saved shape, so a
|
|
2634
|
+
reloaded expression was not recognised as one: an `auto` field then
|
|
2635
|
+
resolved its type from the value it could see, decided the envelope was
|
|
2636
|
+
a `hash`, and rendered `1 + 2` as a raw tree of `is_expression` /
|
|
2637
|
+
`exp` / `args` for the author to edit by hand.
|
|
2638
|
+
|
|
2639
|
+
Both halves have to move together. Flipping the flag alone would put
|
|
2640
|
+
the editor into expression mode and then hand it the ENVELOPE where it
|
|
2641
|
+
expects the expression, so the value is unwrapped here too — after
|
|
2642
|
+
which a saved expression is indistinguishable from one just typed,
|
|
2643
|
+
which is what every component downstream already assumes.
|
|
2644
|
+
|
|
2645
|
+
Reported as "after Run assertion the value shows as raw data", but the
|
|
2646
|
+
run is incidental: the field beside it, which no run touched, was
|
|
2647
|
+
equally raw. The trigger is a RELOAD, which is the only thing that puts
|
|
2648
|
+
an expression into its saved shape. */
|
|
2649
|
+
const nestedExpression = !!(other as { value?: { is_expression?: unknown } })?.value
|
|
2650
|
+
?.is_expression;
|
|
2651
|
+
const expressionAwareValue =
|
|
2652
|
+
nestedExpression ?
|
|
2653
|
+
(other as unknown as { value: { value: unknown } }).value.value
|
|
2654
|
+
: other.value;
|
|
2276
2655
|
const resolvedType =
|
|
2277
2656
|
(isFixedCompactAllowedValueOption(optionSchema) ?
|
|
2278
2657
|
getOptionSchemaStorageType(optionSchema, isRendererOnly)
|
|
2279
2658
|
: schemaUiType && !uiTypeIsAnyLike ? schemaUiType
|
|
2280
2659
|
: undefined) ||
|
|
2281
|
-
|
|
2660
|
+
storedRowType ||
|
|
2282
2661
|
schemaUiType ||
|
|
2283
2662
|
(optionSchema?.type as TQorusType) ||
|
|
2284
2663
|
'any';
|
|
@@ -2425,6 +2804,7 @@ const FormEngineImpl = ({
|
|
|
2425
2804
|
// SEAM: forwarded through TemplateField's rest-spread to AutoFormField,
|
|
2426
2805
|
// which renders consumer-injected editors by field type/ui_type.
|
|
2427
2806
|
componentOverrides={componentOverrides}
|
|
2807
|
+
templateAwareUiTypes={templateAwareUiTypes}
|
|
2428
2808
|
allowTemplates={!!(allowTemplates && options?.[optionName]?.supports_templates)}
|
|
2429
2809
|
allowFunctions={!!options?.[optionName]?.supports_expressions}
|
|
2430
2810
|
// reqraft: form-level expression fields get the Visual/Text shell
|
|
@@ -2441,7 +2821,25 @@ const FormEngineImpl = ({
|
|
|
2441
2821
|
isFixedCompactAllowedValueOption(optionSchema) ||
|
|
2442
2822
|
(options?.[optionName]?.supports_custom_values !== false && resolvedType !== 'any')
|
|
2443
2823
|
}
|
|
2444
|
-
|
|
2824
|
+
// A field may declare a template list OF ITS OWN, and when it does
|
|
2825
|
+
// that list is the one to offer. The engine-wide `stringTemplates`
|
|
2826
|
+
// are the form's shared vocabulary - config items, system properties
|
|
2827
|
+
// - and every field draws on the same set. A per-field list answers a
|
|
2828
|
+
// question only that field asks: a test assertion's `$.` references
|
|
2829
|
+
// are the values ITS case captured, and no other field in the form
|
|
2830
|
+
// should be offering them.
|
|
2831
|
+
//
|
|
2832
|
+
// Passing the engine's list unconditionally overwrote the schema's,
|
|
2833
|
+
// so a field that declared templates had none by the time it
|
|
2834
|
+
// rendered. On a typed field that only cost the picker; on an
|
|
2835
|
+
// ANY-LIKE one it changed the question, because `TemplateField`
|
|
2836
|
+
// opens on the template selector only when there is something to
|
|
2837
|
+
// pick and otherwise falls back to the type picker. An untyped field
|
|
2838
|
+
// therefore asked the author to choose `string`/`int`/`hash` before
|
|
2839
|
+
// it would let them name a value they had already captured.
|
|
2840
|
+
templates={
|
|
2841
|
+
(optionSchema as TFieldWithOwnTemplates | undefined)?.templates ?? templates.value
|
|
2842
|
+
}
|
|
2445
2843
|
{...getTypeAndCanBeNull(
|
|
2446
2844
|
// The RENDERER type picks the editor — the storage type lives on
|
|
2447
2845
|
// the value envelope. Passing storage here rendered a `richtext`
|
|
@@ -2462,8 +2860,8 @@ const FormEngineImpl = ({
|
|
|
2462
2860
|
key={optionName}
|
|
2463
2861
|
arg_schema={options?.[optionName]?.arg_schema}
|
|
2464
2862
|
noSoft={!!rest?.options}
|
|
2465
|
-
value={
|
|
2466
|
-
isFunction={(other
|
|
2863
|
+
value={expressionAwareValue}
|
|
2864
|
+
isFunction={optionHoldsExpression(other)}
|
|
2467
2865
|
isDefaultFunction={options?.[optionName]?.default_view === 'expression'}
|
|
2468
2866
|
sensitive={options?.[optionName]?.sensitive}
|
|
2469
2867
|
default_value={getDefaultValue(options?.[optionName])}
|
|
@@ -2487,7 +2885,7 @@ const FormEngineImpl = ({
|
|
|
2487
2885
|
readOnly={readOnly}
|
|
2488
2886
|
size={editorSize || rest.size}
|
|
2489
2887
|
menuItems={
|
|
2490
|
-
(options?.[optionName]
|
|
2888
|
+
offersTypeChoices(options?.[optionName], anyLikeUiTypes) ?
|
|
2491
2889
|
getCustomMenuTemplateItems(optionName)
|
|
2492
2890
|
: undefined
|
|
2493
2891
|
}
|
|
@@ -2500,6 +2898,7 @@ const FormEngineImpl = ({
|
|
|
2500
2898
|
name={optionName}
|
|
2501
2899
|
option={{ type: resolvedType, ...other }}
|
|
2502
2900
|
getType={getTypeForOption}
|
|
2901
|
+
untouched={!touchedOptionsRef.current.has(optionName)}
|
|
2503
2902
|
/>
|
|
2504
2903
|
{operators && size(operators) && size(other.op) ?
|
|
2505
2904
|
<>
|
|
@@ -2698,7 +3097,21 @@ const FormEngineImpl = ({
|
|
|
2698
3097
|
fields: false,
|
|
2699
3098
|
help: false,
|
|
2700
3099
|
},
|
|
2701
|
-
|
|
3100
|
+
/* From the SCHEMA, not from the value.
|
|
3101
|
+
*
|
|
3102
|
+
* `availableOptions` is keyed off the form's VALUE, so it grows as data
|
|
3103
|
+
* arrives — and the search row is rendered only when this is true
|
|
3104
|
+
* (`CompactToolbar`). A form whose value lands after its first render
|
|
3105
|
+
* therefore ADDED a 38px row to its header a beat later, and everything
|
|
3106
|
+
* below it moved. Measured on the Qorus IDE's test editor, on the nested
|
|
3107
|
+
* sub-form of a case: 33 elements jumped down 51px in one frame, on every
|
|
3108
|
+
* cold load.
|
|
3109
|
+
*
|
|
3110
|
+
* How many fields a form has is a property of its schema. The two agree
|
|
3111
|
+
* once the value has arrived — `fixOptions` materialises an entry per
|
|
3112
|
+
* declared field — so this only changes the answer during the load, which
|
|
3113
|
+
* is the whole point. */
|
|
3114
|
+
hasMultipleOptions: size(options) > 1,
|
|
2702
3115
|
compactQuery,
|
|
2703
3116
|
setCompactQuery,
|
|
2704
3117
|
requiredOnly,
|
|
@@ -2949,6 +3362,13 @@ const FormEngineImpl = ({
|
|
|
2949
3362
|
const bucketCount = (b: TFormEngineBoxKey) =>
|
|
2950
3363
|
bucketGroups[b].reduce((n, g) => n + buckets[b][g].length, 0);
|
|
2951
3364
|
|
|
3365
|
+
/* The default is to collapse the Optional box, and an explicit pass is
|
|
3366
|
+
what waives the "never collapse the sole box" escape below. Resolving it
|
|
3367
|
+
here rather than in the parameter list is what makes the two
|
|
3368
|
+
distinguishable — `['optional']` as a default and `['optional']` from a
|
|
3369
|
+
caller mean different things. */
|
|
3370
|
+
const compactCollapsedGroups = compactCollapsedGroupsProp ?? ['optional'];
|
|
3371
|
+
const collapsedGroupsRequested = compactCollapsedGroupsProp !== undefined;
|
|
2952
3372
|
// "Is the Optional box the whole form?" — when nothing needs attention and
|
|
2953
3373
|
// nothing is set, collapsing it leaves a card with no visible content at all.
|
|
2954
3374
|
const onlyOptionalRows =
|
|
@@ -2991,9 +3411,18 @@ const FormEngineImpl = ({
|
|
|
2991
3411
|
intent?: 'warning' | 'success';
|
|
2992
3412
|
icon: IReqoreIconName;
|
|
2993
3413
|
}> = [
|
|
2994
|
-
{
|
|
2995
|
-
|
|
2996
|
-
|
|
3414
|
+
{
|
|
3415
|
+
key: 'attention',
|
|
3416
|
+
label: compactBoxLabels?.attention ?? 'Needs attention',
|
|
3417
|
+
intent: 'warning',
|
|
3418
|
+
icon: 'ErrorWarningLine',
|
|
3419
|
+
},
|
|
3420
|
+
{ key: 'set', label: compactBoxLabels?.set ?? 'Set', intent: 'success', icon: 'CheckLine' },
|
|
3421
|
+
{
|
|
3422
|
+
key: 'optional',
|
|
3423
|
+
label: compactBoxLabels?.optional ?? 'Optional',
|
|
3424
|
+
icon: 'CheckboxBlankCircleLine',
|
|
3425
|
+
},
|
|
2997
3426
|
];
|
|
2998
3427
|
|
|
2999
3428
|
// Build the rows for one group: contiguous required-group members are pulled
|
|
@@ -3137,7 +3566,7 @@ const FormEngineImpl = ({
|
|
|
3137
3566
|
// Mirrors the toolbar's own search-row gate: when no search
|
|
3138
3567
|
// row renders, the header is a thin strip and sits tight to
|
|
3139
3568
|
// the first status box (see StyledCompactPanel).
|
|
3140
|
-
$tightHeader={!(compactToolbarParts?.search && size(
|
|
3569
|
+
$tightHeader={!(compactToolbarParts?.search && size(options) > 1)}
|
|
3141
3570
|
// The top-level form scrolls, so its toolbar STICKS and carries a
|
|
3142
3571
|
// dark blurred backdrop so content ghosts cleanly beneath it. A
|
|
3143
3572
|
// nested (arg_schema) sub-form owns no scroll context — drop the
|
|
@@ -3205,7 +3634,7 @@ const FormEngineImpl = ({
|
|
|
3205
3634
|
const wouldCollapse =
|
|
3206
3635
|
compactCollapsedGroups.includes(box.key) &&
|
|
3207
3636
|
!query &&
|
|
3208
|
-
!(box.key === 'optional' && onlyOptionalRows);
|
|
3637
|
+
!(box.key === 'optional' && onlyOptionalRows && !collapsedGroupsRequested);
|
|
3209
3638
|
// Only a box that was opened SOLELY to reveal a preselected
|
|
3210
3639
|
// row splits its body; a box that renders open anyway shows
|
|
3211
3640
|
// all of its groups as before.
|
|
@@ -3446,37 +3875,25 @@ const FormEngineImpl = ({
|
|
|
3446
3875
|
);
|
|
3447
3876
|
};
|
|
3448
3877
|
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
<ReqoreControlGroup fixed fill={false}>
|
|
3469
|
-
<ReqoreSkeleton />
|
|
3470
|
-
<ReqoreSkeleton />
|
|
3471
|
-
<ReqoreSkeleton width='100%' />
|
|
3472
|
-
</ReqoreControlGroup>
|
|
3473
|
-
<ReqoreControlGroup vertical fill={false}>
|
|
3474
|
-
<ReqoreSkeleton width='100%' height='150px' />
|
|
3475
|
-
<ReqoreSkeleton width='100%' height='150px' />
|
|
3476
|
-
<ReqoreSkeleton width='100%' height='150px' />
|
|
3477
|
-
</ReqoreControlGroup>
|
|
3478
|
-
</ReqoreControlGroup>
|
|
3479
|
-
);
|
|
3878
|
+
/* Which of the seven waits is holding the form, in the order they are
|
|
3879
|
+
tested. Rendered as `data-wait` on the placeholder so a page can be asked
|
|
3880
|
+
what it is waiting FOR instead of only that it is waiting. */
|
|
3881
|
+
const waitReason =
|
|
3882
|
+
rest.skeleton ? (rest.skeletonReason ? `host:${rest.skeletonReason}` : 'host')
|
|
3883
|
+
: templates.loading ? 'templates'
|
|
3884
|
+
: typesLoading ? 'types'
|
|
3885
|
+
: optionsLoading ? 'options'
|
|
3886
|
+
: loading ? 'fetch'
|
|
3887
|
+
: operatorsUrl && !operators ? 'operators'
|
|
3888
|
+
: (url || customUrl) && !options ? 'schema'
|
|
3889
|
+
: undefined;
|
|
3890
|
+
|
|
3891
|
+
if (waitReason) {
|
|
3892
|
+
/* The shape of the FORM, not a generic block arrangement. This used to be
|
|
3893
|
+
three bars over three big panels, which resembles nothing this gate is
|
|
3894
|
+
waiting for and shared no vocabulary with the other waits on the same
|
|
3895
|
+
page — see `FormFieldsSkeleton`. */
|
|
3896
|
+
return <FormFieldsSkeleton fill className='options-loading-skeleton' reason={waitReason} />;
|
|
3480
3897
|
}
|
|
3481
3898
|
|
|
3482
3899
|
// A loader that rejected (and produced no usable schema) surfaces its error
|