@qoretechnologies/reqraft 0.10.55-pr.124.g43fa138 → 0.10.55
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 +273 -22
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +669 -192
- 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 +17 -2
- 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 +21 -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/number/Number.d.ts +12 -1
- package/dist/components/form/fields/number/Number.d.ts.map +1 -1
- package/dist/components/form/fields/number/Number.js +6 -3
- package/dist/components/form/fields/number/Number.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 +35 -8
- package/dist/helpers/validations.d.ts.map +1 -1
- package/dist/helpers/validations.js +162 -25
- 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 +10 -6
- 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 +1269 -201
- package/src/components/form/engine/FormEngine.tsx +922 -270
- 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 +17 -2
- 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 +102 -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/number/Number.tsx +25 -0
- 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 +203 -51
- 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
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import { ReqoreButton, ReqoreP } from '@qoretechnologies/reqore';
|
|
1
2
|
import { StoryObj } from '@storybook/react-vite';
|
|
2
3
|
import { expect, fireEvent, waitFor, within } from 'storybook/test';
|
|
3
4
|
import { useState } from 'react';
|
|
4
5
|
import { StoryMeta } from '../../../../types';
|
|
5
6
|
import { SelectFormField } from './Select';
|
|
7
|
+
import {
|
|
8
|
+
SELECT_DIALOG_MIN_HEIGHT,
|
|
9
|
+
SELECT_DIALOG_MIN_WIDTH,
|
|
10
|
+
UNAVAILABLE_ITEM_CLASS,
|
|
11
|
+
} from './SelectCollection';
|
|
6
12
|
|
|
7
13
|
const meta = {
|
|
8
14
|
component: SelectFormField,
|
|
@@ -478,3 +484,389 @@ export const CreatableOffersToCreate: Story = {
|
|
|
478
484
|
);
|
|
479
485
|
},
|
|
480
486
|
};
|
|
487
|
+
|
|
488
|
+
// ── per-item row affordances ──────────────────────────────────────────────
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* A caller's row control, as a component the item NAMES rather than one it
|
|
492
|
+
* renders. Items travel through `JSON.stringify(items)` memo keys, and a React
|
|
493
|
+
* element is circular through its fibre — `{ as, props }` is what survives.
|
|
494
|
+
*/
|
|
495
|
+
const ExplainThisItem = ({ explanation }: { explanation?: string }) => (
|
|
496
|
+
<ReqoreButton
|
|
497
|
+
size='tiny'
|
|
498
|
+
minimal
|
|
499
|
+
flat
|
|
500
|
+
compact
|
|
501
|
+
fixed
|
|
502
|
+
icon='QuestionLine'
|
|
503
|
+
className='select-item-explanation'
|
|
504
|
+
aria-label='What this is'
|
|
505
|
+
tooltip={{ handler: 'click', content: explanation, maxWidth: '360px' }}
|
|
506
|
+
// Reading is not choosing: the whole row is one click target.
|
|
507
|
+
onClick={(event) => event.stopPropagation()}
|
|
508
|
+
/>
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
/** Whatever a caller wants inside the row's own hover. */
|
|
512
|
+
const ItemProse = ({ text }: { text?: string }) => <ReqoreP>{text}</ReqoreP>;
|
|
513
|
+
|
|
514
|
+
const EXPLAINED_ITEMS = [
|
|
515
|
+
'Coverage: process',
|
|
516
|
+
'Coverage: runtime',
|
|
517
|
+
'Event: posted',
|
|
518
|
+
'Value: equals',
|
|
519
|
+
'Value: partial match',
|
|
520
|
+
'String: matches regex',
|
|
521
|
+
].map((display_name) => ({
|
|
522
|
+
display_name,
|
|
523
|
+
value: display_name,
|
|
524
|
+
short_desc: `What ${display_name} checks, in one line.`,
|
|
525
|
+
desc: `What ${display_name} checks, in one line.`,
|
|
526
|
+
title_actions: [{ as: ExplainThisItem, props: { explanation: `All of what ${display_name} is for.` } }],
|
|
527
|
+
tooltip: {
|
|
528
|
+
handler: 'hover' as const,
|
|
529
|
+
delay: 0,
|
|
530
|
+
placement: 'bottom-start' as const,
|
|
531
|
+
maxWidth: '360px',
|
|
532
|
+
content: { as: ItemProse, props: { text: `All of what ${display_name} is for.` } },
|
|
533
|
+
},
|
|
534
|
+
}));
|
|
535
|
+
|
|
536
|
+
const openTheCollection = async (canvasElement: HTMLElement) => {
|
|
537
|
+
await fireEvent.click(within(canvasElement).getByRole('button'));
|
|
538
|
+
await waitFor(
|
|
539
|
+
() => expect(document.querySelector('.reqraft-select-dialog .reqore-collection-item')).toBeTruthy(),
|
|
540
|
+
{ timeout: 5000 }
|
|
541
|
+
);
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
const firstRow = () =>
|
|
545
|
+
document.querySelector('.reqraft-select-dialog .reqore-collection-item') as HTMLElement;
|
|
546
|
+
|
|
547
|
+
export const ItemActionsInTheTitleBar: Story = {
|
|
548
|
+
args: {
|
|
549
|
+
forceDropdown: false,
|
|
550
|
+
items: EXPLAINED_ITEMS,
|
|
551
|
+
},
|
|
552
|
+
parameters: {
|
|
553
|
+
docs: {
|
|
554
|
+
description: {
|
|
555
|
+
story:
|
|
556
|
+
"Each offered item carries a control through `title_actions`, which renders it in the row's title bar, inline with the name. An item's `actions` render in the row BODY under the description and cost every entry a line of its own — measured at 31px of a 73px row on a list of twenty-nine. Both seams exist; this is the one for a single icon on a long list.",
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
async play({ canvasElement }) {
|
|
561
|
+
await openTheCollection(canvasElement);
|
|
562
|
+
const row = firstRow();
|
|
563
|
+
/* `:scope >`: `Element.querySelector` matches against the whole document
|
|
564
|
+
and then keeps what is inside the element, so a bare
|
|
565
|
+
`.reqore-panel-content x` finds a control in the row's own TITLE — the
|
|
566
|
+
modal around the collection is a panel too. */
|
|
567
|
+
await expect(
|
|
568
|
+
row.querySelector(':scope > .reqore-panel-title .select-item-explanation')
|
|
569
|
+
).toBeTruthy();
|
|
570
|
+
await expect(
|
|
571
|
+
row.querySelector(':scope > .reqore-panel-content .select-item-explanation')
|
|
572
|
+
).toBeNull();
|
|
573
|
+
},
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
export const ItemTooltipOnTheRow: Story = {
|
|
577
|
+
args: {
|
|
578
|
+
forceDropdown: false,
|
|
579
|
+
items: EXPLAINED_ITEMS,
|
|
580
|
+
},
|
|
581
|
+
parameters: {
|
|
582
|
+
docs: {
|
|
583
|
+
description: {
|
|
584
|
+
story:
|
|
585
|
+
"The per-item `tooltip` puts content on the ROW's own hover: the whole entry is the target, and the list pays nothing in height for something that is not on screen until a pointer rests. The content is a `{ as, props }` descriptor rather than a node, so an item still survives the `JSON.stringify(items)` memo key it travels under.",
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
async play({ canvasElement }) {
|
|
590
|
+
await openTheCollection(canvasElement);
|
|
591
|
+
await fireEvent.mouseEnter(firstRow());
|
|
592
|
+
await waitFor(
|
|
593
|
+
() =>
|
|
594
|
+
expect(
|
|
595
|
+
document.querySelector('[data-popper-placement]')?.textContent
|
|
596
|
+
).toContain('All of what Coverage: process is for.'),
|
|
597
|
+
{ timeout: 5000 }
|
|
598
|
+
);
|
|
599
|
+
},
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
const REFUSED_ITEMS = [
|
|
603
|
+
{
|
|
604
|
+
display_name: 'Coverage: process',
|
|
605
|
+
value: 'process_coverage',
|
|
606
|
+
short_desc: 'Passes when the process you name ran code.',
|
|
607
|
+
desc: 'Passes when the process you name ran code.',
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
display_name: 'Step: result',
|
|
611
|
+
value: 'step_result',
|
|
612
|
+
short_desc: 'Passes when the named step reported this result.',
|
|
613
|
+
desc: 'Passes when the named step reported this result.',
|
|
614
|
+
disabled: true,
|
|
615
|
+
unavailable: {
|
|
616
|
+
intent: 'warning' as const,
|
|
617
|
+
content:
|
|
618
|
+
'Unavailable because some dependencies are not fulfilled: "Interface Kind" must be "workflow"',
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
display_name: 'File: written',
|
|
623
|
+
value: 'file_written',
|
|
624
|
+
short_desc: 'Passes when the target wrote the file.',
|
|
625
|
+
desc: 'Passes when the target wrote the file.',
|
|
626
|
+
disabled: true,
|
|
627
|
+
messages: [
|
|
628
|
+
{ intent: 'danger' as const, content: 'This sandbox denies the FILESYSTEM domain.' },
|
|
629
|
+
],
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
display_name: 'Value: equals',
|
|
633
|
+
value: 'value_equals',
|
|
634
|
+
short_desc: 'Passes when the value is exactly equal.',
|
|
635
|
+
desc: 'Passes when the value is exactly equal.',
|
|
636
|
+
},
|
|
637
|
+
];
|
|
638
|
+
|
|
639
|
+
export const UnavailableItemsSayWhy: Story = {
|
|
640
|
+
args: {
|
|
641
|
+
forceDropdown: false,
|
|
642
|
+
items: REFUSED_ITEMS,
|
|
643
|
+
},
|
|
644
|
+
parameters: {
|
|
645
|
+
docs: {
|
|
646
|
+
description: {
|
|
647
|
+
story:
|
|
648
|
+
"A value that cannot be picked is disabled and says why, never hidden — a choice that vanishes takes its own explanation with it. Two things refuse one and they render identically: the value's own `depends_on`, judged against the form it stands in (row 2), and a refusal the server had already decided as `disabled` with a message (row 3), for what only the server can know. The reason reads FIRST, above the description a `messages` entry would have landed under; the name is dimmed and the cursor says `not-allowed`; and the row keeps every pointer event, because reqore's `disabled` is `pointer-events: none` and would take the row's tooltip with it. What makes the row inert is having no click handler at all.",
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
async play({ canvasElement }) {
|
|
653
|
+
await openTheCollection(canvasElement);
|
|
654
|
+
const rows = () =>
|
|
655
|
+
[...document.querySelectorAll<HTMLElement>('.reqraft-select-dialog .reqore-collection-item')];
|
|
656
|
+
await waitFor(() => expect(rows().length).toBe(4), { timeout: 5000 });
|
|
657
|
+
/* By name, not by index: the collection sorts its rows, so a positional
|
|
658
|
+
lookup asserts about whichever row the sort happened to put there. */
|
|
659
|
+
const rowNamed = (label: string) =>
|
|
660
|
+
rows().find((row) =>
|
|
661
|
+
row.querySelector(':scope > .reqore-panel-title')?.textContent?.includes(label)
|
|
662
|
+
) as HTMLElement;
|
|
663
|
+
|
|
664
|
+
await expect(rows().filter((row) => row.className.includes(UNAVAILABLE_ITEM_CLASS)).length).toBe(
|
|
665
|
+
2
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
// The one thing this must never do: `DisabledElement` would make it `none`
|
|
669
|
+
// and take the row's tooltip with it.
|
|
670
|
+
const gated = rowNamed('Step: result');
|
|
671
|
+
await expect(getComputedStyle(gated).pointerEvents).toBe('auto');
|
|
672
|
+
await expect(getComputedStyle(gated).cursor).toBe('not-allowed');
|
|
673
|
+
await expect(getComputedStyle(rowNamed('Value: equals')).cursor).toBe('pointer');
|
|
674
|
+
|
|
675
|
+
// A served refusal is promoted out of `messages` and printed once, above
|
|
676
|
+
// the description rather than under it.
|
|
677
|
+
const text =
|
|
678
|
+
(rowNamed('File: written').querySelector(':scope > .reqore-panel-content') as HTMLElement)
|
|
679
|
+
.textContent || '';
|
|
680
|
+
await expect(text.indexOf('FILESYSTEM')).toBeGreaterThan(-1);
|
|
681
|
+
await expect(text.indexOf('FILESYSTEM')).toBeLessThan(text.indexOf('wrote the file'));
|
|
682
|
+
},
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
/** The dialog box itself — the element `re-resizable` sizes and drags. */
|
|
686
|
+
const selectDialog = () => document.querySelector('.reqraft-select-dialog') as HTMLElement;
|
|
687
|
+
|
|
688
|
+
const dialogSize = () => {
|
|
689
|
+
const rect = selectDialog().getBoundingClientRect();
|
|
690
|
+
return { width: Math.round(rect.width), height: Math.round(rect.height) };
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
/** Whether a part of the picker is whole inside the dialog, not cut off by it. */
|
|
694
|
+
const isWholeInsideTheDialog = (selector: string) => {
|
|
695
|
+
const box = selectDialog().getBoundingClientRect();
|
|
696
|
+
const part = selectDialog().querySelector(selector)?.getBoundingClientRect();
|
|
697
|
+
|
|
698
|
+
if (!part) {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return (
|
|
703
|
+
part.width > 0 &&
|
|
704
|
+
part.height > 0 &&
|
|
705
|
+
part.left >= box.left - 0.5 &&
|
|
706
|
+
part.right <= box.right + 0.5 &&
|
|
707
|
+
part.top >= box.top - 0.5 &&
|
|
708
|
+
part.bottom <= box.bottom + 0.5
|
|
709
|
+
);
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Drag one of the dialog's resize handles, found by the cursor it sets.
|
|
714
|
+
*
|
|
715
|
+
* The first `col-resize` is the right edge and the first `row-resize` the top
|
|
716
|
+
* one, so a negative dx and a positive dy both shrink. The pointer is kept
|
|
717
|
+
* inside the box because the reqore we pin still clips the drawer, which
|
|
718
|
+
* amputates the outer half of every handle.
|
|
719
|
+
*/
|
|
720
|
+
const dragTheDialogBy = async (cursor: string, dx: number, dy: number) => {
|
|
721
|
+
const box = selectDialog();
|
|
722
|
+
const handle = [...box.querySelectorAll<HTMLElement>('div')].find((el) =>
|
|
723
|
+
(el.getAttribute('style') || '').includes(`cursor: ${cursor}`)
|
|
724
|
+
);
|
|
725
|
+
await expect(handle).toBeTruthy();
|
|
726
|
+
|
|
727
|
+
const rect = handle!.getBoundingClientRect();
|
|
728
|
+
const boxRect = box.getBoundingClientRect();
|
|
729
|
+
const x = Math.min(Math.max(rect.left + rect.width / 2, boxRect.left + 2), boxRect.right - 2);
|
|
730
|
+
const y = Math.min(Math.max(rect.top + rect.height / 2, boxRect.top + 2), boxRect.bottom - 2);
|
|
731
|
+
|
|
732
|
+
fireEvent.mouseDown(handle!, { clientX: x, clientY: y, button: 0 });
|
|
733
|
+
fireEvent.mouseMove(document, { clientX: x + dx, clientY: y + dy });
|
|
734
|
+
fireEvent.mouseUp(document, { clientX: x + dx, clientY: y + dy });
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
export const CannotBeDraggedBelowItsContent: Story = {
|
|
738
|
+
args: {
|
|
739
|
+
forceDropdown: false,
|
|
740
|
+
items: EXPLAINED_ITEMS,
|
|
741
|
+
},
|
|
742
|
+
parameters: {
|
|
743
|
+
docs: {
|
|
744
|
+
description: {
|
|
745
|
+
story:
|
|
746
|
+
'Drags the picker far past its minimum on both axes and shows that it stops at the size its own content needs — a row that fits, the search field, and the close control — rather than at the smaller floor the dialog chrome asks for.',
|
|
747
|
+
},
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
async play({ canvasElement }) {
|
|
751
|
+
await openTheCollection(canvasElement);
|
|
752
|
+
await waitFor(() => expect(dialogSize().width).toBe(700));
|
|
753
|
+
|
|
754
|
+
// Far past any floor, on both axes, the way it was reported: the picker
|
|
755
|
+
// was dragged to roughly 45px wide and still held a search box, a list and
|
|
756
|
+
// a close button stacked in a column nobody could read.
|
|
757
|
+
await dragTheDialogBy('col-resize', -3000, 0);
|
|
758
|
+
await dragTheDialogBy('row-resize', 0, 3000);
|
|
759
|
+
|
|
760
|
+
// The width at which a 300px collection row stops overhanging the dialog,
|
|
761
|
+
// and the height at which the header, the search field and one whole row
|
|
762
|
+
// all fit - the row taken at its tallest, the described row this same
|
|
763
|
+
// picker is also used with.
|
|
764
|
+
await waitFor(() =>
|
|
765
|
+
expect(dialogSize()).toEqual({
|
|
766
|
+
width: SELECT_DIALOG_MIN_WIDTH,
|
|
767
|
+
height: SELECT_DIALOG_MIN_HEIGHT,
|
|
768
|
+
})
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
// And what the floor was measured FOR is what has to be whole at it.
|
|
772
|
+
await expect(isWholeInsideTheDialog('.reqore-collection-item')).toBe(true);
|
|
773
|
+
await expect(isWholeInsideTheDialog('input')).toBe(true);
|
|
774
|
+
await expect(isWholeInsideTheDialog('.reqore-drawer-close-button')).toBe(true);
|
|
775
|
+
},
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Narrow the story's own viewport for the duration of `body`, then put it back.
|
|
780
|
+
*
|
|
781
|
+
* `page.viewport` is the same call qlip makes to capture a story at phone size,
|
|
782
|
+
* and it is reached through a DYNAMIC import: `vitest/browser` throws on import
|
|
783
|
+
* outside Browser Mode, so a static one would take the Storybook dev server
|
|
784
|
+
* down with it. Where the call is unavailable — the dev server, a docs render —
|
|
785
|
+
* the body simply runs at whatever width it already had, and the assertion
|
|
786
|
+
* below is written against the live viewport so it stays true there.
|
|
787
|
+
*/
|
|
788
|
+
const withViewport = async (
|
|
789
|
+
width: number,
|
|
790
|
+
height: number,
|
|
791
|
+
/** `narrowed` is false where the call was unavailable — see above. */
|
|
792
|
+
body: (narrowed: boolean) => Promise<void>
|
|
793
|
+
) => {
|
|
794
|
+
let resize: ((w: number, h: number) => Promise<void>) | undefined;
|
|
795
|
+
try {
|
|
796
|
+
({
|
|
797
|
+
page: { viewport: resize },
|
|
798
|
+
} = (await import('vitest/browser')) as never);
|
|
799
|
+
} catch {
|
|
800
|
+
resize = undefined;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const was = { width: window.innerWidth, height: window.innerHeight };
|
|
804
|
+
await resize?.(width, height);
|
|
805
|
+
try {
|
|
806
|
+
await body(!!resize && window.innerWidth === width);
|
|
807
|
+
} finally {
|
|
808
|
+
// Unconditional: a viewport left narrowed is a 320px browser for every
|
|
809
|
+
// story that runs after this one in the same file.
|
|
810
|
+
await resize?.(was.width, was.height);
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* On a phone the floor is the SCREEN, not the content.
|
|
816
|
+
*
|
|
817
|
+
* `min(320px, 90vw)` / `min(180px, 90vh)` — the cap is the other half of the
|
|
818
|
+
* floor, and it only engages below about a 356px viewport, which is why the
|
|
819
|
+
* story above cannot see it: at the width the test browser runs at, `320px` is
|
|
820
|
+
* simply the smaller of the two. A floor wider than the screen would be a
|
|
821
|
+
* dialog whose edges cannot both be reached, which is worse than a cramped one;
|
|
822
|
+
* 90vw is reqore's own maximum for a centred modal, so the floor stops exactly
|
|
823
|
+
* where the ceiling is.
|
|
824
|
+
*/
|
|
825
|
+
export const ItsFloorIsCappedAtTheScreen: Story = {
|
|
826
|
+
args: {
|
|
827
|
+
forceDropdown: false,
|
|
828
|
+
items: EXPLAINED_ITEMS,
|
|
829
|
+
},
|
|
830
|
+
parameters: {
|
|
831
|
+
// Captured at the width the assertion below runs at, so the picture is the
|
|
832
|
+
// capped dialog rather than a phone-sized story of a desktop-sized one.
|
|
833
|
+
qlip: { viewport: { width: 320, height: 800 } },
|
|
834
|
+
docs: {
|
|
835
|
+
description: {
|
|
836
|
+
story:
|
|
837
|
+
'Drags the picker past its floor on a 320px-wide screen — the floor gives way to the viewport (90vw / 90vh) instead of holding a 320x180 box the screen cannot show, which is the cap the desktop story never reaches.',
|
|
838
|
+
},
|
|
839
|
+
},
|
|
840
|
+
},
|
|
841
|
+
async play({ canvasElement }) {
|
|
842
|
+
await withViewport(320, 800, async (narrowed) => {
|
|
843
|
+
await openTheCollection(canvasElement);
|
|
844
|
+
await dragTheDialogBy('col-resize', -3000, 0);
|
|
845
|
+
await dragTheDialogBy('row-resize', 0, 3000);
|
|
846
|
+
|
|
847
|
+
/* The floor AS DECLARED, resolved against the viewport this is actually
|
|
848
|
+
running in — 288x180 at 320x800, and the plain 320x180 anywhere wide
|
|
849
|
+
enough that the cap does not bite. One assertion, true in both, and it
|
|
850
|
+
is the formula itself rather than a number copied out of it. */
|
|
851
|
+
const capped = {
|
|
852
|
+
width: Math.min(SELECT_DIALOG_MIN_WIDTH, Math.round(window.innerWidth * 0.9)),
|
|
853
|
+
height: Math.min(SELECT_DIALOG_MIN_HEIGHT, Math.round(window.innerHeight * 0.9)),
|
|
854
|
+
};
|
|
855
|
+
await waitFor(() => expect(dialogSize()).toEqual(capped), { timeout: 5000 });
|
|
856
|
+
|
|
857
|
+
// And the claim the cap exists to make: the dialog is never wider or
|
|
858
|
+
// taller than the screen it has to be read on.
|
|
859
|
+
await expect(dialogSize().width).toBeLessThanOrEqual(window.innerWidth);
|
|
860
|
+
await expect(dialogSize().height).toBeLessThanOrEqual(window.innerHeight);
|
|
861
|
+
|
|
862
|
+
if (!narrowed) {
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
/* Where the viewport really was narrowed — the test browser, which is
|
|
866
|
+
the run that gates — the cap is the branch that answered, and saying
|
|
867
|
+
so is what stops this story passing as a second copy of the desktop
|
|
868
|
+
one. */
|
|
869
|
+
await expect(dialogSize().width).toBeLessThan(SELECT_DIALOG_MIN_WIDTH);
|
|
870
|
+
});
|
|
871
|
+
},
|
|
872
|
+
};
|
|
@@ -12,11 +12,14 @@ import { TReqoreSelectItem } from '@qoretechnologies/reqore/dist/components/Sele
|
|
|
12
12
|
import { TReqoreIntent } from '@qoretechnologies/reqore/dist/constants/theme';
|
|
13
13
|
import { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
|
|
14
14
|
import { isEqual, size } from 'lodash';
|
|
15
|
-
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
|
15
|
+
import { memo, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
16
|
+
import { RowOpenPickerContext } from '../../engine/rowOpenPicker';
|
|
16
17
|
import {
|
|
17
18
|
getSelectItemShortDescription,
|
|
19
|
+
getSelectItemUnavailability,
|
|
18
20
|
ISelectFieldCollectionItem,
|
|
19
21
|
SelectFieldCollection,
|
|
22
|
+
UNAVAILABLE_ITEM_CLASS,
|
|
20
23
|
} from './SelectCollection';
|
|
21
24
|
|
|
22
25
|
export type ISelectFormFieldItem = ISelectFieldCollectionItem;
|
|
@@ -99,6 +102,27 @@ export const SelectFormField = memo(
|
|
|
99
102
|
}: ISelectFormFieldProps) => {
|
|
100
103
|
const [items, setItems] = useState<ISelectFormFieldItem[]>(fixItems(rawItems));
|
|
101
104
|
const [collectionOpen, setCollectionOpen] = useState(false);
|
|
105
|
+
// Reqore owns the anchored list's open state; this mirrors it so the
|
|
106
|
+
// trigger can say `aria-expanded` truthfully.
|
|
107
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
108
|
+
|
|
109
|
+
/* A compact row the author opened into THIS control asks it to show the
|
|
110
|
+
choices, instead of drawing a closed trigger that reprints the value the
|
|
111
|
+
collapsed row had just printed (see `RowOpenPickerContext`). A rising
|
|
112
|
+
edge, so closing the list again does not fight the instruction. */
|
|
113
|
+
// Composed rather than replaced: `rest` is spread onto the dropdown and a
|
|
114
|
+
// caller may already be watching it.
|
|
115
|
+
const callerToggle = (rest as { onToggleChange?: (open: boolean) => void }).onToggleChange;
|
|
116
|
+
const handleDropdownToggle = useCallback(
|
|
117
|
+
(open: boolean) => {
|
|
118
|
+
setDropdownOpen(open);
|
|
119
|
+
callerToggle?.(open);
|
|
120
|
+
},
|
|
121
|
+
[callerToggle]
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const rowAsksForTheChoices = useContext(RowOpenPickerContext);
|
|
125
|
+
|
|
102
126
|
|
|
103
127
|
useEffect(() => {
|
|
104
128
|
setItems(fixItems(rawItems));
|
|
@@ -133,15 +157,37 @@ export const SelectFormField = memo(
|
|
|
133
157
|
}, [predicate, items]);
|
|
134
158
|
|
|
135
159
|
const reqoreItems: IReqoreDropdownItem[] = useMemo(() => {
|
|
136
|
-
return filteredItems.map((item) =>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
160
|
+
return filteredItems.map((item) => {
|
|
161
|
+
/* The dropdown used to drop `messages` on the floor, so a value refused
|
|
162
|
+
with a reason arrived here as a row that simply would not respond.
|
|
163
|
+
The reason leads the description — a reader who has just been refused
|
|
164
|
+
a choice is looking for why, not for what the value would have
|
|
165
|
+
done. */
|
|
166
|
+
const unavailable = getSelectItemUnavailability(item);
|
|
167
|
+
const description = getItemDescription(item.value) as string;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
label: item.display_name || valueToShow(item.value),
|
|
171
|
+
description:
|
|
172
|
+
unavailable ?
|
|
173
|
+
[unavailable.title, unavailable.content, description].filter(Boolean).join(' — ')
|
|
174
|
+
: description,
|
|
175
|
+
/* Read in full on hover, however narrow the list is. */
|
|
176
|
+
tooltip: unavailable ? unavailable.content : undefined,
|
|
177
|
+
icon: unavailable ? ('LockLine' as const) : undefined,
|
|
178
|
+
value: item.value,
|
|
179
|
+
selected: isEqual(item.value, value),
|
|
180
|
+
intent: item.intent,
|
|
181
|
+
className: unavailable ? UNAVAILABLE_ITEM_CLASS : undefined,
|
|
182
|
+
/* Dimmed through reqore's own effect, and never through `disabled`:
|
|
183
|
+
that applies `DisabledElement` (`pointer-events: none`), which
|
|
184
|
+
takes the tooltip with it — the only place the whole reason fits.
|
|
185
|
+
The row is made inert by having no handler, as in the collection. */
|
|
186
|
+
effect: unavailable ? { opacity: 0.55 } : undefined,
|
|
187
|
+
style: unavailable ? { cursor: 'not-allowed' } : undefined,
|
|
188
|
+
onClick: unavailable ? undefined : () => handleSelectClick(item),
|
|
189
|
+
};
|
|
190
|
+
});
|
|
145
191
|
}, [JSON.stringify(filteredItems), value]) as IReqoreDropdownItem[];
|
|
146
192
|
|
|
147
193
|
const getItemShortDescription = useCallback(
|
|
@@ -163,6 +209,22 @@ export const SelectFormField = memo(
|
|
|
163
209
|
const hasItemsWithDesc = useCallback((data: ISelectFormFieldItem[]) => {
|
|
164
210
|
return data.some((item) => item.desc || item.short_desc);
|
|
165
211
|
}, []);
|
|
212
|
+
/* Which picker this control actually draws, on the SAME condition the
|
|
213
|
+
trigger below is chosen by. The collection modal is rendered beside that
|
|
214
|
+
trigger rather than as one of its branches, so it is not mutually
|
|
215
|
+
exclusive with the anchored list — and `forceDropdown` DEFAULTS TO TRUE,
|
|
216
|
+
which makes the list the usual trigger. Opening the collection for every
|
|
217
|
+
row that asked for its choices therefore put a dialog on top of a
|
|
218
|
+
dropdown, both listing the same items, for a single click. Reported on
|
|
219
|
+
qlip build #212: "Should this really open both the dropdown and the
|
|
220
|
+
modal dialog?" */
|
|
221
|
+
const picksFromTheCollection = !asMenu && hasItemsWithDesc(items) && !forceDropdown;
|
|
222
|
+
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
if (rowAsksForTheChoices && picksFromTheCollection) {
|
|
225
|
+
setCollectionOpen(true);
|
|
226
|
+
}
|
|
227
|
+
}, [rowAsksForTheChoices, picksFromTheCollection]);
|
|
166
228
|
|
|
167
229
|
const hasItemsWithError = useCallback((data: ISelectFormFieldItem[]) => {
|
|
168
230
|
return data.some(
|
|
@@ -264,14 +326,40 @@ export const SelectFormField = memo(
|
|
|
264
326
|
|
|
265
327
|
const creatableItems = useMemo<TReqoreSelectItem[]>(
|
|
266
328
|
() =>
|
|
267
|
-
filteredItems.map((item) =>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
329
|
+
filteredItems.map((item) => {
|
|
330
|
+
/* The same shape as `reqoreItems` above, for the same reason: this
|
|
331
|
+
branch passed the item's `disabled` straight to reqore, which
|
|
332
|
+
applies `DisabledElement` (`pointer-events: none`) and takes the
|
|
333
|
+
row's tooltip with it — so a value refused WITH a reason arrived
|
|
334
|
+
as a row that would not respond and would not explain.
|
|
335
|
+
|
|
336
|
+
What refuses it here is `handleCreatableChange`, because reqore
|
|
337
|
+
owns this list's selection and there is no per-row handler to
|
|
338
|
+
withhold. Against the 0.74.x we pin, `readOnly` marks the row and
|
|
339
|
+
does not yet refuse it; once the pin moves to a reqore whose list
|
|
340
|
+
declines a read-only row, the row refuses itself and the guard
|
|
341
|
+
below becomes a second lock on the same door. */
|
|
342
|
+
const unavailable = getSelectItemUnavailability(item);
|
|
343
|
+
const description = getItemDescription(item.value) as string;
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
value: valueToShow(item.value) as string,
|
|
347
|
+
label: item.display_name || (valueToShow(item.value) as string),
|
|
348
|
+
description:
|
|
349
|
+
unavailable ?
|
|
350
|
+
[unavailable.title, unavailable.content, description].filter(Boolean).join(' — ')
|
|
351
|
+
: description,
|
|
352
|
+
/* Read in full on hover, which `disabled` would have made
|
|
353
|
+
unreachable. */
|
|
354
|
+
tooltip: unavailable ? unavailable.content : undefined,
|
|
355
|
+
icon: unavailable ? ('LockLine' as const) : undefined,
|
|
356
|
+
className: unavailable ? UNAVAILABLE_ITEM_CLASS : undefined,
|
|
357
|
+
effect: unavailable ? { opacity: 0.55 } : undefined,
|
|
358
|
+
readOnly: !!unavailable,
|
|
359
|
+
intent: item.intent,
|
|
360
|
+
wrap: true,
|
|
361
|
+
};
|
|
362
|
+
}),
|
|
275
363
|
[filteredItems, getItemDescription]
|
|
276
364
|
);
|
|
277
365
|
|
|
@@ -285,6 +373,17 @@ export const SelectFormField = memo(
|
|
|
285
373
|
// the value the caller gave us — its own shape intact. Only a value
|
|
286
374
|
// the author created is a string of their own making.
|
|
287
375
|
const offered = filteredItems.find((item) => valueToShow(item.value) === next);
|
|
376
|
+
|
|
377
|
+
/* Where the refusal actually lands for this branch — see
|
|
378
|
+
`creatableItems`. A value the form will not accept is not accepted
|
|
379
|
+
however it was reached: by clicking its row, by pressing Enter on
|
|
380
|
+
it, or by typing its text into a creatable field, which is the same
|
|
381
|
+
value by another route. Clearing is never the answer: `next ===
|
|
382
|
+
undefined` above is the author clearing it themselves. */
|
|
383
|
+
if (offered && getSelectItemUnavailability(offered)) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
288
387
|
onChange?.(offered ? offered.value : next);
|
|
289
388
|
},
|
|
290
389
|
[filteredItems, onChange]
|
|
@@ -399,15 +498,36 @@ export const SelectFormField = memo(
|
|
|
399
498
|
)}
|
|
400
499
|
{asMenu ?
|
|
401
500
|
<ReqoreMenu>
|
|
402
|
-
{filteredItems.map((item) =>
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
501
|
+
{filteredItems.map((item) => {
|
|
502
|
+
/* This branch owns its handler, so it gets the full shape: no
|
|
503
|
+
`disabled` (that is `DisabledElement`, which would take the
|
|
504
|
+
row's tooltip with it), no handler, the reason printed under
|
|
505
|
+
the name and readable on hover, and `readOnly` for the cursor.
|
|
506
|
+
Dropping the handler is what makes the row inert. */
|
|
507
|
+
const unavailable = getSelectItemUnavailability(item);
|
|
508
|
+
const description = getSelectItemShortDescription(item);
|
|
509
|
+
|
|
510
|
+
return (
|
|
511
|
+
<ReqoreMenuItem
|
|
512
|
+
key={valueToShow(item.value)}
|
|
513
|
+
label={item.display_name || valueToShow(item.value)}
|
|
514
|
+
description={
|
|
515
|
+
unavailable ?
|
|
516
|
+
[unavailable.title, unavailable.content, description]
|
|
517
|
+
.filter(Boolean)
|
|
518
|
+
.join(' — ')
|
|
519
|
+
: description
|
|
520
|
+
}
|
|
521
|
+
tooltip={unavailable ? unavailable.content : undefined}
|
|
522
|
+
icon={unavailable ? 'LockLine' : undefined}
|
|
523
|
+
className={unavailable ? UNAVAILABLE_ITEM_CLASS : undefined}
|
|
524
|
+
effect={unavailable ? { opacity: 0.55 } : undefined}
|
|
525
|
+
readOnly={!!unavailable}
|
|
526
|
+
intent={item.intent}
|
|
527
|
+
onClick={unavailable ? undefined : () => handleSelectClick(item)}
|
|
528
|
+
/>
|
|
529
|
+
);
|
|
530
|
+
})}
|
|
411
531
|
</ReqoreMenu>
|
|
412
532
|
: hasItemsWithDesc(items) && !forceDropdown ?
|
|
413
533
|
<ReqoreButton
|
|
@@ -430,6 +550,14 @@ export const SelectFormField = memo(
|
|
|
430
550
|
badge={itemCount}
|
|
431
551
|
{...getIcon(items, value)}
|
|
432
552
|
rightIcon={showRightIcon ? 'ExpandUpDownLine' : undefined}
|
|
553
|
+
/* What this control IS: a trigger that opens the picker dialog, not
|
|
554
|
+
a place the value can be typed. Assistive technology has always
|
|
555
|
+
needed this, and a compact row's opening reads it too — a row the
|
|
556
|
+
author opened lands them in the list rather than on a closed
|
|
557
|
+
trigger reprinting the value the read row already showed
|
|
558
|
+
(`isClosedPickerTrigger`). */
|
|
559
|
+
aria-haspopup='dialog'
|
|
560
|
+
aria-expanded={collectionOpen}
|
|
433
561
|
onClick={(e) => {
|
|
434
562
|
e.stopPropagation();
|
|
435
563
|
setCollectionOpen(true);
|
|
@@ -446,6 +574,15 @@ export const SelectFormField = memo(
|
|
|
446
574
|
</ReqoreButton>
|
|
447
575
|
: <ReqoreDropdown
|
|
448
576
|
{...(rest as any)}
|
|
577
|
+
/* Same declaration as the dialog trigger above, for the anchored
|
|
578
|
+
list this branch draws instead. `onToggleChange` keeps
|
|
579
|
+
`aria-expanded` honest — reqore owns the popover's open state. */
|
|
580
|
+
aria-haspopup='listbox'
|
|
581
|
+
aria-expanded={dropdownOpen}
|
|
582
|
+
onToggleChange={handleDropdownToggle}
|
|
583
|
+
// Reqore opens the anchored list when this turns true, on mount or
|
|
584
|
+
// later — so the same instruction serves both pickers.
|
|
585
|
+
isDefaultOpen={rowAsksForTheChoices}
|
|
449
586
|
items={reqoreItems}
|
|
450
587
|
listCustomTheme={{
|
|
451
588
|
main: '#010811',
|