@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
@@ -54,22 +54,24 @@ const waitForText = async (text: string | RegExp) => {
54
54
 
55
55
  // --- reqraft behavioral stories (kept from the field-migration batch) -------
56
56
 
57
- /** Auto type, no value — the picker is shown and the field area prompts for a type. */
57
+ /** Untyped, no value — the field opens on an editor to type into, never on a type question. */
58
58
  export const Empty: Story = {
59
59
  args: {},
60
60
  parameters: {
61
61
  docs: {
62
62
  description: {
63
63
  story:
64
- 'Renders AutoFormField with no value or defaultType — the type picker is shown and the field area prompts the operator to pick a data type.',
64
+ 'Renders AutoFormField with no value or defaultType. The field opens on a text editor to type into; it does not show a type picker or ask for a data type.',
65
65
  },
66
66
  },
67
67
  },
68
68
  async play({ canvasElement }) {
69
69
  const canvas = within(canvasElement);
70
- // The IDE auto resolves its type in a mount effect — assert asynchronously.
71
- await expect(await canvas.findByText('Please select data type')).toBeInTheDocument();
72
- // No concrete field is rendered yet.
70
+ // The type resolves in a mount effect — assert asynchronously.
71
+ await waitFor(() => expect(canvasElement.querySelector('textarea')).toBeInTheDocument(), {
72
+ timeout: 5000,
73
+ });
74
+ await expect(canvas.queryByText('Please select data type')).not.toBeInTheDocument();
73
75
  await expect(canvasElement.querySelector('input[type="number"]')).not.toBeInTheDocument();
74
76
  },
75
77
  };
@@ -179,7 +181,7 @@ export const Nullable: Story = {
179
181
  },
180
182
  };
181
183
 
