@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.
Files changed (370) hide show
  1. package/.claude/CLAUDE.md +25 -0
  2. package/.tasks/INDEX.md +1 -0
  3. package/.tasks/STORY_NETWORK_ISOLATION.md +98 -0
  4. package/README.md +1 -1
  5. package/design/FORM_ENGINE_COMPACT.md +34 -0
  6. package/design/FORM_ENGINE_EXPRESSIONS.md +110 -0
  7. package/design/FORM_ENGINE_FIELD_MIGRATION.md +155 -1
  8. package/design/IDE_INTEGRATION.md +13 -0
  9. package/design/RENDER_EXPRESSION_TRANSPORT.md +76 -10
  10. package/design/SELECT_DIALOG_FLOOR.md +86 -0
  11. package/design/SMART_EDITOR_UX.md +21 -0
  12. package/design/UNAVAILABLE_ALLOWED_VALUES.md +226 -0
  13. package/dist/components/composer/Composer.d.ts +8 -3
  14. package/dist/components/composer/Composer.d.ts.map +1 -1
  15. package/dist/components/composer/Composer.js.map +1 -1
  16. package/dist/components/dpqlEditor/DpqlEditor.d.ts.map +1 -1
  17. package/dist/components/dpqlEditor/DpqlEditor.js +3 -3
  18. package/dist/components/dpqlEditor/DpqlEditor.js.map +1 -1
  19. package/dist/components/dpqlEditor/dpqlHelpers.d.ts +6 -0
  20. package/dist/components/dpqlEditor/dpqlHelpers.d.ts.map +1 -1
  21. package/dist/components/dpqlEditor/dpqlHelpers.js +21 -2
  22. package/dist/components/dpqlEditor/dpqlHelpers.js.map +1 -1
  23. package/dist/components/dpqlEditor/dpqlProbe.d.ts +37 -0
  24. package/dist/components/dpqlEditor/dpqlProbe.d.ts.map +1 -0
  25. package/dist/components/dpqlEditor/dpqlProbe.js +203 -0
  26. package/dist/components/dpqlEditor/dpqlProbe.js.map +1 -0
  27. package/dist/components/dpqlEditor/dpqlTags.d.ts +8 -0
  28. package/dist/components/dpqlEditor/dpqlTags.d.ts.map +1 -1
  29. package/dist/components/dpqlEditor/dpqlTags.js +5 -1
  30. package/dist/components/dpqlEditor/dpqlTags.js.map +1 -1
  31. package/dist/components/dpqlEditor/index.d.ts +4 -0
  32. package/dist/components/dpqlEditor/index.d.ts.map +1 -1
  33. package/dist/components/dpqlEditor/index.js +5 -1
  34. package/dist/components/dpqlEditor/index.js.map +1 -1
  35. package/dist/components/dpqlEditor/types.d.ts +42 -2
  36. package/dist/components/dpqlEditor/types.d.ts.map +1 -1
  37. package/dist/components/dpqlEditor/types.js +0 -4
  38. package/dist/components/dpqlEditor/types.js.map +1 -1
  39. package/dist/components/dpqlEditor/useDpqlProbe.d.ts +4 -0
  40. package/dist/components/dpqlEditor/useDpqlProbe.d.ts.map +1 -0
  41. package/dist/components/dpqlEditor/useDpqlProbe.js +29 -0
  42. package/dist/components/dpqlEditor/useDpqlProbe.js.map +1 -0
  43. package/dist/components/dpqlEditor/useDpqlSession.d.ts +5 -2
  44. package/dist/components/dpqlEditor/useDpqlSession.d.ts.map +1 -1
  45. package/dist/components/dpqlEditor/useDpqlSession.js +8 -2
  46. package/dist/components/dpqlEditor/useDpqlSession.js.map +1 -1
  47. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  48. package/dist/components/form/engine/CompactRow.js +414 -128
  49. package/dist/components/form/engine/CompactRow.js.map +1 -1
  50. package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
  51. package/dist/components/form/engine/CompactToolbar.js +5 -2
  52. package/dist/components/form/engine/CompactToolbar.js.map +1 -1
  53. package/dist/components/form/engine/FormEngine.d.ts +272 -19
  54. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  55. package/dist/components/form/engine/FormEngine.js +666 -161
  56. package/dist/components/form/engine/FormEngine.js.map +1 -1
  57. package/dist/components/form/engine/FormFieldsSkeleton.d.ts +48 -0
  58. package/dist/components/form/engine/FormFieldsSkeleton.d.ts.map +1 -0
  59. package/dist/components/form/engine/FormFieldsSkeleton.js +41 -0
  60. package/dist/components/form/engine/FormFieldsSkeleton.js.map +1 -0
  61. package/dist/components/form/engine/OptionFieldMessages.d.ts +108 -2
  62. package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
  63. package/dist/components/form/engine/OptionFieldMessages.js +203 -13
  64. package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
  65. package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
  66. package/dist/components/form/engine/_structuredData/SchemaDataView.js +68 -5
  67. package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
  68. package/dist/components/form/engine/compactRowContext.d.ts +15 -1
  69. package/dist/components/form/engine/compactRowContext.d.ts.map +1 -1
  70. package/dist/components/form/engine/compactRowContext.js.map +1 -1
  71. package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
  72. package/dist/components/form/engine/compactRowStyles.js +55 -29
  73. package/dist/components/form/engine/compactRowStyles.js.map +1 -1
  74. package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
  75. package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
  76. package/dist/components/form/engine/optionActions.d.ts +37 -1
  77. package/dist/components/form/engine/optionActions.d.ts.map +1 -1
  78. package/dist/components/form/engine/optionActions.js +71 -1
  79. package/dist/components/form/engine/optionActions.js.map +1 -1
  80. package/dist/components/form/engine/optionsContext.d.ts +22 -0
  81. package/dist/components/form/engine/optionsContext.d.ts.map +1 -0
  82. package/dist/components/form/engine/optionsContext.js +20 -0
  83. package/dist/components/form/engine/optionsContext.js.map +1 -0
  84. package/dist/components/form/engine/readFirst.d.ts +42 -1
  85. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  86. package/dist/components/form/engine/readFirst.js +76 -8
  87. package/dist/components/form/engine/readFirst.js.map +1 -1
  88. package/dist/components/form/engine/rendererTypes.d.ts +42 -3
  89. package/dist/components/form/engine/rendererTypes.d.ts.map +1 -1
  90. package/dist/components/form/engine/rendererTypes.js +3 -1
  91. package/dist/components/form/engine/rendererTypes.js.map +1 -1
  92. package/dist/components/form/engine/rowFocus.d.ts +30 -0
  93. package/dist/components/form/engine/rowFocus.d.ts.map +1 -0
  94. package/dist/components/form/engine/rowFocus.js +93 -0
  95. package/dist/components/form/engine/rowFocus.js.map +1 -0
  96. package/dist/components/form/engine/rowMenuContext.d.ts +78 -0
  97. package/dist/components/form/engine/rowMenuContext.d.ts.map +1 -0
  98. package/dist/components/form/engine/rowMenuContext.js +103 -0
  99. package/dist/components/form/engine/rowMenuContext.js.map +1 -0
  100. package/dist/components/form/engine/rowOpenPicker.d.ts +27 -0
  101. package/dist/components/form/engine/rowOpenPicker.d.ts.map +1 -0
  102. package/dist/components/form/engine/rowOpenPicker.js +30 -0
  103. package/dist/components/form/engine/rowOpenPicker.js.map +1 -0
  104. package/dist/components/form/engine/typeChoices.d.ts +42 -0
  105. package/dist/components/form/engine/typeChoices.d.ts.map +1 -0
  106. package/dist/components/form/engine/typeChoices.js +33 -0
  107. package/dist/components/form/engine/typeChoices.js.map +1 -0
  108. package/dist/components/form/expressions/DpqlRendering.d.ts +26 -0
  109. package/dist/components/form/expressions/DpqlRendering.d.ts.map +1 -0
  110. package/dist/components/form/expressions/DpqlRendering.js +53 -0
  111. package/dist/components/form/expressions/DpqlRendering.js.map +1 -0
  112. package/dist/components/form/expressions/ExpressionField.d.ts +7 -1
  113. package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
  114. package/dist/components/form/expressions/ExpressionField.js +203 -45
  115. package/dist/components/form/expressions/ExpressionField.js.map +1 -1
  116. package/dist/components/form/expressions/argumentPresence.d.ts +84 -0
  117. package/dist/components/form/expressions/argumentPresence.d.ts.map +1 -0
  118. package/dist/components/form/expressions/argumentPresence.js +217 -0
  119. package/dist/components/form/expressions/argumentPresence.js.map +1 -0
  120. package/dist/components/form/expressions/builder/argumentLabel.d.ts.map +1 -1
  121. package/dist/components/form/expressions/builder/argumentLabel.js +9 -4
  122. package/dist/components/form/expressions/builder/argumentLabel.js.map +1 -1
  123. package/dist/components/form/expressions/builder/index.d.ts +15 -2
  124. package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
  125. package/dist/components/form/expressions/builder/index.js +21 -65
  126. package/dist/components/form/expressions/builder/index.js.map +1 -1
  127. package/dist/components/form/expressions/builder/renderTemplate.d.ts +4 -1
  128. package/dist/components/form/expressions/builder/renderTemplate.d.ts.map +1 -1
  129. package/dist/components/form/expressions/builder/renderTemplate.js +10 -67
  130. package/dist/components/form/expressions/builder/renderTemplate.js.map +1 -1
  131. package/dist/components/form/expressions/mockExpressions.d.ts.map +1 -1
  132. package/dist/components/form/expressions/mockExpressions.js +8 -4
  133. package/dist/components/form/expressions/mockExpressions.js.map +1 -1
  134. package/dist/components/form/expressions/renderExpressionToText.d.ts +1 -1
  135. package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
  136. package/dist/components/form/expressions/renderExpressionToText.js +95 -19
  137. package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
  138. package/dist/components/form/expressions/types.d.ts +7 -0
  139. package/dist/components/form/expressions/types.d.ts.map +1 -1
  140. package/dist/components/form/expressions/useExpressions.d.ts.map +1 -1
  141. package/dist/components/form/expressions/useExpressions.js +5 -2
  142. package/dist/components/form/expressions/useExpressions.js.map +1 -1
  143. package/dist/components/form/expressions/useRenderExpression.d.ts +7 -16
  144. package/dist/components/form/expressions/useRenderExpression.d.ts.map +1 -1
  145. package/dist/components/form/expressions/useRenderExpression.js +50 -126
  146. package/dist/components/form/expressions/useRenderExpression.js.map +1 -1
  147. package/dist/components/form/fields/Field.d.ts +18 -0
  148. package/dist/components/form/fields/Field.d.ts.map +1 -1
  149. package/dist/components/form/fields/Field.js +54 -8
  150. package/dist/components/form/fields/Field.js.map +1 -1
  151. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +11 -1
  152. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
  153. package/dist/components/form/fields/allowed-values/AllowedValues.js +52 -8
  154. package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
  155. package/dist/components/form/fields/auto/AutoFormField.d.ts +7 -1
  156. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  157. package/dist/components/form/fields/auto/AutoFormField.js +91 -28
  158. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  159. package/dist/components/form/fields/date/Date.d.ts +1 -1
  160. package/dist/components/form/fields/date/Date.d.ts.map +1 -1
  161. package/dist/components/form/fields/date/Date.js +16 -2
  162. package/dist/components/form/fields/date/Date.js.map +1 -1
  163. package/dist/components/form/fields/long-string/LongString.d.ts.map +1 -1
  164. package/dist/components/form/fields/long-string/LongString.js +14 -2
  165. package/dist/components/form/fields/long-string/LongString.js.map +1 -1
  166. package/dist/components/form/fields/multi-select/MultiSelectFormField.d.ts.map +1 -1
  167. package/dist/components/form/fields/multi-select/MultiSelectFormField.js +50 -4
  168. package/dist/components/form/fields/multi-select/MultiSelectFormField.js.map +1 -1
  169. package/dist/components/form/fields/rich-text/RichText.d.ts +47 -2
  170. package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
  171. package/dist/components/form/fields/rich-text/RichText.js +153 -12
  172. package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
  173. package/dist/components/form/fields/select/Select.d.ts.map +1 -1
  174. package/dist/components/form/fields/select/Select.js +120 -20
  175. package/dist/components/form/fields/select/Select.js.map +1 -1
  176. package/dist/components/form/fields/select/SelectCollection.d.ts +166 -9
  177. package/dist/components/form/fields/select/SelectCollection.d.ts.map +1 -1
  178. package/dist/components/form/fields/select/SelectCollection.js +200 -7
  179. package/dist/components/form/fields/select/SelectCollection.js.map +1 -1
  180. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts +2 -1
  181. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts.map +1 -1
  182. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js +3 -2
  183. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js.map +1 -1
  184. package/dist/components/form/fields/template/TemplateField.d.ts +34 -2
  185. package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
  186. package/dist/components/form/fields/template/TemplateField.js +606 -58
  187. package/dist/components/form/fields/template/TemplateField.js.map +1 -1
  188. package/dist/components/form/fields/template/TemplateText.d.ts +36 -0
  189. package/dist/components/form/fields/template/TemplateText.d.ts.map +1 -0
  190. package/dist/components/form/fields/template/TemplateText.js +80 -0
  191. package/dist/components/form/fields/template/TemplateText.js.map +1 -0
  192. package/dist/components/form/index.d.ts +2 -0
  193. package/dist/components/form/index.d.ts.map +1 -1
  194. package/dist/components/form/index.js +4 -0
  195. package/dist/components/form/index.js.map +1 -1
  196. package/dist/components/smartEditor/SmartEditor.d.ts.map +1 -1
  197. package/dist/components/smartEditor/SmartEditor.js +29 -8
  198. package/dist/components/smartEditor/SmartEditor.js.map +1 -1
  199. package/dist/components/smartEditor/useLspSession.d.ts +5 -1
  200. package/dist/components/smartEditor/useLspSession.d.ts.map +1 -1
  201. package/dist/components/smartEditor/useLspSession.js +16 -2
  202. package/dist/components/smartEditor/useLspSession.js.map +1 -1
  203. package/dist/components/supportTicket/TicketReplyBox.d.ts +8 -4
  204. package/dist/components/supportTicket/TicketReplyBox.d.ts.map +1 -1
  205. package/dist/components/supportTicket/TicketReplyBox.js.map +1 -1
  206. package/dist/helpers/common.d.ts.map +1 -1
  207. package/dist/helpers/common.js +7 -3
  208. package/dist/helpers/common.js.map +1 -1
  209. package/dist/helpers/dpqlDetection.d.ts +58 -0
  210. package/dist/helpers/dpqlDetection.d.ts.map +1 -0
  211. package/dist/helpers/dpqlDetection.js +164 -0
  212. package/dist/helpers/dpqlDetection.js.map +1 -0
  213. package/dist/helpers/optionUiTypes.d.ts +31 -3
  214. package/dist/helpers/optionUiTypes.d.ts.map +1 -1
  215. package/dist/helpers/optionUiTypes.js +36 -1
  216. package/dist/helpers/optionUiTypes.js.map +1 -1
  217. package/dist/helpers/options.d.ts +57 -0
  218. package/dist/helpers/options.d.ts.map +1 -1
  219. package/dist/helpers/options.js +97 -1
  220. package/dist/helpers/options.js.map +1 -1
  221. package/dist/helpers/templateItems.d.ts +47 -0
  222. package/dist/helpers/templateItems.d.ts.map +1 -0
  223. package/dist/helpers/templateItems.js +88 -0
  224. package/dist/helpers/templateItems.js.map +1 -0
  225. package/dist/helpers/templateText.d.ts +80 -0
  226. package/dist/helpers/templateText.d.ts.map +1 -0
  227. package/dist/helpers/templateText.js +291 -0
  228. package/dist/helpers/templateText.js.map +1 -0
  229. package/dist/helpers/templates.d.ts +30 -12
  230. package/dist/helpers/templates.d.ts.map +1 -1
  231. package/dist/helpers/templates.js +91 -33
  232. package/dist/helpers/templates.js.map +1 -1
  233. package/dist/helpers/validations.d.ts +17 -8
  234. package/dist/helpers/validations.d.ts.map +1 -1
  235. package/dist/helpers/validations.js +117 -22
  236. package/dist/helpers/validations.js.map +1 -1
  237. package/dist/hooks/useArgSchema.d.ts +2 -1
  238. package/dist/hooks/useArgSchema.d.ts.map +1 -1
  239. package/dist/hooks/useFetch/useFetch.d.ts +3 -1
  240. package/dist/hooks/useFetch/useFetch.d.ts.map +1 -1
  241. package/dist/hooks/useFetch/useFetch.js +47 -9
  242. package/dist/hooks/useFetch/useFetch.js.map +1 -1
  243. package/dist/hooks/useTemplates.d.ts +20 -0
  244. package/dist/hooks/useTemplates.d.ts.map +1 -1
  245. package/dist/hooks/useTemplates.js +34 -2
  246. package/dist/hooks/useTemplates.js.map +1 -1
  247. package/dist/index.d.ts +2 -1
  248. package/dist/index.d.ts.map +1 -1
  249. package/dist/index.js +5 -1
  250. package/dist/index.js.map +1 -1
  251. package/dist/utils/fetch.d.ts +151 -1
  252. package/dist/utils/fetch.d.ts.map +1 -1
  253. package/dist/utils/fetch.js +256 -44
  254. package/dist/utils/fetch.js.map +1 -1
  255. package/dist/utils/lspClient.d.ts +6 -0
  256. package/dist/utils/lspClient.d.ts.map +1 -1
  257. package/dist/utils/lspClient.js +62 -11
  258. package/dist/utils/lspClient.js.map +1 -1
  259. package/dist/utils/websocket.d.ts +6 -0
  260. package/dist/utils/websocket.d.ts.map +1 -1
  261. package/dist/utils/websocket.js +15 -7
  262. package/dist/utils/websocket.js.map +1 -1
  263. package/package.json +8 -4
  264. package/src/components/composer/Composer.tsx +7 -2
  265. package/src/components/dpqlEditor/DpqlEditor.stories.tsx +3 -119
  266. package/src/components/dpqlEditor/DpqlEditor.tsx +5 -2
  267. package/src/components/dpqlEditor/dpqlHelpers.ts +20 -1
  268. package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
  269. package/src/components/dpqlEditor/dpqlTags.ts +13 -0
  270. package/src/components/dpqlEditor/index.ts +4 -0
  271. package/src/components/dpqlEditor/types.ts +38 -2
  272. package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
  273. package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
  274. package/src/components/form/engine/CompactRow.tsx +487 -160
  275. package/src/components/form/engine/CompactToolbar.tsx +10 -3
  276. package/src/components/form/engine/FormEngine.stories.tsx +1294 -161
  277. package/src/components/form/engine/FormEngine.tsx +916 -213
  278. package/src/components/form/engine/FormEngineStickyHeaders.stories.tsx +331 -0
  279. package/src/components/form/engine/FormFieldsSkeleton.tsx +77 -0
  280. package/src/components/form/engine/OptionFieldMessages.tsx +300 -14
  281. package/src/components/form/engine/_structuredData/SchemaDataView.tsx +94 -3
  282. package/src/components/form/engine/compactRowContext.ts +15 -6
  283. package/src/components/form/engine/compactRowStyles.ts +56 -2
  284. package/src/components/form/engine/compactToolbarContext.ts +1 -1
  285. package/src/components/form/engine/optionActions.ts +89 -1
  286. package/src/components/form/engine/optionsContext.ts +24 -0
  287. package/src/components/form/engine/readFirst.ts +88 -8
  288. package/src/components/form/engine/rendererTypes.ts +48 -4
  289. package/src/components/form/engine/rowFocus.ts +101 -0
  290. package/src/components/form/engine/rowMenuContext.ts +159 -0
  291. package/src/components/form/engine/rowOpenPicker.ts +27 -0
  292. package/src/components/form/engine/typeChoices.ts +63 -0
  293. package/src/components/form/expressions/DpqlRendering.tsx +85 -0
  294. package/src/components/form/expressions/ExpressionField.stories.tsx +826 -48
  295. package/src/components/form/expressions/ExpressionField.tsx +269 -55
  296. package/src/components/form/expressions/argumentPresence.ts +213 -0
  297. package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +38 -21
  298. package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
  299. package/src/components/form/expressions/builder/index.tsx +39 -81
  300. package/src/components/form/expressions/builder/renderTemplate.tsx +23 -56
  301. package/src/components/form/expressions/dpqlMockLanguage.ts +565 -0
  302. package/src/components/form/expressions/dpqlMockLsp.ts +109 -83
  303. package/src/components/form/expressions/mockExpressions.ts +8 -4
  304. package/src/components/form/expressions/renderExpressionToText.ts +112 -22
  305. package/src/components/form/expressions/types.ts +7 -0
  306. package/src/components/form/expressions/useExpressions.ts +5 -1
  307. package/src/components/form/expressions/useRenderExpression.ts +43 -127
  308. package/src/components/form/fields/Field.tsx +99 -20
  309. package/src/components/form/fields/allowed-values/AllowedValues.tsx +106 -44
  310. package/src/components/form/fields/auto/AutoFormField.stories.tsx +85 -18
  311. package/src/components/form/fields/auto/AutoFormField.tsx +97 -21
  312. package/src/components/form/fields/date/Date.tsx +17 -1
  313. package/src/components/form/fields/long-string/LongString.tsx +15 -3
  314. package/src/components/form/fields/multi-select/MultiSelectFormField.tsx +65 -6
  315. package/src/components/form/fields/rich-text/RichText.tsx +248 -30
  316. package/src/components/form/fields/select/Select.stories.tsx +392 -0
  317. package/src/components/form/fields/select/Select.tsx +164 -27
  318. package/src/components/form/fields/select/SelectCollection.tsx +350 -6
  319. package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
  320. package/src/components/form/fields/template/TemplateField.stories.tsx +93 -14
  321. package/src/components/form/fields/template/TemplateField.tsx +825 -131
  322. package/src/components/form/fields/template/TemplateText.tsx +115 -0
  323. package/src/components/form/index.tsx +4 -0
  324. package/src/components/log/Log.stories.tsx +2 -1
  325. package/src/components/searchFilterBar/SearchFilterBar.stories.tsx +3 -3
  326. package/src/components/smartEditor/SmartEditor.tsx +41 -9
  327. package/src/components/smartEditor/__fixtures__/mockLspServer.ts +52 -3
  328. package/src/components/smartEditor/useLspSession.ts +21 -3
  329. package/src/components/supportTicket/TicketReplyBox.tsx +8 -4
  330. package/src/components/ticketThread/TicketThread.stories.tsx +1 -0
  331. package/src/helpers/common.ts +7 -3
  332. package/src/helpers/dpqlDetection.ts +209 -0
  333. package/src/helpers/optionUiTypes.ts +38 -0
  334. package/src/helpers/options.ts +96 -0
  335. package/src/helpers/templateItems.ts +81 -0
  336. package/src/helpers/templateText.ts +350 -0
  337. package/src/helpers/templates.ts +94 -41
  338. package/src/helpers/validations.ts +138 -39
  339. package/src/hooks/useFetch/useFetch.stories.tsx +6 -5
  340. package/src/hooks/useFetch/useFetch.tsx +53 -7
  341. package/src/hooks/useStorage/useStorage.stories.tsx +2 -1
  342. package/src/hooks/useTemplates.ts +41 -0
  343. package/src/hooks/useWebSocket/useWebsocket.stories.tsx +2 -1
  344. package/src/index.tsx +14 -1
  345. package/src/stores/currentUser/currentUser.stories.tsx +2 -1
  346. package/src/stories/storyNetwork.ts +92 -0
  347. package/src/utils/fetch.ts +311 -21
  348. package/src/utils/lspClient.ts +68 -11
  349. package/src/utils/websocket.ts +20 -9
  350. package/dist/components/form/engine/__fixtures__/basicFormOptions.d.ts +0 -73
  351. package/dist/components/form/engine/__fixtures__/basicFormOptions.d.ts.map +0 -1
  352. package/dist/components/form/engine/__fixtures__/basicFormOptions.js +0 -317
  353. package/dist/components/form/engine/__fixtures__/basicFormOptions.js.map +0 -1
  354. package/dist/components/form/engine/__fixtures__/fieldChromeOptions.d.ts +0 -41
  355. package/dist/components/form/engine/__fixtures__/fieldChromeOptions.d.ts.map +0 -1
  356. package/dist/components/form/engine/__fixtures__/fieldChromeOptions.js +0 -45
  357. package/dist/components/form/engine/__fixtures__/fieldChromeOptions.js.map +0 -1
  358. package/dist/components/form/expressions/dpqlMockLsp.d.ts +0 -3
  359. package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +0 -1
  360. package/dist/components/form/expressions/dpqlMockLsp.js +0 -91
  361. package/dist/components/form/expressions/dpqlMockLsp.js.map +0 -1
  362. package/dist/components/form/fields/template/__fixtures__/longExampleValue.d.ts +0 -2
  363. package/dist/components/form/fields/template/__fixtures__/longExampleValue.d.ts.map +0 -1
  364. package/dist/components/form/fields/template/__fixtures__/longExampleValue.js +0 -9
  365. package/dist/components/form/fields/template/__fixtures__/longExampleValue.js.map +0 -1
  366. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +0 -55
  367. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +0 -1
  368. package/dist/components/smartEditor/__fixtures__/mockLspServer.js +0 -126
  369. package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +0 -1
  370. package/dist/stories/Data/fileFieldArgSchema.json +0 -86
