@qoretechnologies/reqraft 0.10.54 → 0.10.55-pr.115.g37abdfc
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_COMPACT.md +34 -0
- package/design/FORM_ENGINE_EXPRESSIONS.md +110 -0
- package/design/FORM_ENGINE_FIELD_MIGRATION.md +155 -1
- package/design/IDE_INTEGRATION.md +13 -0
- package/design/RENDER_EXPRESSION_TRANSPORT.md +76 -10
- package/design/SELECT_DIALOG_FLOOR.md +86 -0
- package/design/SMART_EDITOR_UX.md +21 -0
- package/design/UNAVAILABLE_ALLOWED_VALUES.md +226 -0
- package/dist/components/composer/Composer.d.ts +8 -3
- package/dist/components/composer/Composer.d.ts.map +1 -1
- package/dist/components/composer/Composer.js.map +1 -1
- 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 +414 -128
- 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 +272 -19
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +666 -161
- 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 +41 -0
- package/dist/components/form/engine/FormFieldsSkeleton.js.map +1 -0
- package/dist/components/form/engine/OptionFieldMessages.d.ts +108 -2
- package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
- package/dist/components/form/engine/OptionFieldMessages.js +203 -13
- 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 +68 -5
- package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
- package/dist/components/form/engine/compactRowContext.d.ts +15 -1
- package/dist/components/form/engine/compactRowContext.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowContext.js.map +1 -1
- package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowStyles.js +55 -29
- package/dist/components/form/engine/compactRowStyles.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/optionActions.d.ts +37 -1
- package/dist/components/form/engine/optionActions.d.ts.map +1 -1
- package/dist/components/form/engine/optionActions.js +71 -1
- package/dist/components/form/engine/optionActions.js.map +1 -1
- package/dist/components/form/engine/optionsContext.d.ts +22 -0
- package/dist/components/form/engine/optionsContext.d.ts.map +1 -0
- package/dist/components/form/engine/optionsContext.js +20 -0
- package/dist/components/form/engine/optionsContext.js.map +1 -0
- 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 +76 -8
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/engine/rendererTypes.d.ts +42 -3
- package/dist/components/form/engine/rendererTypes.d.ts.map +1 -1
- package/dist/components/form/engine/rendererTypes.js +3 -1
- package/dist/components/form/engine/rendererTypes.js.map +1 -1
- package/dist/components/form/engine/rowFocus.d.ts +30 -0
- package/dist/components/form/engine/rowFocus.d.ts.map +1 -0
- package/dist/components/form/engine/rowFocus.js +93 -0
- package/dist/components/form/engine/rowFocus.js.map +1 -0
- 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/rowOpenPicker.d.ts +27 -0
- package/dist/components/form/engine/rowOpenPicker.d.ts.map +1 -0
- package/dist/components/form/engine/rowOpenPicker.js +30 -0
- package/dist/components/form/engine/rowOpenPicker.js.map +1 -0
- package/dist/components/form/engine/typeChoices.d.ts +42 -0
- package/dist/components/form/engine/typeChoices.d.ts.map +1 -0
- package/dist/components/form/engine/typeChoices.js +33 -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 +203 -45
- package/dist/components/form/expressions/ExpressionField.js.map +1 -1
- package/dist/components/form/expressions/argumentPresence.d.ts +84 -0
- package/dist/components/form/expressions/argumentPresence.d.ts.map +1 -0
- package/dist/components/form/expressions/argumentPresence.js +217 -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 +21 -65
- 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/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/useExpressions.d.ts.map +1 -1
- package/dist/components/form/expressions/useExpressions.js +5 -2
- package/dist/components/form/expressions/useExpressions.js.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 +18 -0
- package/dist/components/form/fields/Field.d.ts.map +1 -1
- package/dist/components/form/fields/Field.js +54 -8
- 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 +52 -8
- package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.d.ts +7 -1
- package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.js +91 -28
- 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/long-string/LongString.d.ts.map +1 -1
- package/dist/components/form/fields/long-string/LongString.js +14 -2
- package/dist/components/form/fields/long-string/LongString.js.map +1 -1
- package/dist/components/form/fields/multi-select/MultiSelectFormField.d.ts.map +1 -1
- package/dist/components/form/fields/multi-select/MultiSelectFormField.js +50 -4
- package/dist/components/form/fields/multi-select/MultiSelectFormField.js.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts +47 -2
- package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.js +153 -12
- package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
- package/dist/components/form/fields/select/Select.d.ts.map +1 -1
- package/dist/components/form/fields/select/Select.js +120 -20
- package/dist/components/form/fields/select/Select.js.map +1 -1
- package/dist/components/form/fields/select/SelectCollection.d.ts +166 -9
- package/dist/components/form/fields/select/SelectCollection.d.ts.map +1 -1
- package/dist/components/form/fields/select/SelectCollection.js +200 -7
- package/dist/components/form/fields/select/SelectCollection.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 +34 -2
- package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
- package/dist/components/form/fields/template/TemplateField.js +606 -58
- package/dist/components/form/fields/template/TemplateField.js.map +1 -1
- package/dist/components/form/fields/template/TemplateText.d.ts +36 -0
- package/dist/components/form/fields/template/TemplateText.d.ts.map +1 -0
- package/dist/components/form/fields/template/TemplateText.js +80 -0
- package/dist/components/form/fields/template/TemplateText.js.map +1 -0
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +4 -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/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/components/supportTicket/TicketReplyBox.d.ts +8 -4
- package/dist/components/supportTicket/TicketReplyBox.d.ts.map +1 -1
- package/dist/components/supportTicket/TicketReplyBox.js.map +1 -1
- package/dist/helpers/common.d.ts.map +1 -1
- package/dist/helpers/common.js +7 -3
- package/dist/helpers/common.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 +31 -3
- package/dist/helpers/optionUiTypes.d.ts.map +1 -1
- package/dist/helpers/optionUiTypes.js +36 -1
- package/dist/helpers/optionUiTypes.js.map +1 -1
- package/dist/helpers/options.d.ts +57 -0
- package/dist/helpers/options.d.ts.map +1 -1
- package/dist/helpers/options.js +97 -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 +80 -0
- package/dist/helpers/templateText.d.ts.map +1 -0
- package/dist/helpers/templateText.js +291 -0
- package/dist/helpers/templateText.js.map +1 -0
- package/dist/helpers/templates.d.ts +30 -12
- package/dist/helpers/templates.d.ts.map +1 -1
- package/dist/helpers/templates.js +91 -33
- package/dist/helpers/templates.js.map +1 -1
- package/dist/helpers/validations.d.ts +17 -8
- package/dist/helpers/validations.d.ts.map +1 -1
- package/dist/helpers/validations.js +117 -22
- 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/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 +8 -4
- package/src/components/composer/Composer.tsx +7 -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 +487 -160
- package/src/components/form/engine/CompactToolbar.tsx +10 -3
- package/src/components/form/engine/FormEngine.stories.tsx +1294 -161
- package/src/components/form/engine/FormEngine.tsx +916 -213
- package/src/components/form/engine/FormEngineStickyHeaders.stories.tsx +331 -0
- package/src/components/form/engine/FormFieldsSkeleton.tsx +77 -0
- package/src/components/form/engine/OptionFieldMessages.tsx +300 -14
- package/src/components/form/engine/_structuredData/SchemaDataView.tsx +94 -3
- package/src/components/form/engine/compactRowContext.ts +15 -6
- package/src/components/form/engine/compactRowStyles.ts +56 -2
- package/src/components/form/engine/compactToolbarContext.ts +1 -1
- package/src/components/form/engine/optionActions.ts +89 -1
- package/src/components/form/engine/optionsContext.ts +24 -0
- package/src/components/form/engine/readFirst.ts +88 -8
- package/src/components/form/engine/rendererTypes.ts +48 -4
- package/src/components/form/engine/rowFocus.ts +101 -0
- package/src/components/form/engine/rowMenuContext.ts +159 -0
- package/src/components/form/engine/rowOpenPicker.ts +27 -0
- package/src/components/form/engine/typeChoices.ts +63 -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 +269 -55
- package/src/components/form/expressions/argumentPresence.ts +213 -0
- package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +38 -21
- package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
- package/src/components/form/expressions/builder/index.tsx +39 -81
- 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/useExpressions.ts +5 -1
- package/src/components/form/expressions/useRenderExpression.ts +43 -127
- package/src/components/form/fields/Field.tsx +99 -20
- package/src/components/form/fields/allowed-values/AllowedValues.tsx +106 -44
- package/src/components/form/fields/auto/AutoFormField.stories.tsx +85 -18
- package/src/components/form/fields/auto/AutoFormField.tsx +97 -21
- package/src/components/form/fields/date/Date.tsx +17 -1
- package/src/components/form/fields/long-string/LongString.tsx +15 -3
- package/src/components/form/fields/multi-select/MultiSelectFormField.tsx +65 -6
- package/src/components/form/fields/rich-text/RichText.tsx +248 -30
- package/src/components/form/fields/select/Select.stories.tsx +392 -0
- package/src/components/form/fields/select/Select.tsx +164 -27
- package/src/components/form/fields/select/SelectCollection.tsx +350 -6
- package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
- package/src/components/form/fields/template/TemplateField.stories.tsx +93 -14
- package/src/components/form/fields/template/TemplateField.tsx +825 -131
- package/src/components/form/fields/template/TemplateText.tsx +115 -0
- package/src/components/form/index.tsx +4 -0
- package/src/components/log/Log.stories.tsx +2 -1
- package/src/components/searchFilterBar/SearchFilterBar.stories.tsx +3 -3
- 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/components/supportTicket/TicketReplyBox.tsx +8 -4
- package/src/components/ticketThread/TicketThread.stories.tsx +1 -0
- package/src/helpers/common.ts +7 -3
- package/src/helpers/dpqlDetection.ts +209 -0
- package/src/helpers/optionUiTypes.ts +38 -0
- package/src/helpers/options.ts +96 -0
- package/src/helpers/templateItems.ts +81 -0
- package/src/helpers/templateText.ts +350 -0
- package/src/helpers/templates.ts +94 -41
- package/src/helpers/validations.ts +138 -39
- 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
- package/dist/components/form/engine/__fixtures__/basicFormOptions.d.ts +0 -73
- package/dist/components/form/engine/__fixtures__/basicFormOptions.d.ts.map +0 -1
- package/dist/components/form/engine/__fixtures__/basicFormOptions.js +0 -317
- package/dist/components/form/engine/__fixtures__/basicFormOptions.js.map +0 -1
- package/dist/components/form/engine/__fixtures__/fieldChromeOptions.d.ts +0 -41
- package/dist/components/form/engine/__fixtures__/fieldChromeOptions.d.ts.map +0 -1
- package/dist/components/form/engine/__fixtures__/fieldChromeOptions.js +0 -45
- package/dist/components/form/engine/__fixtures__/fieldChromeOptions.js.map +0 -1
- package/dist/components/form/expressions/dpqlMockLsp.d.ts +0 -3
- package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +0 -1
- package/dist/components/form/expressions/dpqlMockLsp.js +0 -91
- package/dist/components/form/expressions/dpqlMockLsp.js.map +0 -1
- package/dist/components/form/fields/template/__fixtures__/longExampleValue.d.ts +0 -2
- package/dist/components/form/fields/template/__fixtures__/longExampleValue.d.ts.map +0 -1
- package/dist/components/form/fields/template/__fixtures__/longExampleValue.js +0 -9
- package/dist/components/form/fields/template/__fixtures__/longExampleValue.js.map +0 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +0 -55
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +0 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js +0 -126
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +0 -1
- package/dist/stories/Data/fileFieldArgSchema.json +0 -86
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
} from '@qoretechnologies/reqore/dist/components/Textarea';
|
|
30
30
|
import { IQorusFormFieldSchemaBase, TQorusType } from '@qoretechnologies/ts-toolkit';
|
|
31
31
|
import { size } from 'lodash';
|
|
32
|
-
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
|
32
|
+
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
33
33
|
import { useUpdateEffect } from 'react-use';
|
|
34
34
|
import {
|
|
35
35
|
filterTemplatesByType as templatesFilterFunc,
|
|
@@ -40,7 +40,14 @@ import {
|
|
|
40
40
|
describeTemplateReference,
|
|
41
41
|
isValueTemplate,
|
|
42
42
|
} from '../../../../helpers/templates';
|
|
43
|
+
import { classifyTypedText, mightBeDpqlExpression } from '../../../../helpers/dpqlDetection';
|
|
44
|
+
import {
|
|
45
|
+
renderTemplateItemDescriptions,
|
|
46
|
+
templateItemsToShow,
|
|
47
|
+
} from '../../../../helpers/templateItems';
|
|
48
|
+
import { useMarkdownRenderer } from '../../../Description/markdownRendererContext';
|
|
43
49
|
import { getTypeFromValue } from '../../../../helpers/validations';
|
|
50
|
+
import { useDpqlProbe } from '../../../dpqlEditor/useDpqlProbe';
|
|
44
51
|
import { useQorusTypes } from '../../../../hooks/useQorusTypes';
|
|
45
52
|
import { useWhyDidYouUpdate } from '../../../../hooks/useWhyDidYouUpdate';
|
|
46
53
|
import { ExpressionBuilder, IExpressionBuilderProps } from '../../expressions/builder';
|
|
@@ -57,11 +64,28 @@ import LongStringFormField from '../long-string/LongString';
|
|
|
57
64
|
import NumberFormField from '../number/Number';
|
|
58
65
|
import { ReadOnlyTemplateTag } from './ReadOnlyTemplateTag';
|
|
59
66
|
import { RichTextFormField } from '../rich-text/RichText';
|
|
67
|
+
import { richtextToString } from '../../../../helpers/common';
|
|
68
|
+
import { isSingleLineStringType } from '../../../../helpers/singleLineString';
|
|
69
|
+
import { isUntypedOptionType } from '../../../../helpers/optionUiTypes';
|
|
70
|
+
import {
|
|
71
|
+
IRowMenuRegistration,
|
|
72
|
+
RowMenuContext,
|
|
73
|
+
useRowMenu,
|
|
74
|
+
useRowMenuPublisher,
|
|
75
|
+
} from '../../engine/rowMenuContext';
|
|
60
76
|
|
|
61
77
|
// Re-export template utilities for consumers
|
|
62
78
|
export { getTemplateKey, getTemplateValue, isValueTemplate };
|
|
63
79
|
export type { IQorusFormType as IQorusType };
|
|
64
80
|
|
|
81
|
+
/**
|
|
82
|
+
* How long the author must pause before typed text is sent to the DPQL
|
|
83
|
+
* probe. Long enough that a probe is not sent for every prefix of what is
|
|
84
|
+
* being typed, short enough that the offer appears while they are still
|
|
85
|
+
* looking at the field.
|
|
86
|
+
*/
|
|
87
|
+
const DPQL_DETECT_DEBOUNCE_MS = 400;
|
|
88
|
+
|
|
65
89
|
export const TemplatesListProps: IReqoreDropdownProps = {
|
|
66
90
|
useTargetWidth: true,
|
|
67
91
|
handler: 'focus',
|
|
@@ -166,6 +190,14 @@ export interface ITemplateFieldProps extends Partial<
|
|
|
166
190
|
* out of `rest` and never spread onto a leaf input.
|
|
167
191
|
*/
|
|
168
192
|
extraActions?: IExpressionBuilderProps['extraActions'];
|
|
193
|
+
/**
|
|
194
|
+
* SEAM (reqraft): the host's per-`ui_type` editors, forwarded to the field
|
|
195
|
+
* component and to the expression editor's operand rows. Declared here — not
|
|
196
|
+
* left to the index signature — for the same reason as `extraActions` above:
|
|
197
|
+
* so it is destructured out of `rest` and never spread onto a leaf input or a
|
|
198
|
+
* Reqore layout component, both of which pass unknown props to the DOM.
|
|
199
|
+
*/
|
|
200
|
+
componentOverrides?: Record<string, React.FC<any>>;
|
|
169
201
|
[key: string]: any;
|
|
170
202
|
default_value?: unknown;
|
|
171
203
|
}
|
|
@@ -300,16 +332,19 @@ const MenuActionsSection = memo(
|
|
|
300
332
|
actions,
|
|
301
333
|
closePopover,
|
|
302
334
|
size,
|
|
335
|
+
startExpanded,
|
|
303
336
|
...rest
|
|
304
337
|
}: {
|
|
305
338
|
actions: ITemplateMenuActions;
|
|
306
339
|
closePopover?: () => void;
|
|
307
340
|
size?: IReqoreButtonProps['size'];
|
|
341
|
+
/** @see `loneSectionStartsExpanded` — the menu's only group opens itself. */
|
|
342
|
+
startExpanded?: boolean;
|
|
308
343
|
}) => (
|
|
309
344
|
<ReqoreMenuSection
|
|
310
345
|
label={actions.label}
|
|
311
346
|
icon={actions.icon}
|
|
312
|
-
isCollapsed
|
|
347
|
+
isCollapsed={!startExpanded}
|
|
313
348
|
transparent
|
|
314
349
|
className='template-menu-actions'
|
|
315
350
|
size={size}
|
|
@@ -326,20 +361,28 @@ export const CustomMenuItems = memo(
|
|
|
326
361
|
closePopover,
|
|
327
362
|
setIsTemplate,
|
|
328
363
|
setTemplateValue,
|
|
364
|
+
startExpanded,
|
|
329
365
|
...rest
|
|
330
366
|
}: {
|
|
331
367
|
items: TCustomTemplateItems | undefined;
|
|
332
368
|
closePopover?: () => void;
|
|
333
369
|
setIsTemplate: React.Dispatch<React.SetStateAction<boolean>>;
|
|
334
370
|
setTemplateValue: React.Dispatch<React.SetStateAction<string | null>>;
|
|
371
|
+
/** @see `loneSectionStartsExpanded` — the menu's only group opens itself. */
|
|
372
|
+
startExpanded?: boolean;
|
|
335
373
|
}) => {
|
|
336
374
|
return (
|
|
337
|
-
<ReqoreMenuSection
|
|
375
|
+
<ReqoreMenuSection
|
|
376
|
+
label='Set Custom Value'
|
|
377
|
+
isCollapsed={!startExpanded}
|
|
378
|
+
transparent
|
|
379
|
+
icon='Text'
|
|
380
|
+
{...rest}
|
|
381
|
+
>
|
|
338
382
|
{items.map((menuItem, index) =>
|
|
339
|
-
'isDivider' in menuItem ?
|
|
383
|
+
'isDivider' in menuItem ?
|
|
340
384
|
<ReqoreMenuDivider key={index} {...menuItem} />
|
|
341
|
-
|
|
342
|
-
<ReqoreButton
|
|
385
|
+
: <ReqoreButton
|
|
343
386
|
{...(rest as any)}
|
|
344
387
|
{...menuItem}
|
|
345
388
|
key={index}
|
|
@@ -351,7 +394,6 @@ export const CustomMenuItems = memo(
|
|
|
351
394
|
});
|
|
352
395
|
}}
|
|
353
396
|
/>
|
|
354
|
-
)
|
|
355
397
|
)}
|
|
356
398
|
</ReqoreMenuSection>
|
|
357
399
|
);
|
|
@@ -370,6 +412,15 @@ export const TemplateDropdownSelector = memo(
|
|
|
370
412
|
size,
|
|
371
413
|
...rest
|
|
372
414
|
}: ITemplateDropdownSelectorProps) => {
|
|
415
|
+
/* The LAST hop before Reqore, which is the only place a drawn description
|
|
416
|
+
may be made: `filteredTemplates` upstream is a `JSON.stringify` memo key,
|
|
417
|
+
and an element in it throws on its own circular owner. */
|
|
418
|
+
const renderMarkdown = useMarkdownRenderer();
|
|
419
|
+
const shownItems = useMemo(
|
|
420
|
+
() => renderTemplateItemDescriptions(templateItemsToShow(items), renderMarkdown),
|
|
421
|
+
[items, renderMarkdown]
|
|
422
|
+
);
|
|
423
|
+
|
|
373
424
|
// One resolver for every surface that names a reference — the picker chip
|
|
374
425
|
// here, the read-only tag, and the compact row's expression summary. The
|
|
375
426
|
// row and this editor must agree: a reference that reads as its path when
|
|
@@ -403,7 +454,7 @@ export const TemplateDropdownSelector = memo(
|
|
|
403
454
|
minimal
|
|
404
455
|
compact
|
|
405
456
|
onItemSelect={onItemSelect}
|
|
406
|
-
items={
|
|
457
|
+
items={shownItems}
|
|
407
458
|
label={label}
|
|
408
459
|
leftIconProps={leftIconProps}
|
|
409
460
|
caretPosition='right'
|
|
@@ -411,7 +462,7 @@ export const TemplateDropdownSelector = memo(
|
|
|
411
462
|
size={size}
|
|
412
463
|
{...TemplatesListProps}
|
|
413
464
|
/>
|
|
414
|
-
{allowCustomValues || value ?
|
|
465
|
+
{allowCustomValues || value ?
|
|
415
466
|
<ReqoreButton
|
|
416
467
|
customTheme={TemplatesListProps.listCustomTheme}
|
|
417
468
|
fixed
|
|
@@ -423,15 +474,48 @@ export const TemplateDropdownSelector = memo(
|
|
|
423
474
|
size={size}
|
|
424
475
|
onClick={onRemoveClick}
|
|
425
476
|
/>
|
|
426
|
-
|
|
477
|
+
: null}
|
|
427
478
|
</ReqoreControlGroup>
|
|
428
479
|
</ReqoreControlGroup>
|
|
429
480
|
);
|
|
430
481
|
}
|
|
431
482
|
);
|
|
432
483
|
|
|
433
|
-
|
|
484
|
+
/**
|
|
485
|
+
* `ui_type`s whose OWN editor renders templates as chips inline.
|
|
486
|
+
*
|
|
487
|
+
* For these the template SELECTOR must not take over: it replaces a control the
|
|
488
|
+
* author can type in with one they can only pick from. A Qorus test assertion's
|
|
489
|
+
* `Value` is the case this was written for — the IDE's own `auto.tsx` already
|
|
490
|
+
* renders it as template rich text (type freely, references become chips), and
|
|
491
|
+
* the same field reached through this form engine offered a dropdown and no way
|
|
492
|
+
* to type at all.
|
|
493
|
+
*
|
|
494
|
+
* `richtext` is reqraft's own; a consumer adds its types through the
|
|
495
|
+
* `templateAwareUiTypes` prop, exactly as it adds `rendererOnlyUiTypes`.
|
|
496
|
+
*/
|
|
497
|
+
export const BuiltInTemplateAwareUiTypes = ['richtext'];
|
|
498
|
+
|
|
499
|
+
export const isTemplateAwareUiType = (uiType?: string, extra?: string[]): boolean =>
|
|
500
|
+
!!uiType && [...BuiltInTemplateAwareUiTypes, ...(extra ?? [])].includes(uiType);
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* One entry of the "set a value of this type" menu an untyped field offers.
|
|
504
|
+
*
|
|
505
|
+
* `onClick` is typed by what the caller actually invokes it with — the chosen
|
|
506
|
+
* value and a reset callback — rather than as `Function`, which accepts a class
|
|
507
|
+
* declaration as readily as a handler and gives no help at the call site.
|
|
508
|
+
*/
|
|
509
|
+
interface IUntypedFieldMenuItem {
|
|
510
|
+
label?: unknown;
|
|
511
|
+
description?: unknown;
|
|
512
|
+
isDivider?: boolean;
|
|
513
|
+
onClick?: (value: unknown, reset: () => void) => void;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const TemplateFieldImpl = memo(
|
|
434
517
|
({
|
|
518
|
+
rowMenu,
|
|
435
519
|
value,
|
|
436
520
|
name,
|
|
437
521
|
onChange,
|
|
@@ -442,6 +526,7 @@ export const TemplateField = memo(
|
|
|
442
526
|
allowFunctions,
|
|
443
527
|
allowTextExpressions,
|
|
444
528
|
extraActions,
|
|
529
|
+
componentOverrides,
|
|
445
530
|
allowCustomValues = true,
|
|
446
531
|
filterTemplatesByType = true,
|
|
447
532
|
filterTemplatesFunc,
|
|
@@ -458,7 +543,7 @@ export const TemplateField = memo(
|
|
|
458
543
|
reorder,
|
|
459
544
|
label,
|
|
460
545
|
...rest
|
|
461
|
-
}: ITemplateFieldProps) => {
|
|
546
|
+
}: ITemplateFieldProps & { rowMenu?: IRowMenuRegistration }) => {
|
|
462
547
|
const qorusTypes = useQorusTypes();
|
|
463
548
|
const functions = useExpressions({
|
|
464
549
|
allow: !!allowFunctions,
|
|
@@ -467,9 +552,22 @@ export const TemplateField = memo(
|
|
|
467
552
|
});
|
|
468
553
|
const type = rest.ui_type || rest.type || rest.defaultType;
|
|
469
554
|
|
|
555
|
+
/* The type an EXPRESSION must return, and is stored with, is a DATA type.
|
|
556
|
+
`type` above deliberately prefers `ui_type`, because that is what decides
|
|
557
|
+
which editor to render — but a ui_type says how a value is EDITED, not
|
|
558
|
+
what it is. The IDE's reference fields carry `ui_type: 'test-reference'`
|
|
559
|
+
(a rich-text editor that turns `$.` paths into chips), and feeding that
|
|
560
|
+
to the expression builder asked it for an expression RETURNING
|
|
561
|
+
`test-reference`: no expression returns one, so every choice was refused
|
|
562
|
+
with *"the expected return type is test-reference"*, and the ui_type was
|
|
563
|
+
then stored on the saved expression as its declared type.
|
|
564
|
+
|
|
565
|
+
The declared data type is the constraint; the ui_type is only a fallback
|
|
566
|
+
for a field that declares no type at all. */
|
|
567
|
+
const expressionDataType = rest.type || rest.defaultType || rest.ui_type;
|
|
568
|
+
|
|
470
569
|
const filteredTemplates = useMemo<IReqoreFormTemplates>(():
|
|
471
|
-
|
|
|
472
|
-
| undefined => {
|
|
570
|
+
IReqoreFormTemplates | undefined => {
|
|
473
571
|
if (!allowTemplates) {
|
|
474
572
|
return undefined;
|
|
475
573
|
}
|
|
@@ -484,7 +582,20 @@ export const TemplateField = memo(
|
|
|
484
582
|
result = filterTemplatesFunc(result);
|
|
485
583
|
}
|
|
486
584
|
|
|
487
|
-
|
|
585
|
+
/* A lone category is opened, once, HERE — where this control resolves the
|
|
586
|
+
templates every one of its pickers then reads: the "Select Template"
|
|
587
|
+
dropdown, the in-editor `$` list, the numeric field's focus dropdown and
|
|
588
|
+
the expression builder's argument picker. Applying it per picker meant
|
|
589
|
+
each new one had to remember, and the ones that forgot made the author
|
|
590
|
+
click through a header naming the only category on offer to reach the
|
|
591
|
+
only values on offer.
|
|
592
|
+
|
|
593
|
+
LAST, after both filters. `filterTemplatesFunc` is given the grouped
|
|
594
|
+
shape on purpose — the Qog expression builder filters top-level items by
|
|
595
|
+
`metadata.dataRole`, which is a property of the CATEGORY — so flattening
|
|
596
|
+
before it would hand it values and quietly change what it keeps. This
|
|
597
|
+
only changes what is finally shown. */
|
|
598
|
+
return { ...result, items: templateItemsToShow(result?.items) };
|
|
488
599
|
}, [
|
|
489
600
|
JSON.stringify(templates),
|
|
490
601
|
type,
|
|
@@ -509,24 +620,74 @@ export const TemplateField = memo(
|
|
|
509
620
|
// Only while the field is EMPTY. A field already holding a literal must
|
|
510
621
|
// open showing that literal — flipping to the template view would hide a
|
|
511
622
|
// value the author put there and make it look lost.
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
|
|
516
|
-
|
|
623
|
+
//
|
|
624
|
+
// A field that may hold a custom value opens there whether or not templates
|
|
625
|
+
// are on offer, and whether or not they have ARRIVED: template mode gives it
|
|
626
|
+
// a typable editor, which offers the list once there is one. Waiting for the
|
|
627
|
+
// list decided the landing from data that is async — on a cold load the
|
|
628
|
+
// type-filtered list did not exist at mount, so the field fell to the type
|
|
629
|
+
// picker and stayed there. A pick-only field (no custom values) is in
|
|
630
|
+
// template mode regardless, by the `!allowCustomValues` term.
|
|
631
|
+
const typeIsAnyLike = isUntypedOptionType(type);
|
|
632
|
+
/* An EMPTY field, for the purpose of offering the template selector.
|
|
633
|
+
|
|
634
|
+
`null` is deliberately not empty. It is the DPQL null literal — a value
|
|
635
|
+
an author writes to say "this is nothing", and the only way to assert
|
|
636
|
+
that a call returned no value. Counted as emptiness here, an argument
|
|
637
|
+
holding it was treated as a field the author had just cleared: with a
|
|
638
|
+
type of `any` and templates on offer, the selector-restore effect below
|
|
639
|
+
flipped the field into template mode with a null template value, and
|
|
640
|
+
that reported the field as cleared — so writing `null` in the Text tab
|
|
641
|
+
and switching to Visual silently erased it, leaving `{type: "any"}` in
|
|
642
|
+
the draft and the message "Value for argument 1 ("any") is invalid:
|
|
643
|
+
Value is empty".
|
|
644
|
+
|
|
645
|
+
`undefined` is the cleared state; `null` is a value. Same rule as
|
|
646
|
+
`expressions/argumentPresence`, which states it canonically. */
|
|
647
|
+
const isEmptyValue = value === undefined || value === '';
|
|
517
648
|
const hasTemplatesOnOffer = !!size(filteredTemplates?.items);
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
649
|
+
/* The field's own editor already renders templates inline, so the selector
|
|
650
|
+
is not merely unnecessary here — it is a downgrade, swapping a typable
|
|
651
|
+
control for a pick-only one. Derived rather than folded into the state
|
|
652
|
+
below so every `setIsTemplate` path keeps working untouched; they simply
|
|
653
|
+
stop having anything to say for these fields. */
|
|
654
|
+
const editorHandlesTemplates =
|
|
655
|
+
!!allowTemplates && isTemplateAwareUiType(type as string, (rest as any).templateAwareUiTypes);
|
|
656
|
+
|
|
657
|
+
const hasOnlyAllowedValues = useMemo(
|
|
658
|
+
() => !!size(rest.allowed_values) && !rest.allowed_values_creatable,
|
|
659
|
+
[rest.allowed_values, rest.allowed_values_creatable]
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
/** Where this field lands when it is empty: an untyped one in template mode — see above. */
|
|
663
|
+
const emptyLandsOnTemplates =
|
|
664
|
+
typeIsAnyLike && (hasTemplatesOnOffer || (!!allowCustomValues && !hasOnlyAllowedValues));
|
|
665
|
+
const opensOnTemplates = isEmptyValue && emptyLandsOnTemplates;
|
|
666
|
+
|
|
667
|
+
const [isTemplateState, setIsTemplate] = useState<boolean>(
|
|
668
|
+
(isDefaultTemplate || isValueTemplate(value) || !allowCustomValues || opensOnTemplates) &&
|
|
523
669
|
allowTemplates
|
|
524
670
|
);
|
|
671
|
+
|
|
672
|
+
const isTemplate = editorHandlesTemplates ? false : isTemplateState;
|
|
525
673
|
const [internalIsFunction, setInternalIsFunction] = useState<boolean>(
|
|
526
674
|
!!isDefaultFunction && !!allowFunctions
|
|
527
675
|
);
|
|
528
676
|
const [templateValue, setTemplateValue] = useState<string | null>(value);
|
|
529
677
|
|
|
678
|
+
// Typed text that turned out to be an expression the author may accept.
|
|
679
|
+
const [dpqlOffer, setDpqlOffer] = useState<{ text: string; expression: any } | null>(null);
|
|
680
|
+
// The literal this field held before typed text switched it into
|
|
681
|
+
// expression mode — kept so the switch is undoable, and so the editor
|
|
682
|
+
// opens on the Text view the author was already writing in.
|
|
683
|
+
const [expressionFromText, setExpressionFromText] = useState<string | null>(null);
|
|
684
|
+
// Texts the author has said no to. Without this, dismissing an offer (or
|
|
685
|
+
// undoing a switch) would re-detect the same text on the next render and
|
|
686
|
+
// ask again forever.
|
|
687
|
+
const declinedTexts = useRef<Set<string>>(new Set());
|
|
688
|
+
const detectTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
689
|
+
const probeDpql = useDpqlProbe();
|
|
690
|
+
|
|
530
691
|
const effectiveIsFunction = isFunction || internalIsFunction;
|
|
531
692
|
|
|
532
693
|
useWhyDidYouUpdate(`Template field ${name}`, {
|
|
@@ -542,6 +703,63 @@ export const TemplateField = memo(
|
|
|
542
703
|
}
|
|
543
704
|
}, [JSON.stringify(value)]);
|
|
544
705
|
|
|
706
|
+
/* Clearing an untyped field returns it to the template selector, which is
|
|
707
|
+
where mounting it empty already lands.
|
|
708
|
+
|
|
709
|
+
The rule above that opens an `any`-like field on the template selector is
|
|
710
|
+
a `useState` INITIALISER, so it only ever ran on mount. Clearing a value
|
|
711
|
+
in place does not remount anything, so the field fell through to the TYPE
|
|
712
|
+
picker and demanded `string`/`int`/`hash` before it would let the author
|
|
713
|
+
name a value they had already captured -- the very question that rule
|
|
714
|
+
exists to stop asking. Reloading the page fixed it, which is the tell:
|
|
715
|
+
same field, same empty value, different landing, because only one of the
|
|
716
|
+
two paths ran the rule.
|
|
717
|
+
|
|
718
|
+
Watched as a TRANSITION from a value to no value, not as "the value is
|
|
719
|
+
empty": an author who picks `Set Custom Value` on an empty field is
|
|
720
|
+
asking for the type picker, and re-asserting the template view on every
|
|
721
|
+
render while the field sat empty would take it away again immediately. */
|
|
722
|
+
const hadValue = useRef(!isEmptyValue);
|
|
723
|
+
/* A clear is REMEMBERED rather than acted on in the same render.
|
|
724
|
+
|
|
725
|
+
While the field holds a value its resolved type is that VALUE's type — a
|
|
726
|
+
reference reads as `test-reference`, not `auto` — and clearing reverts it
|
|
727
|
+
to the schema's `auto` one render LATER than the value empties. Acting
|
|
728
|
+
only on the transition therefore ran while `typeIsAnyLike` was still
|
|
729
|
+
false and did nothing, and by the time the type settled the transition
|
|
730
|
+
had passed. Measured in the live IDE at the moment of the clear:
|
|
731
|
+
`{ wasCleared: true, typeIsAnyLike: false, type: "test-reference",
|
|
732
|
+
hasTemplatesOnOffer: true }`, then `typeIsAnyLike: true` on the very next
|
|
733
|
+
render with `wasCleared` already false. */
|
|
734
|
+
const restoreSelectorWhenSettled = useRef(false);
|
|
735
|
+
/** Set by the template control's own `×`, which is an author saying "not a
|
|
736
|
+
* template" — that clear must not be answered with the selector again. */
|
|
737
|
+
const suppressSelectorRestore = useRef(false);
|
|
738
|
+
|
|
739
|
+
useEffect(() => {
|
|
740
|
+
if (hadValue.current && isEmptyValue) {
|
|
741
|
+
restoreSelectorWhenSettled.current = !suppressSelectorRestore.current;
|
|
742
|
+
suppressSelectorRestore.current = false;
|
|
743
|
+
}
|
|
744
|
+
if (!isEmptyValue) {
|
|
745
|
+
// A value arrived: whatever the author did, they are not sitting on an
|
|
746
|
+
// empty field waiting to be offered the list.
|
|
747
|
+
restoreSelectorWhenSettled.current = false;
|
|
748
|
+
}
|
|
749
|
+
hadValue.current = !isEmptyValue;
|
|
750
|
+
|
|
751
|
+
if (
|
|
752
|
+
restoreSelectorWhenSettled.current &&
|
|
753
|
+
opensOnTemplates &&
|
|
754
|
+
allowTemplates &&
|
|
755
|
+
!effectiveIsFunction
|
|
756
|
+
) {
|
|
757
|
+
restoreSelectorWhenSettled.current = false;
|
|
758
|
+
setTemplateValue(null);
|
|
759
|
+
setIsTemplate(true);
|
|
760
|
+
}
|
|
761
|
+
}, [isEmptyValue, opensOnTemplates, allowTemplates, effectiveIsFunction]);
|
|
762
|
+
|
|
545
763
|
useEffect(() => {
|
|
546
764
|
if (allowCustomValues && isTemplate && value && !isValueTemplate(value)) {
|
|
547
765
|
setIsTemplate(false);
|
|
@@ -562,6 +780,17 @@ export const TemplateField = memo(
|
|
|
562
780
|
return;
|
|
563
781
|
}
|
|
564
782
|
|
|
783
|
+
// A template reference with an OPERATOR on it (`$local:count + 1`) is
|
|
784
|
+
// an expression, not a template. The loose check above passes it —
|
|
785
|
+
// it starts with `$` and has a colon — so without this every such
|
|
786
|
+
// string was swallowed into template mode, where the arithmetic is
|
|
787
|
+
// dead text and no affordance can reach it. A token standing alone
|
|
788
|
+
// (`$local:count`, `$data:{1.field}`) has no operator and still
|
|
789
|
+
// flips, which is the case template mode is for.
|
|
790
|
+
if (!isCompleteTemplateToken(value) && mightBeDpqlExpression(value)) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
|
|
565
794
|
setIsTemplate(true);
|
|
566
795
|
setTemplateValue(value);
|
|
567
796
|
}
|
|
@@ -574,11 +803,6 @@ export const TemplateField = memo(
|
|
|
574
803
|
}
|
|
575
804
|
}, [JSON.stringify(templateValue)]);
|
|
576
805
|
|
|
577
|
-
const hasOnlyAllowedValues = useMemo(
|
|
578
|
-
() => !!size(rest.allowed_values) && !rest.allowed_values_creatable,
|
|
579
|
-
[rest.allowed_values, rest.allowed_values_creatable]
|
|
580
|
-
);
|
|
581
|
-
|
|
582
806
|
const showTemplateToggle = allowCustomValues && allowTemplates && !rest.arg_schema;
|
|
583
807
|
|
|
584
808
|
// Only a BRACED context ref (`$data:{…}` — machine-written, nobody types
|
|
@@ -590,8 +814,23 @@ export const TemplateField = memo(
|
|
|
590
814
|
// the rich-text string mode.
|
|
591
815
|
const templateValueIsBracedToken = isBracedTemplateToken(templateValue);
|
|
592
816
|
|
|
817
|
+
/* An UNTYPED field can be typed into as well as picked from.
|
|
818
|
+
|
|
819
|
+
Template mode has a typable editor whenever the field's type can hold
|
|
820
|
+
arbitrary text, and `any`/`auto` can — it is the type that has not been
|
|
821
|
+
narrowed yet, not a type that excludes text. Restricting this to
|
|
822
|
+
`string` meant an empty untyped field opened on template mode with no
|
|
823
|
+
editor to offer, so it fell through to the pick-only "Select Template"
|
|
824
|
+
dropdown: a control that can only choose from a list, on exactly the
|
|
825
|
+
fields where an author most often needs to write something the list
|
|
826
|
+
cannot hold — a deeper walk, a literal, or an expression.
|
|
827
|
+
|
|
828
|
+
Reported against a Qorus assertion's Expected Value, and it was never
|
|
829
|
+
specific to that field: every empty untyped field with templates on
|
|
830
|
+
offer got the same downgrade. The editor still offers the same templates
|
|
831
|
+
on focus, so nothing is lost by being able to type as well. */
|
|
593
832
|
const templateSupportsCustomValues =
|
|
594
|
-
allowCustomValues && type === 'string' && !hasOnlyAllowedValues;
|
|
833
|
+
allowCustomValues && (type === 'string' || typeIsAnyLike) && !hasOnlyAllowedValues;
|
|
595
834
|
const showTemplatesDropdown =
|
|
596
835
|
allowTemplates && (!allowCustomValues || (isTemplate && !templateSupportsCustomValues));
|
|
597
836
|
const hasOnlyExpressions = !allowCustomValues && !allowTemplates && allowFunctions;
|
|
@@ -612,18 +851,28 @@ export const TemplateField = memo(
|
|
|
612
851
|
const componentTypeProp = componentFromType ? { type } : {};
|
|
613
852
|
const fieldAriaLabel = rest['aria-label'] ?? label ?? rest.display_name ?? name;
|
|
614
853
|
|
|
615
|
-
|
|
616
854
|
const handleTemplateFieldChange = useCallback(
|
|
617
855
|
(_name: string, val: string) => {
|
|
618
856
|
if (!val) {
|
|
619
|
-
|
|
857
|
+
/* Emptying the text leaves the field where an empty field lands. For
|
|
858
|
+
an untyped field that is THIS editor: switching to custom mode here
|
|
859
|
+
mounted a different editor in its place, so deleting the last
|
|
860
|
+
character lost the cursor. */
|
|
861
|
+
if (!(emptyLandsOnTemplates && allowTemplates)) {
|
|
862
|
+
setIsTemplate(false);
|
|
863
|
+
}
|
|
620
864
|
setTemplateValue(null);
|
|
621
865
|
onChange(name, undefined);
|
|
622
866
|
} else {
|
|
623
867
|
setTemplateValue(val);
|
|
624
868
|
}
|
|
625
869
|
},
|
|
626
|
-
[name, onChange]
|
|
870
|
+
[name, onChange, emptyLandsOnTemplates, allowTemplates]
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
const handleTemplateTextChange = useCallback(
|
|
874
|
+
(val: unknown) => handleTemplateFieldChange(name, typeof val === 'string' ? val : ''),
|
|
875
|
+
[handleTemplateFieldChange, name]
|
|
627
876
|
);
|
|
628
877
|
|
|
629
878
|
const handleSelectTemplateFromList = useCallback(
|
|
@@ -631,24 +880,38 @@ export const TemplateField = memo(
|
|
|
631
880
|
(item) => {
|
|
632
881
|
// If the template type is richtext, we need to wrap the template value in the richtext template format
|
|
633
882
|
const value =
|
|
634
|
-
item.badge === 'richtext'
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
883
|
+
item.badge === 'richtext' ?
|
|
884
|
+
([
|
|
885
|
+
{
|
|
886
|
+
type: 'paragraph',
|
|
887
|
+
/* The empty text nodes on either side are load-bearing. A
|
|
888
|
+
chip is an inline VOID: it holds no text of its own, so the
|
|
889
|
+
only places a cursor can go are the text nodes AROUND it.
|
|
890
|
+
Slate treats that as an invariant and repairs it during
|
|
891
|
+
normalisation, which runs on edits but NOT on a document
|
|
892
|
+
handed in whole as a controlled value — which is what this
|
|
893
|
+
is. Without them the picked template renders as a chip that
|
|
894
|
+
cannot be typed after, so it can never be extended by hand. */
|
|
895
|
+
children: [
|
|
896
|
+
{ text: '' },
|
|
897
|
+
{
|
|
898
|
+
children: [{ text: '' }],
|
|
899
|
+
label: item.label,
|
|
900
|
+
type: 'tag',
|
|
901
|
+
value: item.value,
|
|
902
|
+
metadata: item.metadata,
|
|
903
|
+
},
|
|
904
|
+
{ text: '' },
|
|
905
|
+
],
|
|
906
|
+
},
|
|
907
|
+
] as IReqoreRichTextEditorProps['value'])
|
|
908
|
+
: item.value;
|
|
909
|
+
|
|
910
|
+
onChange(
|
|
911
|
+
name,
|
|
912
|
+
value,
|
|
913
|
+
hasOnlyAllowedValues ? (type as TQorusType) : (item.badge as TQorusType)
|
|
914
|
+
);
|
|
652
915
|
},
|
|
653
916
|
[name, onChange]
|
|
654
917
|
);
|
|
@@ -658,7 +921,15 @@ export const TemplateField = memo(
|
|
|
658
921
|
// IDE-only, so the menu item is dropped (`allowSaving` is inert).
|
|
659
922
|
|
|
660
923
|
const handleRemoveTemplateClick = useCallback(() => {
|
|
924
|
+
/* This is the `×` ON the template control, and it means "I do not want a
|
|
925
|
+
template here" — on a field whose menu offers no `Set Custom Value` it
|
|
926
|
+
is the ONLY way to reach a literal. So it still drops to the custom
|
|
927
|
+
value editor, and it tells the empty-field rule below to keep its hands
|
|
928
|
+
off this particular clear: that rule watches the value going away, and
|
|
929
|
+
this handler clears the value too, so without the flag it would send
|
|
930
|
+
the author straight back to the selector they just dismissed. */
|
|
661
931
|
if (allowCustomValues) {
|
|
932
|
+
suppressSelectorRestore.current = true;
|
|
662
933
|
setIsTemplate(false);
|
|
663
934
|
}
|
|
664
935
|
|
|
@@ -697,6 +968,25 @@ export const TemplateField = memo(
|
|
|
697
968
|
value,
|
|
698
969
|
]);
|
|
699
970
|
|
|
971
|
+
/* Leaving template mode from the ⋮ — the counterpart of "Use Template".
|
|
972
|
+
Template mode's editor has no `×` of its own, so without this the author
|
|
973
|
+
who switched to a template had no way back and no menu to ask with. */
|
|
974
|
+
const handleUseCustomValueClick = useCallback(() => {
|
|
975
|
+
setIsTemplate(false);
|
|
976
|
+
/* A reference cannot stay in the custom editor: the field reads it as a
|
|
977
|
+
template again and switches straight back. Anything else is text the
|
|
978
|
+
author wrote, and it survives the switch. */
|
|
979
|
+
if (isValueTemplate(value as string) || isCompleteTemplateToken(templateValue)) {
|
|
980
|
+
/* Only where the value is actually cleared. The flag is read by the
|
|
981
|
+
"emptied" effect, which never runs when the text survives — setting
|
|
982
|
+
it unconditionally left it armed to swallow the NEXT genuine clear's
|
|
983
|
+
selector restore. */
|
|
984
|
+
suppressSelectorRestore.current = true;
|
|
985
|
+
setTemplateValue(null);
|
|
986
|
+
onChange?.(name, undefined);
|
|
987
|
+
}
|
|
988
|
+
}, [name, onChange, value, templateValue]);
|
|
989
|
+
|
|
700
990
|
const handleTemplateToggleClick = useCallback(() => {
|
|
701
991
|
setInternalIsFunction(false);
|
|
702
992
|
onChange(name, undefined, undefined, false);
|
|
@@ -708,46 +998,327 @@ export const TemplateField = memo(
|
|
|
708
998
|
(expressionValue: IExpression | undefined, remove: boolean) => {
|
|
709
999
|
if (remove) {
|
|
710
1000
|
setInternalIsFunction(false);
|
|
1001
|
+
// The expression is gone, so there is no longer a switch to undo.
|
|
1002
|
+
setExpressionFromText(null);
|
|
711
1003
|
}
|
|
712
|
-
onChange(name, expressionValue?.value,
|
|
1004
|
+
onChange(name, expressionValue?.value, expressionDataType as TQorusType, !remove);
|
|
1005
|
+
},
|
|
1006
|
+
[name, onChange, expressionDataType, value]
|
|
1007
|
+
);
|
|
1008
|
+
|
|
1009
|
+
// ─── Text typed into a plain field that is really a DPQL expression ───
|
|
1010
|
+
//
|
|
1011
|
+
// A field that accepts expressions still gets DPQL TYPED into it, in the
|
|
1012
|
+
// ordinary editor, by an author who never opened the expression view.
|
|
1013
|
+
// What happens then depends on the field's own type, and the asymmetry is
|
|
1014
|
+
// deliberate: text that could stand as a literal here is only ever
|
|
1015
|
+
// OFFERED, because silently rewriting the string `a + b` into a
|
|
1016
|
+
// concatenation would destroy a value the author meant. Text that could
|
|
1017
|
+
// NOT be a literal here was already an error the moment it was typed, so
|
|
1018
|
+
// switching costs nothing and explains the error.
|
|
1019
|
+
//
|
|
1020
|
+
// The server decides whether the text is an expression at all — see
|
|
1021
|
+
// `helpers/dpqlDetection` for why a successful parse alone means nothing.
|
|
1022
|
+
const validationField = useMemo(
|
|
1023
|
+
() => ({
|
|
1024
|
+
validation_regex: rest.validation_regex,
|
|
1025
|
+
has_to_be_valid_identifier: rest.has_to_be_valid_identifier,
|
|
1026
|
+
has_to_have_value: rest.has_to_have_value,
|
|
1027
|
+
rules: rest.rules,
|
|
1028
|
+
arg_schema: rest.arg_schema,
|
|
1029
|
+
}),
|
|
1030
|
+
[
|
|
1031
|
+
rest.validation_regex,
|
|
1032
|
+
rest.has_to_be_valid_identifier,
|
|
1033
|
+
rest.has_to_have_value,
|
|
1034
|
+
JSON.stringify(rest.rules),
|
|
1035
|
+
JSON.stringify(rest.arg_schema),
|
|
1036
|
+
]
|
|
1037
|
+
);
|
|
1038
|
+
|
|
1039
|
+
const enterExpressionFromText = useCallback(
|
|
1040
|
+
(text: string, expression: any) => {
|
|
1041
|
+
setDpqlOffer(null);
|
|
1042
|
+
setExpressionFromText(text);
|
|
1043
|
+
setInternalIsFunction(true);
|
|
1044
|
+
setIsTemplate(false);
|
|
1045
|
+
setTemplateValue(null);
|
|
1046
|
+
// `dpql/parse` answers with the field-ready envelope
|
|
1047
|
+
// (`{is_expression, value}`); this field stores the inner AST and
|
|
1048
|
+
// signals the flag through `onChange`'s fourth argument, exactly as
|
|
1049
|
+
// `handleExpressionChange` does.
|
|
1050
|
+
onChange?.(name, expression?.value ?? expression, expressionDataType as TQorusType, true);
|
|
713
1051
|
},
|
|
714
|
-
[name, onChange,
|
|
1052
|
+
[name, onChange, expressionDataType]
|
|
715
1053
|
);
|
|
716
1054
|
|
|
1055
|
+
const undoExpressionFromText = useCallback(() => {
|
|
1056
|
+
if (expressionFromText === null) {
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
declinedTexts.current.add(expressionFromText);
|
|
1061
|
+
setInternalIsFunction(false);
|
|
1062
|
+
setExpressionFromText(null);
|
|
1063
|
+
onChange?.(name, expressionFromText, type as TQorusType, false);
|
|
1064
|
+
}, [expressionFromText, name, onChange, type]);
|
|
1065
|
+
|
|
1066
|
+
const declineDpqlOffer = useCallback(() => {
|
|
1067
|
+
if (dpqlOffer) {
|
|
1068
|
+
declinedTexts.current.add(dpqlOffer.text);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
setDpqlOffer(null);
|
|
1072
|
+
}, [dpqlOffer]);
|
|
1073
|
+
|
|
1074
|
+
const acceptDpqlOffer = useCallback(() => {
|
|
1075
|
+
if (dpqlOffer) {
|
|
1076
|
+
enterExpressionFromText(dpqlOffer.text, dpqlOffer.expression);
|
|
1077
|
+
}
|
|
1078
|
+
}, [dpqlOffer, enterExpressionFromText]);
|
|
1079
|
+
|
|
1080
|
+
// Held in a ref so the debounce below does not depend on it. `onChange`
|
|
1081
|
+
// comes from the host form and can be a fresh function on any render;
|
|
1082
|
+
// with the callback in the effect's deps, a re-render caused by a
|
|
1083
|
+
// DIFFERENT field would restart this field's timer, and a form the
|
|
1084
|
+
// author is typing in re-renders constantly.
|
|
1085
|
+
const enterExpressionRef = useRef(enterExpressionFromText);
|
|
1086
|
+
useEffect(() => {
|
|
1087
|
+
enterExpressionRef.current = enterExpressionFromText;
|
|
1088
|
+
}, [enterExpressionFromText]);
|
|
1089
|
+
|
|
1090
|
+
// Detection only runs on a field that could hold an expression and is
|
|
1091
|
+
// currently showing a plain editor — never on a read-only field, a
|
|
1092
|
+
// fixed-value list, or one already in template or expression mode.
|
|
1093
|
+
const canDetectDpql =
|
|
1094
|
+
!!allowFunctions &&
|
|
1095
|
+
!!allowTextExpressions &&
|
|
1096
|
+
!effectiveIsFunction &&
|
|
1097
|
+
!isTemplate &&
|
|
1098
|
+
!hasOnlyAllowedValues &&
|
|
1099
|
+
!rest.readonly &&
|
|
1100
|
+
!rest.readOnly &&
|
|
1101
|
+
!rest.disabled;
|
|
1102
|
+
|
|
1103
|
+
useEffect(() => {
|
|
1104
|
+
if (detectTimer.current) {
|
|
1105
|
+
clearTimeout(detectTimer.current);
|
|
1106
|
+
detectTimer.current = null;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/* A richtext field is still text. It hands its value back as a Slate
|
|
1110
|
+
DOCUMENT rather than a flattened string — deliberately, so a chosen
|
|
1111
|
+
reference cannot fuse with text typed beside it — and reading only
|
|
1112
|
+
`typeof value === 'string'` here silently switched detection off for
|
|
1113
|
+
every one of them the moment the field changed shape. A test
|
|
1114
|
+
assertion's Value is exactly that field, and `1 + 2` typed into it
|
|
1115
|
+
stopped being offered as an expression.
|
|
1116
|
+
|
|
1117
|
+
`richtextToString` flattens a tag to its raw value, so a document
|
|
1118
|
+
holding only a chosen reference reads as `$.result` — no operator, so
|
|
1119
|
+
`mightBeDpqlExpression` rejects it below and the server is never
|
|
1120
|
+
asked. */
|
|
1121
|
+
const text =
|
|
1122
|
+
typeof value === 'string' ? value
|
|
1123
|
+
: Array.isArray(value) ? richtextToString(value as never)
|
|
1124
|
+
: undefined;
|
|
1125
|
+
|
|
1126
|
+
if (
|
|
1127
|
+
!canDetectDpql ||
|
|
1128
|
+
!text ||
|
|
1129
|
+
declinedTexts.current.has(text) ||
|
|
1130
|
+
!mightBeDpqlExpression(text)
|
|
1131
|
+
) {
|
|
1132
|
+
setDpqlOffer(null);
|
|
1133
|
+
return undefined;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
let cancelled = false;
|
|
1137
|
+
|
|
1138
|
+
detectTimer.current = setTimeout(() => {
|
|
1139
|
+
detectTimer.current = null;
|
|
1140
|
+
|
|
1141
|
+
void probeDpql(text)
|
|
1142
|
+
.then((parsed) => {
|
|
1143
|
+
if (cancelled) {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
const outcome = classifyTypedText({ text, type, field: validationField, parsed });
|
|
1148
|
+
|
|
1149
|
+
if (outcome === 'switch') {
|
|
1150
|
+
enterExpressionRef.current(text, parsed.expression);
|
|
1151
|
+
} else if (outcome === 'offer') {
|
|
1152
|
+
setDpqlOffer({ text, expression: parsed.expression });
|
|
1153
|
+
} else {
|
|
1154
|
+
setDpqlOffer(null);
|
|
1155
|
+
}
|
|
1156
|
+
})
|
|
1157
|
+
.catch(() => {
|
|
1158
|
+
// Detection is an offer of help, never a reason for a field to
|
|
1159
|
+
// break: an unreachable instance or a classifier that threw leaves
|
|
1160
|
+
// the author's text exactly where they put it.
|
|
1161
|
+
if (!cancelled) {
|
|
1162
|
+
setDpqlOffer(null);
|
|
1163
|
+
}
|
|
1164
|
+
});
|
|
1165
|
+
}, DPQL_DETECT_DEBOUNCE_MS);
|
|
1166
|
+
|
|
1167
|
+
return () => {
|
|
1168
|
+
cancelled = true;
|
|
1169
|
+
|
|
1170
|
+
if (detectTimer.current) {
|
|
1171
|
+
clearTimeout(detectTimer.current);
|
|
1172
|
+
detectTimer.current = null;
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
}, [value, canDetectDpql, type, validationField, probeDpql]);
|
|
1176
|
+
|
|
1177
|
+
const canOfferExpression =
|
|
1178
|
+
allowFunctions && !hasOnlyAllowedValues && !rest.readonly && !internalIsFunction;
|
|
1179
|
+
const canOfferTemplate = showTemplateToggle && !isTemplate;
|
|
1180
|
+
/* The way back out, published on the same terms the field's own ⋮ draws it.
|
|
1181
|
+
A field inside a form ROW draws no menu of its own — it publishes into
|
|
1182
|
+
the row's — so leaving this out of the published list meant template mode
|
|
1183
|
+
was a one-way door on the surface most options are edited from. */
|
|
1184
|
+
const canOfferCustomValue = showTemplateToggle && isTemplate && templateSupportsCustomValues;
|
|
1185
|
+
|
|
1186
|
+
const publishedItems = useMemo(
|
|
1187
|
+
() => [
|
|
1188
|
+
...(canOfferExpression && !functions.loading
|
|
1189
|
+
? [
|
|
1190
|
+
{
|
|
1191
|
+
label: 'Use Expression',
|
|
1192
|
+
icon: 'Functions' as const,
|
|
1193
|
+
tooltip: 'Run a function on this value',
|
|
1194
|
+
onClick: handleSelectFunctionChange,
|
|
1195
|
+
},
|
|
1196
|
+
]
|
|
1197
|
+
: []),
|
|
1198
|
+
...(canOfferTemplate
|
|
1199
|
+
? [
|
|
1200
|
+
{
|
|
1201
|
+
label: 'Use Template',
|
|
1202
|
+
icon: 'MoneyDollarCircleLine' as const,
|
|
1203
|
+
tooltip: 'Use a template',
|
|
1204
|
+
onClick: handleTemplateToggleClick,
|
|
1205
|
+
},
|
|
1206
|
+
]
|
|
1207
|
+
: []),
|
|
1208
|
+
...(canOfferCustomValue
|
|
1209
|
+
? [
|
|
1210
|
+
{
|
|
1211
|
+
label: 'Use Custom Value',
|
|
1212
|
+
icon: 'EditLine' as const,
|
|
1213
|
+
tooltip: 'Write the value here instead of choosing a template',
|
|
1214
|
+
onClick: handleUseCustomValueClick,
|
|
1215
|
+
},
|
|
1216
|
+
]
|
|
1217
|
+
: []),
|
|
1218
|
+
/* The "set a value of this type" choices an untyped field offers.
|
|
1219
|
+
Dividers are dropped: they grouped items in a menu this field drew
|
|
1220
|
+
itself, and in the row's shared menu they would divide other
|
|
1221
|
+
people's. */
|
|
1222
|
+
...((menuItems ?? []) as IUntypedFieldMenuItem[])
|
|
1223
|
+
.filter((item) => !('isDivider' in item))
|
|
1224
|
+
.map((item) => ({
|
|
1225
|
+
label: item.label as string,
|
|
1226
|
+
description: item.description as string | undefined,
|
|
1227
|
+
onClick: () =>
|
|
1228
|
+
item.onClick?.(undefined, () => {
|
|
1229
|
+
setIsTemplate(false);
|
|
1230
|
+
setTemplateValue(null);
|
|
1231
|
+
}),
|
|
1232
|
+
})),
|
|
1233
|
+
],
|
|
1234
|
+
[
|
|
1235
|
+
canOfferExpression,
|
|
1236
|
+
functions.loading,
|
|
1237
|
+
canOfferTemplate,
|
|
1238
|
+
canOfferCustomValue,
|
|
1239
|
+
menuItems,
|
|
1240
|
+
handleSelectFunctionChange,
|
|
1241
|
+
handleTemplateToggleClick,
|
|
1242
|
+
handleUseCustomValueClick,
|
|
1243
|
+
]
|
|
1244
|
+
);
|
|
1245
|
+
|
|
1246
|
+
/* Keyed on WHICH items are offered, never on the items: they carry
|
|
1247
|
+
handlers, so they are a new array every render and a row comparing them
|
|
1248
|
+
by value would loop. */
|
|
1249
|
+
const publishedKey = [
|
|
1250
|
+
canOfferExpression && !functions.loading ? 'expression' : '',
|
|
1251
|
+
canOfferTemplate ? 'template' : '',
|
|
1252
|
+
canOfferCustomValue ? 'custom-value' : '',
|
|
1253
|
+
`custom:${((menuItems ?? []) as { label?: unknown }[]).map((item) => String(item.label ?? '')).join('|')}`,
|
|
1254
|
+
].join(',');
|
|
1255
|
+
|
|
1256
|
+
useRowMenuPublisher(rowMenu, publishedKey, publishedItems as never);
|
|
1257
|
+
|
|
717
1258
|
const renderControls = useCallback(() => {
|
|
718
1259
|
const showFunctionsDropdown =
|
|
719
1260
|
allowFunctions && !hasOnlyAllowedValues && !rest.readonly && !internalIsFunction;
|
|
720
1261
|
const showTemplatesButton = showTemplateToggle && !isTemplate;
|
|
1262
|
+
// The way back out of template mode, where the editor draws no `×`.
|
|
1263
|
+
const showCustomValueButton = showTemplateToggle && isTemplate && templateSupportsCustomValues;
|
|
721
1264
|
// The "Set value" label promises a way to set one — reorder rows alone don't.
|
|
722
|
-
const hasValueRows =
|
|
1265
|
+
const hasValueRows =
|
|
1266
|
+
showFunctionsDropdown || showTemplatesButton || showCustomValueButton || size(menuItems) > 0;
|
|
1267
|
+
|
|
1268
|
+
/* A lone group opens itself. Two of the menu's groups are collapsed
|
|
1269
|
+
sections, so a menu holding nothing but one of them asked for a click
|
|
1270
|
+
to reach the only thing on offer — on an untyped field, "Set Custom
|
|
1271
|
+
Value" hiding the type rows behind it. Expanded, it still names what
|
|
1272
|
+
its rows are and can still be collapsed. The same rule the template
|
|
1273
|
+
list follows for a lone category, and the row menu already publishes
|
|
1274
|
+
these rows flat. */
|
|
1275
|
+
const loneSectionStartsExpanded =
|
|
1276
|
+
[
|
|
1277
|
+
showFunctionsDropdown,
|
|
1278
|
+
showTemplatesButton,
|
|
1279
|
+
showCustomValueButton,
|
|
1280
|
+
size(menuActions?.items) > 0,
|
|
1281
|
+
size(menuItems) > 0,
|
|
1282
|
+
].filter(Boolean).length === 1;
|
|
1283
|
+
|
|
1284
|
+
/* ONE menu per control. Where this field sits in a form ROW, the row
|
|
1285
|
+
already renders a ⋮ of its own, and drawing a second one beside it
|
|
1286
|
+
put two menus of slightly different widths on the same control —
|
|
1287
|
+
with the type choices an `any` field offers hidden in the narrower
|
|
1288
|
+
one. The items are published to the row instead (see
|
|
1289
|
+
`rowMenuContext`); outside a row there is nothing to publish into,
|
|
1290
|
+
so the field goes on drawing its own. */
|
|
1291
|
+
if (rowMenu) {
|
|
1292
|
+
return null;
|
|
1293
|
+
}
|
|
723
1294
|
|
|
724
1295
|
if (hasOnlyExpressions) {
|
|
725
|
-
return
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
<ReqoreButton
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
)
|
|
1296
|
+
return (
|
|
1297
|
+
showFunctionsDropdown ?
|
|
1298
|
+
functions.loading ?
|
|
1299
|
+
<ReqoreSkeleton size={rest.size} />
|
|
1300
|
+
: <ReqoreButton
|
|
1301
|
+
compact
|
|
1302
|
+
minimal
|
|
1303
|
+
label='Create New Expression'
|
|
1304
|
+
className='function-selector'
|
|
1305
|
+
icon='Functions'
|
|
1306
|
+
tooltip='This field only accepts expressions'
|
|
1307
|
+
onClick={() => {
|
|
1308
|
+
setIsTemplate(false);
|
|
1309
|
+
setTemplateValue(null);
|
|
1310
|
+
onChange?.(
|
|
1311
|
+
name,
|
|
1312
|
+
{
|
|
1313
|
+
args: [],
|
|
1314
|
+
},
|
|
1315
|
+
undefined,
|
|
1316
|
+
true
|
|
1317
|
+
);
|
|
1318
|
+
}}
|
|
1319
|
+
/>
|
|
1320
|
+
: null
|
|
1321
|
+
);
|
|
751
1322
|
}
|
|
752
1323
|
|
|
753
1324
|
if (hasValueRows || size(menuActions?.items) > 0 || size(menuTrailingItems) > 0) {
|
|
@@ -768,10 +1339,18 @@ export const TemplateField = memo(
|
|
|
768
1339
|
transparent: true,
|
|
769
1340
|
size: rest.size,
|
|
770
1341
|
fixed: true,
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1342
|
+
/* Centre the trailing menu in its flex line so it lines up
|
|
1343
|
+
with sibling action buttons (reqore alignSelf; replaces a
|
|
1344
|
+
reqraft `align-self !important` override of this button).
|
|
1345
|
+
|
|
1346
|
+
Centring is only right beside a ONE-LINE editor. The
|
|
1347
|
+
expression shell is a toolbar with an editor, a type message
|
|
1348
|
+
and a preview stacked under it, so centring put this menu
|
|
1349
|
+
somewhere down the side of that block — level with nothing,
|
|
1350
|
+
over the editor, and a long way from the Undo it belongs
|
|
1351
|
+
beside. There it goes to the top, which is where the shell's
|
|
1352
|
+
own toolbar is. */
|
|
1353
|
+
alignSelf: effectiveIsFunction ? 'flex-start' : 'center',
|
|
775
1354
|
label: hasInputAffordance || !hasValueRows ? undefined : 'Set value',
|
|
776
1355
|
style:
|
|
777
1356
|
hasInputAffordance ?
|
|
@@ -797,11 +1376,10 @@ export const TemplateField = memo(
|
|
|
797
1376
|
handler='click'
|
|
798
1377
|
content={
|
|
799
1378
|
<ReqoreMenu size={rest.size} maxHeight='400px' style={{ overflow: 'auto' }}>
|
|
800
|
-
{showFunctionsDropdown ?
|
|
801
|
-
functions.loading ?
|
|
1379
|
+
{showFunctionsDropdown ?
|
|
1380
|
+
functions.loading ?
|
|
802
1381
|
<ReqoreSkeleton size={rest.size} />
|
|
803
|
-
|
|
804
|
-
<ReqoreButton
|
|
1382
|
+
: <ReqoreButton
|
|
805
1383
|
compact
|
|
806
1384
|
transparent
|
|
807
1385
|
label='Use Expression'
|
|
@@ -810,10 +1388,10 @@ export const TemplateField = memo(
|
|
|
810
1388
|
tooltip='Run a function on this value'
|
|
811
1389
|
onClick={handleSelectFunctionChange}
|
|
812
1390
|
/>
|
|
813
|
-
)
|
|
814
|
-
) : null}
|
|
815
1391
|
|
|
816
|
-
|
|
1392
|
+
: null}
|
|
1393
|
+
|
|
1394
|
+
{showTemplatesButton ?
|
|
817
1395
|
<ReqoreButton
|
|
818
1396
|
transparent
|
|
819
1397
|
icon='MoneyDollarCircleLine'
|
|
@@ -826,23 +1404,43 @@ export const TemplateField = memo(
|
|
|
826
1404
|
{' '}
|
|
827
1405
|
Use Template{' '}
|
|
828
1406
|
</ReqoreButton>
|
|
829
|
-
|
|
1407
|
+
: null}
|
|
1408
|
+
|
|
1409
|
+
{showCustomValueButton ?
|
|
1410
|
+
<ReqoreButton
|
|
1411
|
+
transparent
|
|
1412
|
+
icon='EditLine'
|
|
1413
|
+
className='template-custom-value'
|
|
1414
|
+
tooltip='Write the value here instead of choosing a template'
|
|
1415
|
+
compact
|
|
1416
|
+
size={rest.size}
|
|
1417
|
+
onClick={handleUseCustomValueClick}
|
|
1418
|
+
>
|
|
1419
|
+
{' '}
|
|
1420
|
+
Use Custom Value{' '}
|
|
1421
|
+
</ReqoreButton>
|
|
1422
|
+
: null}
|
|
830
1423
|
|
|
831
|
-
{size(menuActions?.items) > 0 ?
|
|
832
|
-
<MenuActionsSection
|
|
833
|
-
|
|
1424
|
+
{size(menuActions?.items) > 0 ?
|
|
1425
|
+
<MenuActionsSection
|
|
1426
|
+
actions={menuActions}
|
|
1427
|
+
size={rest.size}
|
|
1428
|
+
startExpanded={loneSectionStartsExpanded}
|
|
1429
|
+
/>
|
|
1430
|
+
: null}
|
|
834
1431
|
|
|
835
|
-
{size(menuItems) > 0 ?
|
|
1432
|
+
{size(menuItems) > 0 ?
|
|
836
1433
|
<CustomMenuItems
|
|
837
1434
|
items={menuItems}
|
|
838
1435
|
setIsTemplate={setIsTemplate}
|
|
839
1436
|
setTemplateValue={setTemplateValue}
|
|
1437
|
+
startExpanded={loneSectionStartsExpanded}
|
|
840
1438
|
/>
|
|
841
|
-
|
|
1439
|
+
: null}
|
|
842
1440
|
|
|
843
|
-
{size(menuTrailingItems) > 0 ?
|
|
1441
|
+
{size(menuTrailingItems) > 0 ?
|
|
844
1442
|
<MenuTrailingItems items={menuTrailingItems} size={rest.size} />
|
|
845
|
-
|
|
1443
|
+
: null}
|
|
846
1444
|
</ReqoreMenu>
|
|
847
1445
|
}
|
|
848
1446
|
/>
|
|
@@ -855,11 +1453,14 @@ export const TemplateField = memo(
|
|
|
855
1453
|
functions.expressions,
|
|
856
1454
|
handleSelectFunctionChange,
|
|
857
1455
|
handleTemplateToggleClick,
|
|
1456
|
+
handleUseCustomValueClick,
|
|
858
1457
|
hasOnlyAllowedValues,
|
|
859
1458
|
isTemplate,
|
|
860
1459
|
rest.readonly,
|
|
861
1460
|
rest.size,
|
|
862
1461
|
showTemplateToggle,
|
|
1462
|
+
// reaches the menu through `templateSupportsCustomValues`
|
|
1463
|
+
allowCustomValues,
|
|
863
1464
|
hasOnlyExpressions,
|
|
864
1465
|
type,
|
|
865
1466
|
value,
|
|
@@ -867,23 +1468,37 @@ export const TemplateField = memo(
|
|
|
867
1468
|
menuActions,
|
|
868
1469
|
menuTrailingItems,
|
|
869
1470
|
internalIsFunction,
|
|
1471
|
+
effectiveIsFunction,
|
|
870
1472
|
hasInputAffordance,
|
|
1473
|
+
// the row decides whether this field draws a menu at all
|
|
1474
|
+
rowMenu,
|
|
871
1475
|
]);
|
|
872
1476
|
|
|
873
1477
|
// When the type is a list, and it has an element type - that element type is different
|
|
874
1478
|
// from the field type, so we need to send down the full templates object
|
|
875
1479
|
// and the actual rendered field will filter it's own templates
|
|
876
1480
|
// This is a special case only for lists with element types
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1481
|
+
/* `undefined` when nothing is on offer, rather than a list with no items:
|
|
1482
|
+
an editor given a list draws the control that opens it, so a field whose
|
|
1483
|
+
templates were all filtered out by type opened an EMPTY menu. */
|
|
1484
|
+
const componentTemplates = useMemo(() => {
|
|
1485
|
+
if (type === 'list' && (rest.ui_element_type || rest.element_type)) {
|
|
1486
|
+
return templates;
|
|
1487
|
+
}
|
|
1488
|
+
return size(filteredTemplates?.items) ?
|
|
1489
|
+
{
|
|
1490
|
+
...filteredTemplates,
|
|
1491
|
+
...TemplatesListProps,
|
|
1492
|
+
}
|
|
1493
|
+
: undefined;
|
|
1494
|
+
}, [
|
|
1495
|
+
JSON.stringify(filteredTemplates),
|
|
1496
|
+
// the list-with-element-type branch hands this back untouched
|
|
1497
|
+
templates,
|
|
1498
|
+
rest.ui_element_type,
|
|
1499
|
+
rest.element_type,
|
|
1500
|
+
type,
|
|
1501
|
+
]);
|
|
887
1502
|
|
|
888
1503
|
if (effectiveIsFunction && !hasOnlyAllowedValues) {
|
|
889
1504
|
// SEAM (reqraft): `allowTextExpressions` swaps the IDE's bare builder
|
|
@@ -899,9 +1514,14 @@ export const TemplateField = memo(
|
|
|
899
1514
|
is_expression: true,
|
|
900
1515
|
value,
|
|
901
1516
|
}}
|
|
1517
|
+
// The host's per-ui_type editors reach this field through the
|
|
1518
|
+
// rest-spread; the expression shell needs them explicitly or the
|
|
1519
|
+
// builder's operands render "Unknown type!" for any consumer
|
|
1520
|
+
// ui_type (an assertion's `test-reference` Value, for one).
|
|
1521
|
+
componentOverrides={componentOverrides}
|
|
902
1522
|
localTemplates={templates}
|
|
903
1523
|
type={type as string}
|
|
904
|
-
returnType={(returnType ||
|
|
1524
|
+
returnType={(returnType || expressionDataType) as any}
|
|
905
1525
|
onChange={handleExpressionChange}
|
|
906
1526
|
readOnly={rest.readOnly || rest.disabled}
|
|
907
1527
|
expressions={rest.expressions}
|
|
@@ -909,9 +1529,29 @@ export const TemplateField = memo(
|
|
|
909
1529
|
serverHandled={rest.server_expression_handling}
|
|
910
1530
|
extraActions={extraActions}
|
|
911
1531
|
size={rest.size}
|
|
1532
|
+
// An author who typed the expression as text is already
|
|
1533
|
+
// writing in that language — dropping them into the visual
|
|
1534
|
+
// builder would make them find their own sentence again.
|
|
1535
|
+
defaultMode={expressionFromText !== null ? 'text' : 'visual'}
|
|
912
1536
|
reorder={reorder}
|
|
913
1537
|
/>
|
|
914
1538
|
</ReqoreErrorBoundary>
|
|
1539
|
+
{expressionFromText !== null ?
|
|
1540
|
+
<ReqoreButton
|
|
1541
|
+
fixed
|
|
1542
|
+
compact
|
|
1543
|
+
minimal
|
|
1544
|
+
icon='ArrowGoBackLine'
|
|
1545
|
+
// No `intent` with `minimal`: a Reqore intent is a FILL, and
|
|
1546
|
+
// `muted` as TEXT is 1.3:1 on the app surface.
|
|
1547
|
+
size={rest.size}
|
|
1548
|
+
className='dpql-detected-undo'
|
|
1549
|
+
tooltip={`Keep "${expressionFromText}" as plain text instead`}
|
|
1550
|
+
onClick={undoExpressionFromText}
|
|
1551
|
+
>
|
|
1552
|
+
Undo
|
|
1553
|
+
</ReqoreButton>
|
|
1554
|
+
: null}
|
|
915
1555
|
{renderControls()}
|
|
916
1556
|
</ReqoreControlGroup>
|
|
917
1557
|
);
|
|
@@ -925,10 +1565,11 @@ export const TemplateField = memo(
|
|
|
925
1565
|
is_expression: true,
|
|
926
1566
|
value,
|
|
927
1567
|
}}
|
|
1568
|
+
componentOverrides={componentOverrides}
|
|
928
1569
|
localTemplates={templates}
|
|
929
1570
|
level={level}
|
|
930
1571
|
type={type as string}
|
|
931
|
-
returnType={(returnType ||
|
|
1572
|
+
returnType={(returnType || expressionDataType) as any}
|
|
932
1573
|
onChange={handleExpressionChange}
|
|
933
1574
|
readOnly={rest.readOnly || rest.disabled}
|
|
934
1575
|
expressions={rest.expressions}
|
|
@@ -952,7 +1593,15 @@ export const TemplateField = memo(
|
|
|
952
1593
|
return <ReadOnlyTemplateTag value={templateValue} templates={templates} size={rest.size} />;
|
|
953
1594
|
}
|
|
954
1595
|
|
|
955
|
-
return
|
|
1596
|
+
return (
|
|
1597
|
+
<Comp
|
|
1598
|
+
value={value}
|
|
1599
|
+
onChange={onChange}
|
|
1600
|
+
name={name}
|
|
1601
|
+
{...rest}
|
|
1602
|
+
componentOverrides={componentOverrides}
|
|
1603
|
+
/>
|
|
1604
|
+
);
|
|
956
1605
|
}
|
|
957
1606
|
|
|
958
1607
|
return (
|
|
@@ -964,7 +1613,7 @@ export const TemplateField = memo(
|
|
|
964
1613
|
stack={false}
|
|
965
1614
|
verticalAlign='flex-start'
|
|
966
1615
|
>
|
|
967
|
-
{!isTemplate && allowCustomValues ?
|
|
1616
|
+
{!isTemplate && allowCustomValues ?
|
|
968
1617
|
<Component
|
|
969
1618
|
value={value}
|
|
970
1619
|
allowTemplates={allowTemplates}
|
|
@@ -972,32 +1621,60 @@ export const TemplateField = memo(
|
|
|
972
1621
|
name={name}
|
|
973
1622
|
level={level}
|
|
974
1623
|
{...rest}
|
|
1624
|
+
componentOverrides={componentOverrides}
|
|
975
1625
|
{...componentTypeProp}
|
|
976
1626
|
aria-label={fieldAriaLabel}
|
|
977
1627
|
className={`${className} template-selector`}
|
|
978
1628
|
templates={componentTemplates}
|
|
979
1629
|
/>
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
{
|
|
983
|
-
|
|
1630
|
+
: null}
|
|
1631
|
+
|
|
1632
|
+
{/* Template mode's editor for a value that can also be typed: each
|
|
1633
|
+
reference in it is a chip named as the catalogue names it, while the
|
|
1634
|
+
field still stores the plain string. A textarea spelled a chosen
|
|
1635
|
+
template `$local:name` wherever it was edited — in the Visual
|
|
1636
|
+
builder's operands, in every string field that takes templates. */}
|
|
1637
|
+
{isTemplate && templateSupportsCustomValues && !templateValueIsBracedToken ?
|
|
1638
|
+
<RichTextFormField
|
|
984
1639
|
className='template-selector'
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
...TemplatesListProps,
|
|
992
|
-
}}
|
|
993
|
-
onChange={handleTemplateFieldChange}
|
|
1640
|
+
valueFormat='text'
|
|
1641
|
+
singleLine={isSingleLineStringType('string')}
|
|
1642
|
+
value={typeof templateValue === 'string' ? templateValue : ''}
|
|
1643
|
+
templates={filteredTemplates}
|
|
1644
|
+
allowTemplates
|
|
1645
|
+
onChange={handleTemplateTextChange}
|
|
994
1646
|
{...rest}
|
|
995
1647
|
aria-label={fieldAriaLabel}
|
|
996
1648
|
/>
|
|
997
|
-
|
|
1649
|
+
: null}
|
|
1650
|
+
|
|
1651
|
+
{dpqlOffer ?
|
|
1652
|
+
<ReqoreControlGroup fixed stack size={rest.size}>
|
|
1653
|
+
<ReqoreButton
|
|
1654
|
+
compact
|
|
1655
|
+
icon='Functions'
|
|
1656
|
+
intent='info'
|
|
1657
|
+
className='dpql-detected-offer'
|
|
1658
|
+
tooltip={`"${dpqlOffer.text}" reads as an expression. It is also valid text here, so nothing has been changed — use it as an expression?`}
|
|
1659
|
+
onClick={acceptDpqlOffer}
|
|
1660
|
+
>
|
|
1661
|
+
Use as expression
|
|
1662
|
+
</ReqoreButton>
|
|
1663
|
+
<ReqoreButton
|
|
1664
|
+
compact
|
|
1665
|
+
icon='CloseLine'
|
|
1666
|
+
intent='info'
|
|
1667
|
+
className='dpql-detected-dismiss'
|
|
1668
|
+
tooltip='Keep it as plain text'
|
|
1669
|
+
onClick={declineDpqlOffer}
|
|
1670
|
+
/>
|
|
1671
|
+
</ReqoreControlGroup>
|
|
1672
|
+
: null}
|
|
998
1673
|
|
|
999
|
-
{
|
|
1000
|
-
|
|
1674
|
+
{(
|
|
1675
|
+
showTemplatesDropdown ||
|
|
1676
|
+
(isTemplate && templateSupportsCustomValues && templateValueIsBracedToken)
|
|
1677
|
+
) ?
|
|
1001
1678
|
<TemplateDropdownSelector
|
|
1002
1679
|
allowCustomValues={allowCustomValues}
|
|
1003
1680
|
templates={templates}
|
|
@@ -1009,10 +1686,27 @@ export const TemplateField = memo(
|
|
|
1009
1686
|
label={label}
|
|
1010
1687
|
hasOnlyAllowedValues={hasOnlyAllowedValues}
|
|
1011
1688
|
/>
|
|
1012
|
-
|
|
1689
|
+
: null}
|
|
1013
1690
|
|
|
1014
1691
|
{renderControls()}
|
|
1015
1692
|
</ReqoreControlGroup>
|
|
1016
1693
|
);
|
|
1017
1694
|
}
|
|
1018
1695
|
);
|
|
1696
|
+
|
|
1697
|
+
/**
|
|
1698
|
+
* The row channel belongs to the row's OWN editor: this field. It is read here
|
|
1699
|
+
* and hidden from everything this field renders, so a field nested inside it —
|
|
1700
|
+
* an expression operand, a list item — keeps drawing its own menu instead of
|
|
1701
|
+
* publishing actions into a row menu that cannot say which value they act on.
|
|
1702
|
+
* `undefined` outside a row, where this field draws its own menu too.
|
|
1703
|
+
*/
|
|
1704
|
+
export const TemplateField = memo((props: ITemplateFieldProps) => {
|
|
1705
|
+
const rowMenu = useRowMenu();
|
|
1706
|
+
|
|
1707
|
+
return (
|
|
1708
|
+
<RowMenuContext.Provider value={undefined}>
|
|
1709
|
+
<TemplateFieldImpl {...props} rowMenu={rowMenu} />
|
|
1710
|
+
</RowMenuContext.Provider>
|
|
1711
|
+
);
|
|
1712
|
+
});
|