182
- /** The picker can be restricted to a subset of types via `allowedTypes`. */
184
+ /** A field declaring several allowed types still picks between them. */
183
185
  export const AllowedTypesSubset: Story = {
184
186
  args: {
185
187
  allowedTypes: [
@@ -191,14 +193,14 @@ export const AllowedTypesSubset: Story = {
191
193
  docs: {
192
194
  description: {
193
195
  story:
194
- 'Renders AutoFormField with allowedTypes restricted to Integer and Text — the picker badge shows 2 available types instead of the full 14.',
196
+ 'Renders AutoFormField with allowedTypes restricted to Integer and Text. The type picker offers those 2 types (its badge reads 2), and the field can be typed into meanwhile without being asked for a data type.',
195
197
  },
196
198
  },
197
199
  },
198
- async play() {
200
+ async play({ canvasElement }) {
199
201
  // The picker badge reflects the restricted list — 2 instead of 14.
200
- await waitForText('Please select data type');
201
202
  await waitForText('2');
203
+ await expect(within(canvasElement).queryByText('Please select data type')).not.toBeInTheDocument();
202
204
  },
203
205
  };
204
206
 
@@ -206,31 +208,35 @@ export const AllowedTypesSubset: Story = {
206
208
  export const NoSoftTypes: Story = {
207
209
  args: {
208
210
  noSoft: true,
211
+ /* The picker belongs to a value with a type: an untyped field that is
212
+ empty is typed into, and one holding text has resolved to `string`. */
213
+ defaultType: 'auto',
214
+ value: 'some text',
209
215
  },
210
216
  parameters: {
211
217
  docs: {
212
218
  description: {
213
219
  story:
214
- 'Renders AutoFormField with noSoft enabled — the soft* type variants are hidden and the picker badge reads 9 instead of 14.',
220
+ 'Renders an untyped AutoFormField holding a text value with noSoft enabled. The picker for the type the value resolved to leaves out the soft* variants, so its badge reads 9 instead of 14.',
215
221
  },
216
222
  },
217
223
  },
218
- async play() {
219
- await waitForText('Please select data type');
224
+ async play({ canvasElement }) {
220
225
  await waitForText('9');
226
+ await expect(within(canvasElement).queryByText('Please select data type')).not.toBeInTheDocument();
221
227
  },
222
228
  };
223
229
 
224
230
  /**
225
231
  * The whole point of the migration: a `FormEngine` schema with a field typed
226
- * `auto` renders the picker for free, via FormEngine → TemplateField → auto.
232
+ * `auto` renders the untyped editor for free, via FormEngine → TemplateField → auto.
227
233
  */
228
234
  export const ViaFormEngine: Story = {
229
235
  parameters: {
230
236
  docs: {
231
237
  description: {
232
238
  story:
233
- 'Renders a FormEngine schema whose single option is typed as auto — the engine flows through TemplateField and renders the AutoFormField picker without any extra wiring.',
239
+ 'Renders a FormEngine schema whose single option is typed as auto. The engine flows through TemplateField to the untyped editor without any extra wiring: an editor to type into, with no data-type question.',
234
240
  },
235
241
  },
236
242
  },
@@ -255,15 +261,76 @@ export const ViaFormEngine: Story = {
255
261
  async play({ canvasElement }) {
256
262
  const canvas = within(canvasElement);
257
263
  // Generous timeout: findByText defaults to 1s, which flakes under CI load
258
- // while the engine boots the auto field's type picker (the rest of the suite
259
- // waits ~10s).
264
+ // while the engine boots the auto field (the rest of the suite waits ~10s).
260
265
  await expect(
261
266
  await canvas.findByText('My Auto Field', undefined, { timeout: 10000 })
262
267
  ).toBeInTheDocument();
263
- // The auto field renders its type picker inside the engine-driven form.
268
+ // The untyped field opens on an editor inside the engine-driven form, and
269
+ // never asks for a data type.
270
+ await waitFor(() => expect(canvasElement.querySelector('textarea')).toBeInTheDocument(), {
271
+ timeout: 10000,
272
+ });
273
+ await expect(canvas.queryByText('Please select data type')).not.toBeInTheDocument();
274
+ },
275
+ };
276
+
277
+ /**
278
+ * The ⋮ on that same field holds one group — "Set Custom Value" and the data
279
+ * types under it — and opens it, rather than asking for a click to reach the
280
+ * only thing on offer.
281
+ */
282
+ export const ViaFormEngineMenuOpensItsOnlyGroup: Story = {
283
+ parameters: {
284
+ docs: {
285
+ description: {
286
+ story:
287
+ 'The untyped field\'s ⋮ holds nothing but the "Set Custom Value" section, so that section is already open: the data types are one click from the menu, not two. Where a menu holds more than one group the sections stay shut, because there the click is the choice.',
288
+ },
289
+ },
290
+ },
291
+ render: () => {
292
+ const [val, setVal] = useState<any>({});
293
+ return (
294
+ <FormEngine
295
+ name='autoFieldMenuDemo'
296
+ options={{
297
+ myAutoField: {
298
+ type: 'auto',
299
+ ui_type: 'auto',
300
+ display_name: 'My Auto Field',
301
+ preselected: true,
302
+ },
303
+ }}
304
+ value={val}
305
+ onChange={(_n, v) => setVal(v)}
306
+ />
307
+ );
308
+ },
309
+ async play({ canvasElement }) {
310
+ const canvas = within(canvasElement);
264
311
  await expect(
265
- await canvas.findByText('Please select data type', undefined, { timeout: 10000 })
312
+ await canvas.findByText('My Auto Field', undefined, { timeout: 10000 })
266
313
  ).toBeInTheDocument();
314
+
315
+ const menu = await waitFor(
316
+ () => {
317
+ const el = canvasElement.querySelector('.template-more');
318
+ expect(el).toBeTruthy();
319
+ return el as HTMLElement;
320
+ },
321
+ { timeout: 10000 }
322
+ );
323
+ await userEvent.click(menu);
324
+
325
+ // The section still names its rows...
326
+ await waitFor(
327
+ () => expect(document.body.textContent).toContain('Set Custom Value'),
328
+ { timeout: 10000 }
329
+ );
330
+ // ...and the rows are there to click, with no header click in between.
331
+ await waitFor(() => expect(document.body.textContent).toContain('Ordered key-value pair'), {
332
+ timeout: 10000,
333
+ });
267
334
  },
268
335
  };
269
336
 
@@ -7,7 +7,6 @@ import {
7
7
  ReqoreControlGroup,
8
8
  ReqoreErrorBoundary,
9
9
  ReqoreMessage,
10
- ReqoreSpinner,
11
10
  ReqoreTag,
12
11
  } from '@qoretechnologies/reqore';
13
12
  import { IReqoreFormTemplates } from '@qoretechnologies/reqore/dist/components/Textarea';
@@ -19,6 +18,7 @@ import { useUpdateEffect } from 'react-use';
19
18
  import useMount from 'react-use/lib/useMount';
20
19
  import { typedToYaml, yamlToTyped } from '../../../../helpers/common';
21
20
  import { getListElementValue } from '../../../../helpers/options';
21
+ import { isSingleLineStringType } from '../../../../helpers/singleLineString';
22
22
  import {
23
23
  getTypeFromValue,
24
24
  getValueOrDefaultValue,
@@ -53,6 +53,8 @@ import { IDataSchemaDefinition } from '../schema-definition/types';
53
53
  import { ISelectFormFieldItem, SelectFormField } from '../select/Select';
54
54
  import { StringFormField } from '../string/String';
55
55
  import { UrlFormField } from '../url/Url';
56
+ import { FormFieldsSkeleton } from '../../engine/FormFieldsSkeleton';
57
+ import { isUntypedOptionType } from '../../../../helpers/optionUiTypes';
56
58
 
57
59
  /** UI superset of `TQorusType` — the IDE's `Field/systemOptions` `IQorusType`. */
58
60
  export type IQorusType = TQorusType | string;
@@ -62,9 +64,15 @@ export interface IAutoFieldProps
62
64
  IWithReqoreSize,
63
65
  Omit<
64
66
  TQorusFormFieldSchema,
65
- 'get_message' | 'return_message' | 'on_change' | 'type' | 'arg_schema'
67
+ 'get_message' | 'return_message' | 'on_change' | 'type' | 'arg_schema' | 'ui_type'
66
68
  > {
67
69
  name?: string;
70
+ /**
71
+ * The editor the schema asks for. Optional here although the shared
72
+ * descriptor requires it: the field renders from `type` / `defaultType` when
73
+ * there is none, and reads this only to pick an override or a special editor.
74
+ */
75
+ ui_type?: IQorusType;
68
76
  value?: any;
69
77
  default_value?: any;
70
78
  type?: IQorusType;
@@ -261,7 +269,7 @@ function AutoField<T = any>({
261
269
  let internalType: IQorusType;
262
270
  // If value already exists, but the type is auto or any
263
271
  // set the type based on the value
264
- if (value && (defType === 'auto' || defType === 'any') && !defaultInternalType) {
272
+ if (value && isUntypedOptionType(defType) && !defaultInternalType) {
265
273
  internalType = getTypeFromValue(maybeParseYaml(value)) as IQorusType;
266
274
  } else {
267
275
  internalType = defaultInternalType || defType;
@@ -296,7 +304,7 @@ function AutoField<T = any>({
296
304
  if (typeValue && typeValue !== currentType) {
297
305
  // If this is auto / any field
298
306
  // set the internal type
299
- if (typeValue === 'auto' || typeValue === 'any') {
307
+ if (isUntypedOptionType(typeValue)) {
300
308
  setInternalType(value ? (getTypeFromValue(maybeParseYaml(value)) as IQorusType) : 'any');
301
309
  } else {
302
310
  setInternalType(typeValue);
@@ -395,6 +403,13 @@ function AutoField<T = any>({
395
403
  arg_schema={arg_schema}
396
404
  element_type={element_type}
397
405
  ui_element_type={ui_element_type}
406
+ // Same reason as the three above: it is destructured out of `rest` so the
407
+ // primitive renderers do not spread it onto a DOM node, which also took
408
+ // it away from host editors — and a host editor is exactly what
409
+ // `inherit_props` exists to feed. A field declaring it got the values
410
+ // resolved and then had nowhere to send them, so every consumer
411
+ // downstream saw `undefined` and silently did nothing.
412
+ inheritedFromParent={inheritedFromParent}
398
413
  name={name}
399
414
  value={value}
400
415
  onChange={(val: any, emittedType?: IQorusType, emittedIsFunction?: boolean) => {
@@ -457,7 +472,10 @@ function AutoField<T = any>({
457
472
  }
458
473
 
459
474
  if (arg_schema && !finalArgSchema) {
460
- return <ReqoreSpinner size='small'>Loading field data...</ReqoreSpinner>;
475
+ /* One ROW, because this stands in for one field. It was a spinner reading
476
+ "Loading field data…", which is a different picture from every other
477
+ wait on the page and announces a delay the reader cannot act on. */
478
+ return <FormFieldsSkeleton rows={1} />;
461
479
  }
462
480
 
463
481
  const renderAllowedValues = (currentType: IQorusType) => {
@@ -490,6 +508,40 @@ function AutoField<T = any>({
490
508
  );
491
509
  };
492
510
 
511
+ /* Text that may hold template references is edited in the chip editor
512
+ whenever the field offers templates (`RichTextFormField`, `text` format):
513
+ each reference is drawn as the name it was chosen by and the value stays the
514
+ plain string. Text holding a reference anywhere but its start
515
+ (`Interface $local:id failed`) never enters template mode, so in a textarea
516
+ its references read as their spelling. */
517
+ const offersTemplates = rest.allowTemplates !== false && size(rest.templates?.items) > 0;
518
+ const renderTextEditor = (editorType: string) =>
519
+ offersTemplates ?
520
+ <RichTextFormField
521
+ {...rest}
522
+ valueFormat='text'
523
+ singleLine={isSingleLineStringType(editorType)}
524
+ allowTemplates
525
+ /* Stringified rather than blanked: an `auto` field resolved to a text
526
+ type can be holding a number, and showing an empty box for it reads
527
+ as the value having been lost. A non-string coming back out is not a
528
+ value this editor produced, so it is ignored rather than written as
529
+ '' over what is there. */
530
+ value={String(value ?? '')}
531
+ onChange={(next) => {
532
+ if (typeof next !== 'string') {
533
+ return;
534
+ }
535
+ handleChange(name, next);
536
+ }}
537
+ />
538
+ : <LongStringFormField
539
+ {...rest}
540
+ type={editorType}
541
+ onChange={(next) => handleChange(name, next)}
542
+ value={value}
543
+ />;
544
+
493
545
  const renderField = (currentType: IQorusType) => {
494
546
  // If this field is set to null
495
547
  if (isSetToNull) {
@@ -565,18 +617,35 @@ function AutoField<T = any>({
565
617
  // reqraft FormField vocabulary — the IDE calls it `string` (both render
566
618
  // the textarea field).
567
619
  switch (currentType) {
620
+ /* These four share an editor but not a shape: a `string` holds exactly
621
+ one line, while `data`, `binary` and `long-string` hold a document.
622
+ Without the type the field cannot tell them apart and treats them all
623
+ as documents, so an interface's Internal Name — which becomes a YAML
624
+ key — accepted Enter. Only text takes references; `data` and
625
+ `binary` are encoded content, where a chip would stand for nothing. */
568
626
  case 'string':
627
+ case 'long-string':
628
+ return renderTextEditor(currentType);
569
629
  case 'data':
570
630
  case 'binary':
571
- case 'long-string':
572
631
  return (
573
632
  <LongStringFormField
574
633
  {...rest}
575
- // These four share one editor but not one shape: a `string` holds
576
- // exactly one line, while `data`, `binary` and `long-string` hold
577
- // a document. Without the type the field cannot tell them apart
578
- // and treats them all as documents, so an interface's Internal
579
- // Name — which becomes a YAML key — accepted Enter.
634
+ /* Say what the field takes before it is typed into. The server
635
+ decodes a binary value as base64 unless it is told otherwise
636
+ (`lib/misc.ql` `_priv_parse_ui_hash_value_intern`, symmetric
637
+ with the `toBase64()` it encodes with), and bare hex is the
638
+ trap: it is not rejected, it is read as base64 and corrupts.
639
+ `validateField` already accepts exactly these three spellings
640
+ — this is that rule said up front instead of after the fact.
641
+ Kept short enough to read in one line at phone width, where a
642
+ longer hint wrapped out of the field's single visible row. */
643
+ placeholder={
644
+ currentType === 'binary' ?
645
+ ((rest as { placeholder?: string }).placeholder ??
646
+ 'Base64, 0x hex, or data: URL')
647
+ : (rest as { placeholder?: string }).placeholder
648
+ }
580
649
  type={currentType}
581
650
  onChange={(value) => handleChange(name, value)}
582
651
  value={value}
@@ -941,17 +1010,17 @@ function AutoField<T = any>({
941
1010
  />
942
1011
  );
943
1012
  }
1013
+ /* An UNTYPED field is typed into, never asked for a type.
1014
+
1015
+ It used to show a type picker and "Please select data type" — a
1016
+ question about storage ("is this a Text or a Number?") that an author
1017
+ writing a value often cannot answer, asked before they could write
1018
+ anything. What is typed is stored untyped, exactly as template mode
1019
+ stores it; a value of an explicit type is set from the field's ⋮ menu
1020
+ (see `FormEngine`'s `getCustomMenuTemplateItems`). */
944
1021
  case 'any':
945
- return null;
946
1022
  case 'auto':
947
- return (
948
- <ReqoreTag
949
- intent='warning'
950
- minimal
951
- icon='ErrorWarningLine'
952
- label='Please select data type'
953
- />
954
- );
1023
+ return renderTextEditor('string');
955
1024
  default:
956
1025
  return <ReqoreTag intent='danger' icon='SpamLine' label='Unknown type!' />;
957
1026
  }
@@ -964,9 +1033,16 @@ function AutoField<T = any>({
964
1033
  );
965
1034
  };
966
1035
 
1036
+ /* No picker while the type is unresolved: the field is typed into instead
1037
+ (see the `auto` / `any` editor above). A field declaring several allowed
1038
+ types still picks between THOSE — that is a choice the schema offers, not a
1039
+ question about storage. */
1040
+ const typeIsUnresolved =
1041
+ !currentInternalType || isUntypedOptionType(currentInternalType);
967
1042
  const showPicker =
968
1043
  size(allowedTypes) > 1 ||
969
- ((!size(rest.allowed_values) ||
1044
+ (!typeIsUnresolved &&
1045
+ (!size(rest.allowed_values) ||
970
1046
  (size(rest?.allowed_values) > 0 && !rest.allowed_values_creatable) ||
971
1047
  !size(rest.element_allowed_values) ||
972
1048
  (size(rest?.element_allowed_values) > 0 && !rest.element_allowed_values_creatable)) &&
@@ -17,7 +17,22 @@ const toFullIso = (value?: string | Date): string | Date | null => {
17
17
  return value;
18
18
  };
19
19
 
20
- export const DateFormField = ({ value, onChange, disabled, ...rest }: IDateFormFieldProps) => {
20
+ /**
21
+ * The language a date is written in: the page's own (`<html lang>`, which is the
22
+ * application's interface language), else the browser's preferred language.
23
+ *
24
+ * Reqore's DatePicker, given no locale, takes the first NON-English entry of
25
+ * `navigator.languages`, so a browser set to English with French as its second
26
+ * language showed "jj / mm / aaaa" in an English application.
27
+ */
28
+ const pageLocale = (): string | undefined => {
29
+ if (typeof document !== 'undefined' && document.documentElement.lang) {
30
+ return document.documentElement.lang;
31
+ }
32
+ return typeof navigator !== 'undefined' ? navigator.language : undefined;
33
+ };
34
+
35
+ export const DateFormField = ({ value, onChange, disabled, locale, ...rest }: IDateFormFieldProps) => {
21
36
  const theme = useReqoreTheme();
22
37
 
23
38
  const handleChange = (date: TDateValue): void => {
@@ -27,6 +42,7 @@ export const DateFormField = ({ value, onChange, disabled, ...rest }: IDateFormF
27
42
  return (
28
43
  <DatePicker
29
44
  {...rest}
45
+ locale={locale ?? pageLocale()}
30
46
  style={{ width: '100%' }}
31
47
  value={toFullIso(value) as TDateValue}
32
48
  onChange={handleChange}
@@ -1,6 +1,6 @@
1
1
  import { ReqoreTextarea } from '@qoretechnologies/reqore';
2
2
  import { IReqoreTextareaProps } from '@qoretechnologies/reqore/dist/components/Textarea';
3
- import { ChangeEvent, KeyboardEvent, useCallback, useEffect, useState } from 'react';
3
+ import { ChangeEvent, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
4
4
  import { useDebounce } from 'react-use';
5
5
  import {
6
6
  flattenToSingleLine,
@@ -29,16 +29,28 @@ export const LongStringFormField = ({
29
29
  ...rest
30
30
  }: ILongStringFormFieldProps) => {
31
31
  const [localValue, setLocalValue] = useState<string>(value ?? '');
32
+ /* What this field last TOLD its parent, which is not the same as the value it
33
+ was given. Comparing against the prop loses a clear wherever the parent
34
+ does not feed the value back: the prop stays `undefined`, so emptying the
35
+ text makes `localValue` '' and `value ?? ''` '' too, and the parent never
36
+ learns the field was emptied. Seeded from the initial value so a mount
37
+ reports nothing — an empty field's text is '' and its value `undefined`,
38
+ and reporting that difference marked every form holding an empty text
39
+ field as changed before anyone typed. */
40
+ const lastEmittedRef = useRef<string>(value ?? '');
32
41
 
33
42
  useEffect(() => {
34
- if (value !== localValue) {
43
+ if ((value ?? '') !== localValue) {
35
44
  setLocalValue(value ?? '');
36
45
  }
46
+ // A value from outside is not something to report back to whoever sent it.
47
+ lastEmittedRef.current = value ?? '';
37
48
  }, [value]);
38
49
 
39
50
  useDebounce(
40
51
  () => {
41
- if (localValue !== value) {
52
+ if (localValue !== lastEmittedRef.current) {
53
+ lastEmittedRef.current = localValue;
42
54
  onChange?.(localValue);
43
55
  }
44
56
  },
@@ -2,7 +2,11 @@ import { ReqoreMultiSelect } from '@qoretechnologies/reqore';
2
2
  import { TReqoreMultiSelectItem } from '@qoretechnologies/reqore/dist/components/MultiSelect';
3
3
  import { IQorusAllowedValue } from '@qoretechnologies/ts-toolkit';
4
4
  import { memo, useMemo } from 'react';
5
- import { getSelectItemShortDescription } from '../select/SelectCollection';
5
+ import {
6
+ getSelectItemShortDescription,
7
+ getSelectItemUnavailability,
8
+ UNAVAILABLE_ITEM_CLASS,
9
+ } from '../select/SelectCollection';
6
10
 
7
11
  export interface IMultiSelectFormFieldProps {
8
12
  /** Currently selected values (plain scalars) */
@@ -32,19 +36,62 @@ export const MultiSelectFormField = memo(
32
36
  [JSON.stringify(value)]
33
37
  );
34
38
 
39
+ /**
40
+ * The values this picker will not take, by their string form.
41
+ *
42
+ * Only a refusal the SERVER resolved reaches here: this component is handed
43
+ * the raw `allowed_values`, not the mapped items the single-value pickers
44
+ * build, so a value's own `depends_on` is not judged against the form —
45
+ * see `design/UNAVAILABLE_ALLOWED_VALUES.md`.
46
+ */
47
+ const refusedValues = useMemo<Set<string>>(
48
+ () =>
49
+ new Set(
50
+ items
51
+ .filter(
52
+ (item) =>
53
+ !!getSelectItemUnavailability({
54
+ disabled: item.disabled,
55
+ messages: item.messages,
56
+ })
57
+ )
58
+ .map((item) => String(item.value?.value ?? ''))
59
+ ),
60
+ [JSON.stringify(items)]
61
+ );
62
+
35
63
  const reqoreItems = useMemo<TReqoreMultiSelectItem[]>(() => {
36
64
  const base = items.map(
37
65
  (item): TReqoreMultiSelectItem => {
38
66
  const description = getSelectItemShortDescription(item);
67
+ /* Never reqore's `disabled` for a refused VALUE: it applies
68
+ `DisabledElement` (`pointer-events: none`), which takes the row's
69
+ tooltip with it — the one place the whole reason fits. The
70
+ refusal itself is in `onValueChange` below, because reqore owns
71
+ this list's selection and there is no per-row handler to drop. */
72
+ const unavailable = getSelectItemUnavailability({
73
+ disabled: item.disabled,
74
+ messages: item.messages,
75
+ });
39
76
 
40
77
  return {
41
78
  value: item.value?.value as string,
42
79
  label: item.display_name ?? String(item.value?.value ?? ''),
43
- description,
80
+ description:
81
+ unavailable ?
82
+ [unavailable.title, unavailable.content, description].filter(Boolean).join(' — ')
83
+ : description,
84
+ tooltip: unavailable ? unavailable.content : undefined,
85
+ icon: unavailable ? ('LockLine' as const) : undefined,
86
+ className: unavailable ? UNAVAILABLE_ITEM_CLASS : undefined,
87
+ effect: unavailable ? { opacity: 0.55 } : undefined,
88
+ readOnly: !!unavailable,
44
89
  wrap: true,
45
- // IDE parity: when `*` is selected, every other item is disabled.
46
- disabled:
47
- !!item.disabled || (selectedValues.includes('*') && item.value?.value !== '*'),
90
+ /* IDE parity: when `*` is selected, every other item is disabled.
91
+ That one stays reqore's `disabled` — the wildcard is a state of
92
+ the SELECTION rather than a refusal of the value, and it has no
93
+ reason of its own to protect. */
94
+ disabled: selectedValues.includes('*') && item.value?.value !== '*',
48
95
  };
49
96
  }
50
97
  );
@@ -63,8 +110,20 @@ export const MultiSelectFormField = memo(
63
110
  items={reqoreItems}
64
111
  value={selectedValues}
65
112
  onValueChange={(selected) => {
113
+ /* The refusal, for the same reason the rows carry no `disabled`: the
114
+ row itself has no handler to withhold, so this is where a refused
115
+ value is dropped. A value already HELD is kept even once it is
116
+ refused — the picker will not take it again, but clearing an
117
+ answer the author gave because a sibling edit closed the value is
118
+ worse than holding one the form can no longer offer.
119
+
120
+ Before the wildcard, so that a refused `*` is refused too. */
121
+ const accepted = selected.filter(
122
+ (val) => selectedValues.includes(val) || !refusedValues.has(val)
123
+ );
124
+
66
125
  // IDE parity: picking the `*` wildcard collapses the selection.
67
- onChange?.(selected.includes('*') ? ['*'] : (selected as unknown[]));
126
+ onChange?.(accepted.includes('*') ? ['*'] : (accepted as unknown[]));
68
127
  }}
69
128
  disabled={disabled}
70
129
  size={size as any}