@@ -1,51 +1,36 @@
1
1
  // Copyright 2026 Qore Technologies, s.r.o.
2
- // The "Explain" seam. Turns an expression AST into a readable string.
2
+ // The "Explain" seam. Turns an expression AST into its readable rendering.
3
3
  //
4
- // The canonical rendering is the server's `DataProvider::renderExpression`,
5
- // reached over the LSP custom method `dpql/renderExpression` (added next to
6
- // `dpql/serialize` in `QorusLspWebSocketHandler.qc`). All consumers share ONE
7
- // render socket: the module-level `ReqraftLspClient` below rides the per-endpoint
8
- // `LspSharedConnection`, and JSON-RPC request ids associate each response
9
- // with the caller — N expression builders on one form means N independent
10
- // in-flight requests on one connection, no cross-wiring.
4
+ // The rendering is the server's `DataProvider::renderExpression`, reached over
5
+ // the LSP custom method `dpql/renderExpression` (added next to `dpql/serialize`
6
+ // in `QorusLspWebSocketHandler.qc`). All consumers share ONE render socket: the
7
+ // module-level `ReqraftLspClient` below rides the per-endpoint
8
+ // `LspSharedConnection`, and JSON-RPC request ids associate each response with
9
+ // the caller — N expression builders on one form means N independent in-flight
10
+ // requests on one connection, no cross-wiring.
11
11
  //
12
- // When the LSP is unreachable (or the server predates the method), `render`
13
- // falls back to the client-side approximation below; `serverRendering` tells
14
- // consumers which path served.
12
+ // There is no client-side stand-in. A Qorus form does not work without Qorus,
13
+ // so the only time the server was "unreachable" in practice was while the
14
+ // page's first connection was still opening — and racing that with a 1.5s timer
15
+ // replaced the real rendering with an approximation labelled "Approximate", on
16
+ // exactly the first Explain of every page. A rendering now waits for the
17
+ // connection; `null` means the server cannot render at all (it predates the
18
+ // method, or the connection gave up), which the caller says plainly.
15
19
  import { nanoid } from 'nanoid';
16
- import { useCallback, useEffect, useRef, useState } from 'react';
20
+ import { useCallback, useEffect, useRef } from 'react';
17
21
  import { ReqraftLspClient } from '../../../utils/lspClient';
18
- import { IExpressionSchema, IExpressionValue } from './types';
22
+ import { IExpressionValue } from './types';
19
23
 
24
+ /* The synchronous summary a collapsed row prints — see `readFirst.ts`. It is
25
+ not a stand-in for this rendering and is never shown in its place. */
20
26
  export { renderExpressionToText } from './renderExpressionToText';
21
- import { renderExpressionToText } from './renderExpressionToText';
22
-
23
- // ── The shared render transport ───────────────────────────────────────
24
- // One lazy module-level client for ALL `useRenderExpression` consumers.
25
- // Rendering needs no document/context state, so a single connection (shared
26
- // further with any open DPQL editors via `LspSharedConnection`) serves
27
- // every builder on the page.
28
27
 
29
28
  /** Coalesce rapid re-renders per consumer (live typing) — trailing edge. */
30
29
  const RENDER_DEBOUNCE_MS = 300;
31
- /** How long a render call waits for the socket before falling back. */
32
- const CONNECT_WAIT_MS = 1500;
33
- /** After a failed wait, fall back instantly for this long before re-waiting. */
34
- const CONNECT_RETRY_COOLDOWN_MS = 10000;
35
30
 
36
31
  let renderClient: ReqraftLspClient | null = null;
37
32
  /** Set after a `-32601 method not found` — the server predates the method. */
38
33
  let methodUnsupported = false;
39
- let lastConnectWaitFailedAt = 0;
40
-
41
- let lastServerRendering = false;
42
- const serverRenderingListeners = new Set<(on: boolean) => void>();
43
- const setLastServerRendering = (on: boolean): void => {
44
- if (on !== lastServerRendering) {
45
- lastServerRendering = on;
46
- serverRenderingListeners.forEach((listener) => listener(on));
47
- }
48
- };
49
34
 
50
35
  const getRenderClient = (): ReqraftLspClient => {
51
36
  if (!renderClient) {
@@ -59,55 +44,24 @@ const getRenderClient = (): ReqraftLspClient => {
59
44
  return renderClient;
60
45
  };
61
46
 
62
- /**
63
- * Wait (briefly) for the shared connection. `connect()` is re-entrant and the
64
- * underlying socket keeps its own reconnect schedule, so this only bounds how
65
- * long a single render call blocks before falling back — after a failed wait
66
- * we fall back instantly for a cooldown period instead of re-blocking.
67
- */
68
- const waitForConnection = async (client: ReqraftLspClient): Promise<boolean> => {
69
- if (client.isConnected) return true;
70
- if (Date.now() - lastConnectWaitFailedAt < CONNECT_RETRY_COOLDOWN_MS) {
71
- return false;
72
- }
73
- const connected = await Promise.race([
74
- client.connect().then(
75
- () => true,
76
- () => false
77
- ),
78
- new Promise<boolean>((resolve) => {
79
- setTimeout(() => resolve(false), CONNECT_WAIT_MS);
80
- }),
81
- ]);
82
- if (!connected || !client.isConnected) {
83
- lastConnectWaitFailedAt = Date.now();
84
- return false;
85
- }
86
- return true;
87
- };
88
-
89
47
  /** One rendered expression — what `renderRich` resolves with. */
90
48
  export interface IRenderedExpression {
91
49
  /** The readable rendering (plain text). */
92
50
  text: string;
93
51
  /**
94
52
  * The server's richtext form — template references arrive as `tag` nodes
95
- * (the IDE renders them as chips). `null` on the client-side fallback.
96
- * Convert with `richtextResponseToSlate` for `RichTextFormField`.
53
+ * (the IDE renders them as chips). Convert with `richtextResponseToSlate`.
97
54
  */
98
55
  richtext: unknown | null;
99
- /** `true` when the server `dpql/renderExpression` produced this result. */
100
- server: boolean;
101
56
  }
102
57
 
103
- /** Server-side render; `null` means "fall back to the client-side path". */
104
- const renderViaServer = async (
105
- value: IExpressionValue
106
- ): Promise<{ text: string; richtext: unknown | null } | null> => {
58
+ const renderViaServer = async (value: IExpressionValue): Promise<IRenderedExpression | null> => {
107
59
  if (methodUnsupported) return null;
108
60
  const client = getRenderClient();
109
- if (!(await waitForConnection(client))) return null;
110
61
  try {
62
+ // Waits as long as the connection does: it has its own reconnect schedule
63
+ // and rejects once it gives up, so this never outlives a dead socket.
64
+ await client.connect();
111
65
  const result = await client.customRequest<{ rendered?: string; richtext?: unknown }>(
112
66
  'dpql/renderExpression',
113
67
  // The server accepts both the bare `{exp, args}` AST and the
@@ -131,75 +85,50 @@ export const _resetRenderExpressionTransportForTests = (): void => {
131
85
  renderClient?.disconnect();
132
86
  renderClient = null;
133
87
  methodUnsupported = false;
134
- lastConnectWaitFailedAt = 0;
135
- lastServerRendering = false;
136
88
  };
137
89
 
138
90
  interface IPendingRender {
139
91
  value: IExpressionValue;
140
- expressions: IExpressionSchema[];
141
- resolvers: Array<(rendered: IRenderedExpression) => void>;
92
+ resolvers: Array<(rendered: IRenderedExpression | null) => void>;
142
93
  }
143
94
 
144
95
  export interface IUseRenderExpressionResult {
145
- /** Render an expression AST to a readable string. */
146
- render: (value: IExpressionValue, expressions?: IExpressionSchema[]) => Promise<string>;
96
+ /** The readable rendering of an expression AST, or `null` when the server cannot render. */
97
+ render: (value: IExpressionValue) => Promise<string | null>;
147
98
  /**
148
99
  * Like `render`, but also resolves the server's richtext form (template
149
- * references as `tag` nodes — chips) and which path served. For consumers
150
- * that style the rendering, e.g. the builder's Explain panel.
151
- */
152
- renderRich: (
153
- value: IExpressionValue,
154
- expressions?: IExpressionSchema[]
155
- ) => Promise<IRenderedExpression>;
156
- /**
157
- * `true` when the server `dpql/renderExpression` path served the last
158
- * render; `false` while on the client-side approximation (LSP unreachable
159
- * or the server predates the method).
100
+ * references as `tag` nodes — chips). `null` when there is nothing to render
101
+ * or the server cannot render it.
160
102
  */
161
- serverRendering: boolean;
103
+ renderRich: (value: IExpressionValue) => Promise<IRenderedExpression | null>;
162
104
  }
163
105
 
164
106
  export const useRenderExpression = (): IUseRenderExpressionResult => {
165
- const [serverRendering, setServerRendering] = useState(lastServerRendering);
166
107
  const pendingRef = useRef<IPendingRender | null>(null);
167
108
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
168
109
  const lastRunRef = useRef(0);
169
110
 
170
- useEffect(() => {
171
- serverRenderingListeners.add(setServerRendering);
172
- return () => {
173
- serverRenderingListeners.delete(setServerRendering);
111
+ useEffect(
112
+ () => () => {
174
113
  if (timerRef.current) {
175
114
  clearTimeout(timerRef.current);
176
115
  timerRef.current = null;
177
116
  }
178
- // Settle any coalesced callers with the synchronous fallback so no
179
- // promise is left hanging past unmount.
117
+ // Settle any coalesced callers so no promise is left hanging past unmount.
180
118
  const pending = pendingRef.current;
181
119
  pendingRef.current = null;
182
- pending?.resolvers.forEach((resolve) =>
183
- resolve({
184
- text: renderExpressionToText(pending.value, pending.expressions),
185
- richtext: null,
186
- server: false,
187
- })
188
- );
189
- };
190
- }, []);
120
+ pending?.resolvers.forEach((resolve) => resolve(null));
121
+ },
122
+ []
123
+ );
191
124
 
192
125
  const renderRich = useCallback(
193
- (
194
- value: IExpressionValue,
195
- expressions: IExpressionSchema[] = []
196
- ): Promise<IRenderedExpression> =>
197
- new Promise<IRenderedExpression>((resolve) => {
126
+ (value: IExpressionValue): Promise<IRenderedExpression | null> =>
127
+ new Promise<IRenderedExpression | null>((resolve) => {
198
128
  // Coalesce calls per consumer: rapid live updates collapse to the
199
129
  // newest AST; every awaiting caller gets that newest rendering.
200
130
  pendingRef.current = {
201
131
  value,
202
- expressions,
203
132
  resolvers: [...(pendingRef.current?.resolvers ?? []), resolve],
204
133
  };
205
134
 
@@ -208,20 +137,7 @@ export const useRenderExpression = (): IUseRenderExpressionResult => {
208
137
  pendingRef.current = null;
209
138
  if (!job) return;
210
139
  lastRunRef.current = Date.now();
211
- let outcome: IRenderedExpression;
212
- if (!job.value?.exp) {
213
- outcome = { text: '', richtext: null, server: false };
214
- } else {
215
- const served = await renderViaServer(job.value);
216
- setLastServerRendering(served !== null);
217
- outcome = served
218
- ? { ...served, server: true }
219
- : {
220
- text: renderExpressionToText(job.value, job.expressions),
221
- richtext: null,
222
- server: false,
223
- };
224
- }
140
+ const outcome = job.value?.exp ? await renderViaServer(job.value) : null;
225
141
  job.resolvers.forEach((r) => r(outcome));
226
142
  };
227
143
 
@@ -240,10 +156,10 @@ export const useRenderExpression = (): IUseRenderExpressionResult => {
240
156
  );
241
157
 
242
158
  const render = useCallback(
243
- (value: IExpressionValue, expressions: IExpressionSchema[] = []): Promise<string> =>
244
- renderRich(value, expressions).then((r) => r.text),
159
+ (value: IExpressionValue): Promise<string | null> =>
160
+ renderRich(value).then((r) => r?.text ?? null),
245
161
  [renderRich]
246
162
  );
247
163
 
248
- return { render, renderRich, serverRendering };
164
+ return { render, renderRich };
249
165
  };
@@ -2,7 +2,6 @@ import {
2
2
  IReqoreLabelProps,
3
3
  ReqoreCheckbox,
4
4
  ReqoreControlGroup,
5
- ReqoreSkeleton,
6
5
  ReqoreSpan,
7
6
  } from '@qoretechnologies/reqore';
8
7
  import { IWithReqoreSize } from '@qoretechnologies/reqore/dist/types/global';
@@ -26,7 +25,15 @@ import { ReqraftObjectFormField } from './object/Object';
26
25
  import { RichTextFormField } from './rich-text/RichText';
27
26
  import { rendersCreatableValueSelect } from './allowed-values/AllowedValues';
28
27
  import { SelectFormField } from './select/Select';
29
- import { getSelectItemShortDescription } from './select/SelectCollection';
28
+ import {
29
+ getSelectItemShortDescription,
30
+ getSelectItemUnavailability,
31
+ ISelectFieldCollectionItem,
32
+ ISelectFieldCollectionItemAction,
33
+ TSelectFieldCollectionItemTooltip,
34
+ UNAVAILABLE_ITEM_CLASS,
35
+ } from './select/SelectCollection';
36
+ import { useAllowedValueAvailability } from '../engine/OptionFieldMessages';
30
37
  import { StringFormField } from './string/String';
31
38
  import { ArrayAutoField } from './array/ArrayAutoField';
32
39
  import { AutoFormField } from './auto/AutoFormField';
@@ -38,6 +45,7 @@ import { IDataSchemaDefinition } from './schema-definition/types';
38
45
  // Direct import — circular dep (FormEngine → TemplateField → FormField → FormEngine) is safe
39
46
  // because modules are all loaded before any component renders
40
47
  import { FormEngine } from '../engine/FormEngine';
48
+ import { FormFieldsSkeleton } from '../engine/FormFieldsSkeleton';
41
49
 
42
50
  /** Soft Qore types (softint, softstring, …) render as their concrete base —
43
51
  * the soft/hard distinction is value-level, not UI. */
@@ -68,6 +76,24 @@ const mapQorusTypeToFormFieldType = (type: string): TFormFieldType => {
68
76
  // Re-export for consumers who need to construct allowed values
69
77
  export type { IQorusAllowedValue };
70
78
 
79
+ /**
80
+ * An allowed value, with the keys reqraft's pickers read.
81
+ *
82
+ * Declared here rather than imported whole because reqraft installs ts-toolkit
83
+ * `^0.5.80`, which carries none of them yet — the same precedent
84
+ * `IConditionalFieldMessage` set for a field's conditional messages. Every key
85
+ * is optional, so a plain `IQorusAllowedValue` still is one; all three collapse
86
+ * into the imported type once 0.5.83 publishes and reqraft can move its pin.
87
+ */
88
+ export interface IReqraftAllowedValue extends IQorusAllowedValue {
89
+ /** Offered only while every entry holds — a FIELD's `depends_on`, one level down. */
90
+ depends_on?: (string | string[])[];
91
+ /** A tooltip on the value's whole row in the picker. */
92
+ tooltip?: TSelectFieldCollectionItemTooltip;
93
+ /** Affordances inline with the value's name, where the row already has the height. */
94
+ title_actions?: ISelectFieldCollectionItemAction[];
95
+ }
96
+
71
97
  export interface IFormFieldProps<T extends TFormFieldType = TFormFieldType> extends Omit<
72
98
  IQorusFormFieldSchemaBase,
73
99
  'value' | 'default_value' | 'arg_schema' | 'element_type' | 'ui_element_type'
@@ -345,7 +371,8 @@ export const FormField = <T extends TFormFieldType>({
345
371
 
346
372
  case 'hash': {
347
373
  if (argSchemaLoading) {
348
- return <ReqoreSkeleton height='80px' />;
374
+ // A hash renders a nested FORM, so it waits in the shape of one.
375
+ return <FormFieldsSkeleton rows={2} />;
349
376
  }
350
377
  if (finalArgSchema) {
351
378
  return (
@@ -522,23 +549,47 @@ export const FormField = <T extends TFormFieldType>({
522
549
  }
523
550
  };
524
551
 
552
+ /* Resolved once for the whole list, against the form this field stands in:
553
+ the value's own `depends_on`, and a refusal the server had already decided.
554
+ Values that gate on nothing cost nothing — see the hook. */
555
+ const availability = useAllowedValueAvailability(allowed_values as IReqraftAllowedValue[]);
556
+
525
557
  /**
526
558
  * Maps an IQorusAllowedValue to the shape SelectFormField / checkboxes expect.
527
559
  * The actual selectable value lives at item.value.value (the wrapper has type metadata).
560
+ *
561
+ * The list used to be a whitelist of eleven keys, which silently dropped the
562
+ * row affordances (`tooltip`, `title_actions`) and `metadata` — so a host that
563
+ * built them, as the IDE's kind picker does, had to hand its items straight to
564
+ * `SelectFormField` and bypass this renderer altogether to keep them.
528
565
  */
529
- const mapAllowedValue = (item: IQorusAllowedValue) => ({
530
- display_name: item.display_name,
531
- short_desc: item.short_desc,
532
- desc: item.desc,
533
- icon: item.icon,
534
- image: item.image,
535
- badge: item.badge as TReqoreBadge,
536
- intent: item.intent,
537
- disabled: item.disabled,
538
- messages: item.messages,
539
- actions: item.actions,
540
- value: item.value?.value,
541
- });
566
+ const mapAllowedValue = (item: IQorusAllowedValue, index = -1): ISelectFieldCollectionItem => {
567
+ const extended = item as IReqraftAllowedValue;
568
+ const resolved = availability[index];
569
+
570
+ return {
571
+ display_name: item.display_name,
572
+ short_desc: item.short_desc,
573
+ desc: item.desc,
574
+ icon: item.icon,
575
+ image: item.image,
576
+ badge: item.badge as TReqoreBadge,
577
+ intent: item.intent,
578
+ messages: item.messages,
579
+ actions: item.actions,
580
+ title_actions: extended.title_actions,
581
+ tooltip: extended.tooltip,
582
+ metadata: item.metadata,
583
+ // Both sources of a refusal end up here, as one state: the click handler
584
+ // is dropped and the reason is printed, whichever of them decided.
585
+ disabled: item.disabled || resolved?.available === false,
586
+ unavailable:
587
+ resolved && !resolved.available ?
588
+ { title: resolved.title, content: resolved.reason as string, intent: resolved.intent }
589
+ : undefined,
590
+ value: item.value?.value,
591
+ };
592
+ };
542
593
 
543
594
  /**
544
595
  * Renders the allowed-values picker.
@@ -607,17 +658,45 @@ export const FormField = <T extends TFormFieldType>({
607
658
  {allowed_values.map((item, index) => {
608
659
  const itemValue = item.value?.value;
609
660
  const checked = isEqual(value, itemValue);
661
+ const mapped = mapAllowedValue(item, index);
662
+ const unavailable = getSelectItemUnavailability(mapped);
610
663
  return (
611
664
  <ReqoreCheckbox
612
665
  margin='right'
613
666
  key={index}
614
667
  label={item.display_name ?? JSON.stringify(itemValue)}
615
- tooltip={getSelectItemShortDescription(mapAllowedValue(item))}
616
- disabled={item.disabled}
668
+ tooltip={getSelectItemShortDescription(mapped)}
669
+ /* The reason is IN the row, not only on its hover: a checkbox
670
+ has no body to bury it in, and a hover is the one affordance
671
+ a touch screen and a keyboard never reach. */
672
+ description={unavailable?.content}
673
+ className={unavailable ? UNAVAILABLE_ITEM_CLASS : undefined}
674
+ /* Never reqore's `disabled` — it applies `DisabledElement`
675
+ (`pointer-events: none`), which would take the tooltip with
676
+ it. Dropping the handler is what makes the box inert; the
677
+ `disabled` this used to pass was the ITEM's, and it was the
678
+ one path in the picker family that destroyed its own
679
+ explanation. */
680
+ /* `labelEffect`, not `effect`: a non-switch `ReqoreCheckbox`
681
+ destructures `effect` out and never applies it — it reaches
682
+ only the switch rendering — so the dimming this asked for
683
+ never appeared at all. And the NAME is the right thing to
684
+ dim: `description` carries the reason, and a reason has to
685
+ stay readable. Same rule the collection rows follow. */
686
+ labelEffect={unavailable ? { opacity: 0.55 } : undefined}
687
+ /* `cursor: not-allowed` from reqore's own `ReadOnlyElement`
688
+ rather than a hand-rolled inline style. It leaves the
689
+ pointer events intact, which is what the tooltip is read
690
+ through; it does NOT withhold `onClick`, but this row
691
+ already withholds it below, so it is purely additive. */
692
+ readOnly={!!unavailable}
693
+ uncheckedIcon={unavailable ? 'LockLine' : undefined}
617
694
  checked={checked}
618
695
  intent={checked ? 'info' : undefined}
619
- onClick={() =>
620
- handleChange((checked ? undefined : itemValue) as TFormFieldValueType<T>)
696
+ onClick={
697
+ unavailable ? undefined : (
698
+ () => handleChange((checked ? undefined : itemValue) as TFormFieldValueType<T>)
699
+ )
621
700
  }
622
701
  />
623
702
  );
@@ -13,8 +13,14 @@ import { IReqoreButtonProps } from '@qoretechnologies/reqore/dist/components/But
13
13
  import { IQorusAllowedValue, TQorusType } from '@qoretechnologies/ts-toolkit';
14
14
  import { size as count, isEqual } from 'lodash';
15
15
  import { memo, useMemo } from 'react';
16
- import { getSelectItemShortDescription } from '../select/SelectCollection';
16
+ import {
17
+ getSelectItemShortDescription,
18
+ getSelectItemUnavailability,
19
+ UNAVAILABLE_ITEM_CLASS,
20
+ } from '../select/SelectCollection';
17
21
  import { ISelectFormFieldItem, SelectFormField as Select } from '../select/Select';
22
+ import { useAllowedValueAvailability } from '../../engine/OptionFieldMessages';
23
+ import type { IReqraftAllowedValue } from '../Field';
18
24
 
19
25
  export interface IFieldAllowedValuesProps extends Pick<
20
26
  IReqoreButtonProps,
@@ -40,13 +46,23 @@ const DISALLOW_COMPACT_AO_TYPES = ['connection'];
40
46
  * Whether a creatable field's value is carried by the chip picker rather than
41
47
  * by a raw editor with a suggestion list beside it.
42
48
  *
43
- * `string` only, and only with something to suggest. Every other shape a
49
+ * String-valued only, and only with something to suggest. Every other shape a
44
50
  * creatable field can declare either does not fit a one-line chip (a document
45
51
  * — `long-string`, `binary`, `data`; a hash; a list) or would come back out of
46
52
  * one as a string (a number, a date), and a field with nothing to offer is
47
53
  * better served by the editor it has always had. Those keep today's rendering:
48
54
  * the raw editor, with the saved-and-suggested picker under it.
49
55
  *
56
+ * `select-string` counts, and leaving it out was the bug. A picker field
57
+ * declares its storage type (`*string`) and its `ui_type` (`select-string`),
58
+ * and by the time a schema reaches here the two have been flattened to one:
59
+ * `type` arrives as `select-string`. So this returned false for every such
60
+ * field and NEITHER renderer stood down — the picker drew the value, and the
61
+ * saved-and-suggested list drew the same candidates again underneath it. A
62
+ * test's Service and Method fields showed 183 and 7 items twice over, in two
63
+ * controls that set one value. A select-string's value IS a string and fits the
64
+ * chip, which is the only thing this predicate is really asking.
65
+ *
50
66
  * Both the renderer of the picker (`FieldAllowedValues`) and the renderer of
51
67
  * the raw editor (`AutoFormField`, `FormField`) ask this, so that exactly one
52
68
  * of them draws the value.
@@ -55,7 +71,8 @@ export const rendersCreatableValueSelect = (
55
71
  type?: string,
56
72
  allowCreation?: boolean,
57
73
  items?: unknown[]
58
- ): boolean => !!allowCreation && type === 'string' && count(items) > 0;
74
+ ): boolean =>
75
+ !!allowCreation && (type === 'string' || type === 'select-string') && count(items) > 0;
59
76
 
60
77
  export const FieldAllowedValuesCheckGroup = memo(
61
78
  ({
@@ -87,44 +104,71 @@ export const FieldAllowedValuesCheckGroup = memo(
87
104
  : multiSelect ? 'Select one or more:'
88
105
  : 'Select one:'}
89
106
  </ReqoreSpan>
90
- {items?.map((item) => (
91
- <ReqoreCheckbox
92
- margin='right'
93
- key={item.value?.toString()}
94
- label={item.display_name || JSON.stringify(item.value)}
95
- tooltip={getSelectItemShortDescription(item)}
96
- disabled={rest.disabled}
97
- readOnly={rest.readOnly}
98
- intent={item.value === value ? 'info' : undefined}
99
- // `isEqual`, and only once there is a value to match. The single
100
- // -select branch compared JSON.stringify(value) with
101
- // JSON.stringify(item.value), and JSON.stringify(undefined) is
102
- // undefined — so an unset field whose items carry no resolved value
103
- // compared undefined with undefined and reported EVERY option as
104
- // checked, on a field that was simultaneously "This field is
105
- // required". Nothing selected must read as nothing selected.
106
- checked={
107
- multiSelect
108
- ? Array.isArray(value) && value.some((v) => isEqual(v, item.value))
109
- : value !== undefined && value !== null && isEqual(value, item.value)
110
- }
111
- onClick={() => {
112
- if (multiSelect) {
113
- let newValue: unknown[] = Array.isArray(value) ? [...value] : [];
114
-
115
- if (newValue.some((v) => isEqual(v, item.value))) {
116
- newValue = newValue.filter((v) => !isEqual(v, item.value));
117
- } else {
118
- newValue.push(item.value);
119
- }
107
+ {items?.map((item) => {
108
+ const unavailable = getSelectItemUnavailability(item);
120
109
 
121
- onChange(name, newValue);
122
- } else {
123
- onChange(name, isEqual(value, item.value) ? undefined : item.value);
110
+ return (
111
+ <ReqoreCheckbox
112
+ margin='right'
113
+ key={item.value?.toString()}
114
+ label={item.display_name || JSON.stringify(item.value)}
115
+ tooltip={getSelectItemShortDescription(item)}
116
+ /* The reason is IN the row: a checkbox has no body to put it in,
117
+ and a hover is the one affordance a touch screen and a keyboard
118
+ never reach. */
119
+ description={unavailable?.content}
120
+ className={unavailable ? UNAVAILABLE_ITEM_CLASS : undefined}
121
+ /* `labelEffect`, not `effect`: a non-switch `ReqoreCheckbox`
122
+ destructures `effect` out and never applies it, so the dimming
123
+ this asked for never appeared. The NAME is also the right
124
+ thing to dim — `description` above carries the reason, and a
125
+ reason has to stay readable. */
126
+ labelEffect={unavailable ? { opacity: 0.55 } : undefined}
127
+ uncheckedIcon={unavailable ? 'LockLine' : undefined}
128
+ /* The FIELD's `disabled` still goes through reqore — a field nobody
129
+ may edit has no explanation to protect. A refused VALUE never
130
+ does: `DisabledElement` is `pointer-events: none` and would take
131
+ its tooltip with it. */
132
+ disabled={rest.disabled}
133
+ /* `cursor: not-allowed` from reqore's own `ReadOnlyElement`,
134
+ which keeps the pointer events the tooltip is read through.
135
+ It does not withhold `onClick` — the handler below does. */
136
+ readOnly={rest.readOnly || !!unavailable}
137
+ intent={item.value === value ? 'info' : undefined}
138
+ // `isEqual`, and only once there is a value to match. The single
139
+ // -select branch compared JSON.stringify(value) with
140
+ // JSON.stringify(item.value), and JSON.stringify(undefined) is
141
+ // undefined — so an unset field whose items carry no resolved value
142
+ // compared undefined with undefined and reported EVERY option as
143
+ // checked, on a field that was simultaneously "This field is
144
+ // required". Nothing selected must read as nothing selected.
145
+ checked={
146
+ multiSelect
147
+ ? Array.isArray(value) && value.some((v) => isEqual(v, item.value))
148
+ : value !== undefined && value !== null && isEqual(value, item.value)
124
149
  }
125
- }}
126
- />
127
- ))}
150
+ onClick={
151
+ unavailable ? undefined : (
152
+ () => {
153
+ if (multiSelect) {
154
+ let newValue: unknown[] = Array.isArray(value) ? [...value] : [];
155
+
156
+ if (newValue.some((v) => isEqual(v, item.value))) {
157
+ newValue = newValue.filter((v) => !isEqual(v, item.value));
158
+ } else {
159
+ newValue.push(item.value);
160
+ }
161
+
162
+ onChange(name, newValue);
163
+ } else {
164
+ onChange(name, isEqual(value, item.value) ? undefined : item.value);
165
+ }
166
+ }
167
+ )
168
+ }
169
+ />
170
+ );
171
+ })}
128
172
  </ReqoreControlGroup>
129
173
  );
130
174
  }
@@ -146,17 +190,35 @@ export const FieldAllowedValues = memo(
146
190
  readOnly,
147
191
  label,
148
192
  }: IFieldAllowedValuesProps) => {
193
+ /* Resolved against the form this field stands in — the value's own
194
+ `depends_on`, and a refusal the server had already decided. Both end up
195
+ as one state on the item: no click handler, and a reason to read. */
196
+ const availability = useAllowedValueAvailability(items as IReqraftAllowedValue[]);
197
+
149
198
  const fullItems = useMemo(() => {
150
199
  const result = [
151
200
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
152
- ...items.map(({ metadata, value, ...rest }) => ({
153
- value: value?.value,
154
- ...rest,
155
- })),
201
+ ...items.map(({ metadata, value, ...rest }, index) => {
202
+ const resolved = availability[index];
203
+
204
+ return {
205
+ value: value?.value,
206
+ ...rest,
207
+ disabled: rest.disabled || resolved?.available === false,
208
+ unavailable:
209
+ resolved && !resolved.available ?
210
+ {
211
+ title: resolved.title,
212
+ content: resolved.reason as string,
213
+ intent: resolved.intent,
214
+ }
215
+ : undefined,
216
+ };
217
+ }),
156
218
  ] as ISelectFormFieldItem[];
157
219
 
158
220
  return result;
159
- }, [JSON.stringify(items), type, value, showSavedValues, disabled, readOnly]);
221
+ }, [JSON.stringify(items), availability, type, value, showSavedValues, disabled, readOnly]);
160
222
 
161
223
  const style = useMemo(() => ({ width: '100%' }), []);
162
224