@qoretechnologies/reqraft 0.10.53 → 0.10.54-pr.115.g9bc96ed

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) 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_EXPRESSIONS.md +110 -0
  6. package/design/FORM_ENGINE_FIELD_MIGRATION.md +56 -1
  7. package/design/RENDER_EXPRESSION_TRANSPORT.md +76 -10
  8. package/design/SMART_EDITOR_UX.md +21 -0
  9. package/dist/components/dpqlEditor/DpqlEditor.d.ts.map +1 -1
  10. package/dist/components/dpqlEditor/DpqlEditor.js +3 -3
  11. package/dist/components/dpqlEditor/DpqlEditor.js.map +1 -1
  12. package/dist/components/dpqlEditor/dpqlHelpers.d.ts +6 -0
  13. package/dist/components/dpqlEditor/dpqlHelpers.d.ts.map +1 -1
  14. package/dist/components/dpqlEditor/dpqlHelpers.js +21 -2
  15. package/dist/components/dpqlEditor/dpqlHelpers.js.map +1 -1
  16. package/dist/components/dpqlEditor/dpqlProbe.d.ts +37 -0
  17. package/dist/components/dpqlEditor/dpqlProbe.d.ts.map +1 -0
  18. package/dist/components/dpqlEditor/dpqlProbe.js +203 -0
  19. package/dist/components/dpqlEditor/dpqlProbe.js.map +1 -0
  20. package/dist/components/dpqlEditor/dpqlTags.d.ts +8 -0
  21. package/dist/components/dpqlEditor/dpqlTags.d.ts.map +1 -1
  22. package/dist/components/dpqlEditor/dpqlTags.js +5 -1
  23. package/dist/components/dpqlEditor/dpqlTags.js.map +1 -1
  24. package/dist/components/dpqlEditor/index.d.ts +4 -0
  25. package/dist/components/dpqlEditor/index.d.ts.map +1 -1
  26. package/dist/components/dpqlEditor/index.js +5 -1
  27. package/dist/components/dpqlEditor/index.js.map +1 -1
  28. package/dist/components/dpqlEditor/types.d.ts +42 -2
  29. package/dist/components/dpqlEditor/types.d.ts.map +1 -1
  30. package/dist/components/dpqlEditor/types.js +0 -4
  31. package/dist/components/dpqlEditor/types.js.map +1 -1
  32. package/dist/components/dpqlEditor/useDpqlProbe.d.ts +4 -0
  33. package/dist/components/dpqlEditor/useDpqlProbe.d.ts.map +1 -0
  34. package/dist/components/dpqlEditor/useDpqlProbe.js +29 -0
  35. package/dist/components/dpqlEditor/useDpqlProbe.js.map +1 -0
  36. package/dist/components/dpqlEditor/useDpqlSession.d.ts +5 -2
  37. package/dist/components/dpqlEditor/useDpqlSession.d.ts.map +1 -1
  38. package/dist/components/dpqlEditor/useDpqlSession.js +8 -2
  39. package/dist/components/dpqlEditor/useDpqlSession.js.map +1 -1
  40. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  41. package/dist/components/form/engine/CompactRow.js +192 -45
  42. package/dist/components/form/engine/CompactRow.js.map +1 -1
  43. package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
  44. package/dist/components/form/engine/CompactToolbar.js +5 -2
  45. package/dist/components/form/engine/CompactToolbar.js.map +1 -1
  46. package/dist/components/form/engine/FormEngine.d.ts +160 -12
  47. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  48. package/dist/components/form/engine/FormEngine.js +376 -75
  49. package/dist/components/form/engine/FormEngine.js.map +1 -1
  50. package/dist/components/form/engine/FormFieldsSkeleton.d.ts +48 -0
  51. package/dist/components/form/engine/FormFieldsSkeleton.d.ts.map +1 -0
  52. package/dist/components/form/engine/FormFieldsSkeleton.js +38 -0
  53. package/dist/components/form/engine/FormFieldsSkeleton.js.map +1 -0
  54. package/dist/components/form/engine/OptionFieldMessages.d.ts +17 -2
  55. package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
  56. package/dist/components/form/engine/OptionFieldMessages.js +61 -11
  57. package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
  58. package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
  59. package/dist/components/form/engine/_structuredData/SchemaDataView.js +7 -1
  60. package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
  61. package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
  62. package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
  63. package/dist/components/form/engine/readFirst.d.ts +42 -1
  64. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  65. package/dist/components/form/engine/readFirst.js +74 -7
  66. package/dist/components/form/engine/readFirst.js.map +1 -1
  67. package/dist/components/form/engine/rendererTypes.d.ts +13 -0
  68. package/dist/components/form/engine/rendererTypes.d.ts.map +1 -1
  69. package/dist/components/form/engine/rendererTypes.js.map +1 -1
  70. package/dist/components/form/engine/rowMenuContext.d.ts +78 -0
  71. package/dist/components/form/engine/rowMenuContext.d.ts.map +1 -0
  72. package/dist/components/form/engine/rowMenuContext.js +103 -0
  73. package/dist/components/form/engine/rowMenuContext.js.map +1 -0
  74. package/dist/components/form/engine/typeChoices.d.ts +37 -0
  75. package/dist/components/form/engine/typeChoices.d.ts.map +1 -0
  76. package/dist/components/form/engine/typeChoices.js +25 -0
  77. package/dist/components/form/engine/typeChoices.js.map +1 -0
  78. package/dist/components/form/expressions/DpqlRendering.d.ts +26 -0
  79. package/dist/components/form/expressions/DpqlRendering.d.ts.map +1 -0
  80. package/dist/components/form/expressions/DpqlRendering.js +53 -0
  81. package/dist/components/form/expressions/DpqlRendering.js.map +1 -0
  82. package/dist/components/form/expressions/ExpressionField.d.ts +7 -1
  83. package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
  84. package/dist/components/form/expressions/ExpressionField.js +202 -45
  85. package/dist/components/form/expressions/ExpressionField.js.map +1 -1
  86. package/dist/components/form/expressions/argumentPresence.d.ts +97 -0
  87. package/dist/components/form/expressions/argumentPresence.d.ts.map +1 -0
  88. package/dist/components/form/expressions/argumentPresence.js +203 -0
  89. package/dist/components/form/expressions/argumentPresence.js.map +1 -0
  90. package/dist/components/form/expressions/builder/argumentLabel.d.ts.map +1 -1
  91. package/dist/components/form/expressions/builder/argumentLabel.js +9 -4
  92. package/dist/components/form/expressions/builder/argumentLabel.js.map +1 -1
  93. package/dist/components/form/expressions/builder/index.d.ts +15 -2
  94. package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
  95. package/dist/components/form/expressions/builder/index.js +20 -58
  96. package/dist/components/form/expressions/builder/index.js.map +1 -1
  97. package/dist/components/form/expressions/builder/renderTemplate.d.ts +4 -1
  98. package/dist/components/form/expressions/builder/renderTemplate.d.ts.map +1 -1
  99. package/dist/components/form/expressions/builder/renderTemplate.js +10 -67
  100. package/dist/components/form/expressions/builder/renderTemplate.js.map +1 -1
  101. package/dist/components/form/expressions/dpqlMockLanguage.d.ts +76 -0
  102. package/dist/components/form/expressions/dpqlMockLanguage.d.ts.map +1 -0
  103. package/dist/components/form/expressions/dpqlMockLanguage.js +525 -0
  104. package/dist/components/form/expressions/dpqlMockLanguage.js.map +1 -0
  105. package/dist/components/form/expressions/dpqlMockLsp.d.ts +31 -2
  106. package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +1 -1
  107. package/dist/components/form/expressions/dpqlMockLsp.js +86 -80
  108. package/dist/components/form/expressions/dpqlMockLsp.js.map +1 -1
  109. package/dist/components/form/expressions/mockExpressions.d.ts.map +1 -1
  110. package/dist/components/form/expressions/mockExpressions.js +8 -4
  111. package/dist/components/form/expressions/mockExpressions.js.map +1 -1
  112. package/dist/components/form/expressions/renderExpressionToText.d.ts +1 -1
  113. package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
  114. package/dist/components/form/expressions/renderExpressionToText.js +95 -19
  115. package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
  116. package/dist/components/form/expressions/types.d.ts +7 -0
  117. package/dist/components/form/expressions/types.d.ts.map +1 -1
  118. package/dist/components/form/expressions/useRenderExpression.d.ts +7 -16
  119. package/dist/components/form/expressions/useRenderExpression.d.ts.map +1 -1
  120. package/dist/components/form/expressions/useRenderExpression.js +50 -126
  121. package/dist/components/form/expressions/useRenderExpression.js.map +1 -1
  122. package/dist/components/form/fields/Field.d.ts.map +1 -1
  123. package/dist/components/form/fields/Field.js +3 -1
  124. package/dist/components/form/fields/Field.js.map +1 -1
  125. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +11 -1
  126. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
  127. package/dist/components/form/fields/allowed-values/AllowedValues.js +14 -2
  128. package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
  129. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  130. package/dist/components/form/fields/auto/AutoFormField.js +32 -8
  131. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  132. package/dist/components/form/fields/date/Date.d.ts +1 -1
  133. package/dist/components/form/fields/date/Date.d.ts.map +1 -1
  134. package/dist/components/form/fields/date/Date.js +16 -2
  135. package/dist/components/form/fields/date/Date.js.map +1 -1
  136. package/dist/components/form/fields/rich-text/RichText.d.ts +21 -1
  137. package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
  138. package/dist/components/form/fields/rich-text/RichText.js +140 -8
  139. package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
  140. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts +2 -1
  141. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts.map +1 -1
  142. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js +3 -2
  143. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js.map +1 -1
  144. package/dist/components/form/fields/template/TemplateField.d.ts +31 -1
  145. package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
  146. package/dist/components/form/fields/template/TemplateField.js +528 -50
  147. package/dist/components/form/fields/template/TemplateField.js.map +1 -1
  148. package/dist/components/form/index.d.ts +1 -0
  149. package/dist/components/form/index.d.ts.map +1 -1
  150. package/dist/components/form/index.js +1 -0
  151. package/dist/components/form/index.js.map +1 -1
  152. package/dist/components/smartEditor/SmartEditor.d.ts.map +1 -1
  153. package/dist/components/smartEditor/SmartEditor.js +29 -8
  154. package/dist/components/smartEditor/SmartEditor.js.map +1 -1
  155. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +8 -0
  156. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +1 -1
  157. package/dist/components/smartEditor/__fixtures__/mockLspServer.js +53 -10
  158. package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +1 -1
  159. package/dist/components/smartEditor/useLspSession.d.ts +5 -1
  160. package/dist/components/smartEditor/useLspSession.d.ts.map +1 -1
  161. package/dist/components/smartEditor/useLspSession.js +16 -2
  162. package/dist/components/smartEditor/useLspSession.js.map +1 -1
  163. package/dist/helpers/dpqlDetection.d.ts +58 -0
  164. package/dist/helpers/dpqlDetection.d.ts.map +1 -0
  165. package/dist/helpers/dpqlDetection.js +164 -0
  166. package/dist/helpers/dpqlDetection.js.map +1 -0
  167. package/dist/helpers/optionUiTypes.d.ts +3 -3
  168. package/dist/helpers/options.d.ts +37 -0
  169. package/dist/helpers/options.d.ts.map +1 -1
  170. package/dist/helpers/options.js +78 -1
  171. package/dist/helpers/options.js.map +1 -1
  172. package/dist/helpers/templateItems.d.ts +47 -0
  173. package/dist/helpers/templateItems.d.ts.map +1 -0
  174. package/dist/helpers/templateItems.js +88 -0
  175. package/dist/helpers/templateItems.js.map +1 -0
  176. package/dist/helpers/templateText.d.ts +37 -0
  177. package/dist/helpers/templateText.d.ts.map +1 -0
  178. package/dist/helpers/templateText.js +64 -0
  179. package/dist/helpers/templateText.js.map +1 -0
  180. package/dist/helpers/templates.d.ts +19 -11
  181. package/dist/helpers/templates.d.ts.map +1 -1
  182. package/dist/helpers/templates.js +46 -29
  183. package/dist/helpers/templates.js.map +1 -1
  184. package/dist/helpers/validations.d.ts.map +1 -1
  185. package/dist/helpers/validations.js +56 -9
  186. package/dist/helpers/validations.js.map +1 -1
  187. package/dist/hooks/useArgSchema.d.ts +2 -1
  188. package/dist/hooks/useArgSchema.d.ts.map +1 -1
  189. package/dist/hooks/useFetch/useFetch.d.ts +3 -1
  190. package/dist/hooks/useFetch/useFetch.d.ts.map +1 -1
  191. package/dist/hooks/useFetch/useFetch.js +47 -9
  192. package/dist/hooks/useFetch/useFetch.js.map +1 -1
  193. package/dist/hooks/useTemplates.d.ts +20 -0
  194. package/dist/hooks/useTemplates.d.ts.map +1 -1
  195. package/dist/hooks/useTemplates.js +34 -2
  196. package/dist/hooks/useTemplates.js.map +1 -1
  197. package/dist/index.d.ts +2 -1
  198. package/dist/index.d.ts.map +1 -1
  199. package/dist/index.js +5 -1
  200. package/dist/index.js.map +1 -1
  201. package/dist/stories/storyNetwork.d.ts +36 -0
  202. package/dist/stories/storyNetwork.d.ts.map +1 -0
  203. package/dist/stories/storyNetwork.js +82 -0
  204. package/dist/stories/storyNetwork.js.map +1 -0
  205. package/dist/utils/fetch.d.ts +151 -1
  206. package/dist/utils/fetch.d.ts.map +1 -1
  207. package/dist/utils/fetch.js +256 -44
  208. package/dist/utils/fetch.js.map +1 -1
  209. package/dist/utils/lspClient.d.ts +6 -0
  210. package/dist/utils/lspClient.d.ts.map +1 -1
  211. package/dist/utils/lspClient.js +62 -11
  212. package/dist/utils/lspClient.js.map +1 -1
  213. package/dist/utils/websocket.d.ts +6 -0
  214. package/dist/utils/websocket.d.ts.map +1 -1
  215. package/dist/utils/websocket.js +15 -7
  216. package/dist/utils/websocket.js.map +1 -1
  217. package/package.json +2 -2
  218. package/src/components/dpqlEditor/DpqlEditor.stories.tsx +3 -119
  219. package/src/components/dpqlEditor/DpqlEditor.tsx +5 -2
  220. package/src/components/dpqlEditor/dpqlHelpers.ts +20 -1
  221. package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
  222. package/src/components/dpqlEditor/dpqlTags.ts +13 -0
  223. package/src/components/dpqlEditor/index.ts +4 -0
  224. package/src/components/dpqlEditor/types.ts +38 -2
  225. package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
  226. package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
  227. package/src/components/form/engine/CompactRow.tsx +240 -51
  228. package/src/components/form/engine/CompactToolbar.tsx +10 -3
  229. package/src/components/form/engine/FormEngine.stories.tsx +423 -120
  230. package/src/components/form/engine/FormEngine.tsx +504 -87
  231. package/src/components/form/engine/FormFieldsSkeleton.tsx +76 -0
  232. package/src/components/form/engine/OptionFieldMessages.tsx +89 -11
  233. package/src/components/form/engine/_structuredData/SchemaDataView.tsx +11 -0
  234. package/src/components/form/engine/compactToolbarContext.ts +1 -1
  235. package/src/components/form/engine/readFirst.ts +86 -7
  236. package/src/components/form/engine/rendererTypes.ts +12 -0
  237. package/src/components/form/engine/rowMenuContext.ts +159 -0
  238. package/src/components/form/engine/typeChoices.ts +55 -0
  239. package/src/components/form/expressions/DpqlRendering.tsx +85 -0
  240. package/src/components/form/expressions/ExpressionField.stories.tsx +826 -48
  241. package/src/components/form/expressions/ExpressionField.tsx +265 -54
  242. package/src/components/form/expressions/argumentPresence.ts +200 -0
  243. package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +20 -4
  244. package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
  245. package/src/components/form/expressions/builder/index.tsx +39 -77
  246. package/src/components/form/expressions/builder/renderTemplate.tsx +23 -56
  247. package/src/components/form/expressions/dpqlMockLanguage.ts +565 -0
  248. package/src/components/form/expressions/dpqlMockLsp.ts +109 -83
  249. package/src/components/form/expressions/mockExpressions.ts +8 -4
  250. package/src/components/form/expressions/renderExpressionToText.ts +112 -22
  251. package/src/components/form/expressions/types.ts +7 -0
  252. package/src/components/form/expressions/useRenderExpression.ts +43 -127
  253. package/src/components/form/fields/Field.tsx +3 -2
  254. package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -2
  255. package/src/components/form/fields/auto/AutoFormField.stories.tsx +27 -20
  256. package/src/components/form/fields/auto/AutoFormField.tsx +33 -9
  257. package/src/components/form/fields/date/Date.tsx +17 -1
  258. package/src/components/form/fields/rich-text/RichText.tsx +199 -22
  259. package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
  260. package/src/components/form/fields/template/TemplateField.stories.tsx +31 -9
  261. package/src/components/form/fields/template/TemplateField.tsx +714 -122
  262. package/src/components/form/index.tsx +1 -0
  263. package/src/components/log/Log.stories.tsx +2 -1
  264. package/src/components/smartEditor/SmartEditor.tsx +41 -9
  265. package/src/components/smartEditor/__fixtures__/mockLspServer.ts +52 -3
  266. package/src/components/smartEditor/useLspSession.ts +21 -3
  267. package/src/helpers/dpqlDetection.ts +209 -0
  268. package/src/helpers/options.ts +70 -0
  269. package/src/helpers/templateItems.ts +81 -0
  270. package/src/helpers/templateText.ts +92 -0
  271. package/src/helpers/templates.ts +47 -37
  272. package/src/helpers/validations.ts +59 -9
  273. package/src/hooks/useFetch/useFetch.stories.tsx +6 -5
  274. package/src/hooks/useFetch/useFetch.tsx +53 -7
  275. package/src/hooks/useStorage/useStorage.stories.tsx +2 -1
  276. package/src/hooks/useTemplates.ts +41 -0
  277. package/src/hooks/useWebSocket/useWebsocket.stories.tsx +2 -1
  278. package/src/index.tsx +14 -1
  279. package/src/stores/currentUser/currentUser.stories.tsx +2 -1
  280. package/src/stories/storyNetwork.ts +92 -0
  281. package/src/utils/fetch.ts +311 -21
  282. package/src/utils/lspClient.ts +68 -11
  283. package/src/utils/websocket.ts +20 -9
@@ -4,10 +4,27 @@ import {
4
4
  } from '@qoretechnologies/reqore/dist/components/RichTextEditor';
5
5
  import { IReqoreFormTemplates } from '@qoretechnologies/reqore/dist/components/Textarea';
6
6
  import { IReqoreTagProps } from '@qoretechnologies/reqore/dist/components/Tag';
7
+ import { IReqoreTooltip } from '@qoretechnologies/reqore/dist/types/global';
7
8
  import { isEqual, size } from 'lodash';
8
- import { memo, useCallback, useEffect, useMemo, useState } from 'react';
9
+ import { KeyboardEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
9
10
  import { useDebounce } from 'react-use';
10
- import { getTemplateTagStyle } from '../../../../helpers/templates';
11
+ import { flattenToSingleLine, hasLineBreak } from '../../../../helpers/singleLineString';
12
+ import {
13
+ renderTemplateItemDescriptions,
14
+ templateItemsToShow,
15
+ } from '../../../../helpers/templateItems';
16
+ import {
17
+ templateChipLabel,
18
+ templateNodesToText,
19
+ templateTextToNodes,
20
+ } from '../../../../helpers/templateText';
21
+ import {
22
+ describeTemplateReference,
23
+ getTemplateTagStyle,
24
+ templateTooltip,
25
+ TTemplateMeta,
26
+ } from '../../../../helpers/templates';
27
+ import { useMarkdownRenderer } from '../../../Description/markdownRendererContext';
11
28
 
12
29
  export interface IRichTextFormFieldProps extends Omit<
13
30
  IReqoreRichTextEditorProps,
@@ -17,18 +34,85 @@ export interface IRichTextFormFieldProps extends Omit<
17
34
  onChange?: (value: string | IReqoreRichTextEditorProps['value']) => void;
18
35
  allowTemplates?: boolean;
19
36
  templates?: IReqoreFormTemplates;
37
+ /**
38
+ * What the value is. `richtext` (the default) stores the editor's document.
39
+ * `text` stores a plain string: each template reference in it is drawn as a
40
+ * chip named from `templates`, and what is emitted is the string again — for
41
+ * a string field whose value may hold references (`Dear $local:name`).
42
+ */
43
+ valueFormat?: 'richtext' | 'text';
44
+ /** With `valueFormat: 'text'`: the value holds one line, so Enter adds none. */
45
+ singleLine?: boolean;
20
46
  }
21
47
 
48
+ /**
49
+ * What each offered template value is FOR, keyed by the value itself.
50
+ *
51
+ * Built from the templates a field was already given rather than from the
52
+ * chip's metadata, which carries only `image` / `displayName` / style flags —
53
+ * putting a description there would mean threading one through every producer
54
+ * of a template list.
55
+ *
56
+ * Exported so the mapping is assertable without rendering a Slate editor.
57
+ */
58
+ export const getChipTooltipDescriptions = (
59
+ templates?: IReqoreFormTemplates
60
+ ): Record<string, string> => {
61
+ const out: Record<string, string> = {};
62
+
63
+ /* BOTH shapes, because this list arrives in either one. A picker's items are
64
+ normally grouped by category, but a LONE category is opened onto its values
65
+ where the templates are resolved (`templateItemsToShow`), so a field with
66
+ one category hands down a FLAT list of leaves. Walking only groups made
67
+ that the common case and silently lost every tooltip on it — one category
68
+ is the ordinary shape for a field, not the exception.
69
+
70
+ Recursive rather than two-level: a leaf is recognised by carrying a value
71
+ rather than by its depth, so a deeper catalogue works too. */
72
+ const collect = (items: any[] | undefined): void => {
73
+ items?.forEach((item: any) => {
74
+ const value = typeof item?.value === 'object' ? item?.value?.value : item?.value;
75
+ const description = item?.description ?? item?.short_desc;
76
+ if (typeof value === 'string' && typeof description === 'string' && description) {
77
+ out[value] = description;
78
+ }
79
+ collect(item?.items);
80
+ });
81
+ };
82
+
83
+ collect((templates as any)?.items);
84
+
85
+ return out;
86
+ };
87
+
22
88
  export const RichTextFormField = memo(({
23
89
  value,
24
90
  onChange,
25
91
  allowTemplates,
26
92
  templates,
93
+ valueFormat = 'richtext',
94
+ singleLine,
27
95
  ...rest
28
96
  }: IRichTextFormFieldProps) => {
29
- const [localValue, setLocalValue] = useState<any>(value);
97
+ const isText = valueFormat === 'text';
98
+ const [localValue, setLocalValue] = useState<any>(() =>
99
+ isText ? templateTextToNodes(String(value ?? ''), templates) : value
100
+ );
101
+ /* In text mode, the string the editor's document stands for. The value that
102
+ comes back from the parent is usually that same string, and rebuilding the
103
+ document from it would hand the editor a new tree on every keystroke —
104
+ which it answers by replacing its content and moving the cursor to the end. */
105
+ const lastTextRef = useRef(isText ? String(value ?? '') : '');
30
106
 
31
107
  useEffect(() => {
108
+ if (isText) {
109
+ const text = String(value ?? '');
110
+ if (text !== lastTextRef.current) {
111
+ lastTextRef.current = text;
112
+ setLocalValue(templateTextToNodes(text, templates));
113
+ }
114
+ return;
115
+ }
32
116
  if (JSON.stringify(value) !== JSON.stringify(localValue)) {
33
117
  setLocalValue(value);
34
118
  }
@@ -36,6 +120,12 @@ export const RichTextFormField = memo(({
36
120
 
37
121
  useDebounce(
38
122
  () => {
123
+ if (isText) {
124
+ if (lastTextRef.current !== String(value ?? '')) {
125
+ onChange?.(lastTextRef.current);
126
+ }
127
+ return;
128
+ }
39
129
  if (!isEqual(localValue, value)) {
40
130
  onChange?.(localValue);
41
131
  }
@@ -50,6 +140,19 @@ export const RichTextFormField = memo(({
50
140
  );
51
141
 
52
142
  const handleChange = (val: any): void => {
143
+ if (isText) {
144
+ const text = templateNodesToText(val);
145
+ // A line break also arrives by paste and by drag-and-drop; Enter alone is
146
+ // stopped below. Flattened text is shown as the one line it now is.
147
+ if (singleLine && hasLineBreak(text)) {
148
+ lastTextRef.current = flattenToSingleLine(text);
149
+ setLocalValue(templateTextToNodes(lastTextRef.current, templates));
150
+ return;
151
+ }
152
+ lastTextRef.current = text;
153
+ setLocalValue(val);
154
+ return;
155
+ }
53
156
  if (JSON.stringify(val) === '[{"type":"paragraph","children":[{"text":""}]}]') {
54
157
  setLocalValue(undefined);
55
158
  return;
@@ -57,26 +160,91 @@ export const RichTextFormField = memo(({
57
160
  setLocalValue(val);
58
161
  };
59
162
 
60
- const handleGetTagProps = useCallback((tag: any): IReqoreTagProps => {
61
- const tagValue = tag.value?.toString();
163
+ // One renderer for both surfaces below — the chip's hover and the picker rows
164
+ // show the same strings, so they must be drawn the same way.
165
+ const renderMarkdown = useMarkdownRenderer();
62
166
 
63
- if (!tagValue) {
64
- return {};
65
- }
167
+ const descriptionByValue = useMemo(() => getChipTooltipDescriptions(templates), [
168
+ JSON.stringify(templates),
169
+ ]);
66
170
 
67
- if (tagValue.startsWith('$')) {
68
- return {
69
- icon: 'ExchangeDollarLine',
70
- leftIconProps: {
71
- image: tag.metadata?.image,
72
- },
73
- labelKey: tag.metadata?.displayName,
74
- ...getTemplateTagStyle(tag.metadata),
75
- };
76
- }
171
+ const handleGetTagProps = useCallback(
172
+ (tag: any): IReqoreTagProps => {
173
+ const tagValue = tag.value?.toString();
174
+
175
+ if (!tagValue) {
176
+ return {};
177
+ }
178
+
179
+ /* A reference in a string is drawn exactly as the collapsed row draws the
180
+ same value (`ReadOnlyTemplateTag`): named from the catalogue, its
181
+ description on hover. Read at render, so a catalogue that arrives
182
+ after the value still names it. */
183
+ if (isText && tagValue.startsWith('$')) {
184
+ const { item } = describeTemplateReference(templates, tagValue);
185
+ const metadata = item?.metadata as TTemplateMeta | undefined;
186
+ return {
187
+ icon: 'ExchangeDollarLine',
188
+ leftIconProps: { image: metadata?.image },
189
+ label: templateChipLabel(templates, tagValue),
190
+ tooltip: templateTooltip(templates, tagValue),
191
+ ...getTemplateTagStyle(metadata),
192
+ };
193
+ }
77
194
 
78
- return {};
79
- }, []);
195
+ if (tagValue.startsWith('$')) {
196
+ return {
197
+ icon: 'ExchangeDollarLine',
198
+ leftIconProps: {
199
+ image: tag.metadata?.image,
200
+ },
201
+ labelKey: tag.metadata?.displayName,
202
+ /* Hovering a chip says what the value IS, not how it is spelled.
203
+ Reqore otherwise falls back to the tag's own value, so a chip
204
+ reading "result" reported `$.result` on hover — the reference the
205
+ author already chose, restating the label in engine syntax instead
206
+ of telling them anything. The path stays discoverable in the
207
+ field's help and in the picker row for an unlabelled value. */
208
+ /* Drawn, for the same reason the picker row is: this description is
209
+ the SAME string the row shows, so leaving it raw here would print
210
+ the punctuation the row no longer prints. A tooltip's `content`
211
+ takes a node, and without a host renderer it stays the plain
212
+ string it always was. */
213
+ /* Drawn, for the same reason the picker row is: this is the SAME
214
+ string the row shows, so leaving it raw here would print the
215
+ punctuation the row no longer prints. A drawn description travels
216
+ as the tooltip's `content` — the prop itself is `string |
217
+ IReqoreTooltip`, and only the object form takes a node. Without a
218
+ host renderer it stays the plain string it always was. */
219
+ tooltip:
220
+ descriptionByValue[tagValue] ?
221
+ renderMarkdown ?
222
+ {
223
+ content: renderMarkdown({
224
+ value: descriptionByValue[tagValue],
225
+ compact: true,
226
+ }) as IReqoreTooltip['content'],
227
+ }
228
+ : descriptionByValue[tagValue]
229
+ : undefined,
230
+ ...getTemplateTagStyle(tag.metadata),
231
+ };
232
+ }
233
+
234
+ return {};
235
+ },
236
+ [descriptionByValue, renderMarkdown, isText, templates]
237
+ );
238
+
239
+ const handleKeyDown = useCallback(
240
+ (event: KeyboardEvent<HTMLTextAreaElement>): void => {
241
+ if (isText && singleLine && event.key === 'Enter') {
242
+ event.preventDefault();
243
+ }
244
+ (rest as { onKeyDown?: (event: KeyboardEvent<HTMLTextAreaElement>) => void }).onKeyDown?.(event);
245
+ },
246
+ [isText, singleLine, (rest as { onKeyDown?: unknown }).onKeyDown]
247
+ );
80
248
 
81
249
  const tags = useMemo<IReqoreRichTextEditorProps['tags']>((): IReqoreRichTextEditorProps['tags'] => {
82
250
  const _tags: IReqoreRichTextEditorProps['tags'] = {};
@@ -87,12 +255,20 @@ export const RichTextFormField = memo(({
87
255
  label: 'Templates',
88
256
  flat: false,
89
257
  description: 'Universal values that can be used in multiple places',
90
- items: templates?.items,
258
+ /* The in-editor list already nests everything under "Templates", so a
259
+ lone category makes the author drill through TWO headers to reach
260
+ the only values on offer. Hoist it. */
261
+ /* Drawn here, at the hand-off to Reqore. A description is prose written
262
+ in the same markdown as every other description this package shows,
263
+ and printing its punctuation at the reader was the last surface still
264
+ doing that. Never made further up: the list is a `JSON.stringify`
265
+ memo key in `TemplateField`. */
266
+ items: renderTemplateItemDescriptions(templateItemsToShow(templates?.items), renderMarkdown),
91
267
  };
92
268
  }
93
269
 
94
270
  return _tags;
95
- }, [allowTemplates, templates]);
271
+ }, [allowTemplates, templates, renderMarkdown]);
96
272
 
97
273
  const formattedValue: IReqoreRichTextEditorProps['value'] =
98
274
  typeof localValue !== 'object' ? [{ type: 'paragraph', children: [{ text: String(localValue ?? '') }] }]
@@ -128,6 +304,7 @@ export const RichTextFormField = memo(({
128
304
  panelProps={{ fluid: true, style: { minWidth: '150px', ...rest.panelProps?.style } }}
129
305
  {...rest}
130
306
  readOnly={readOnly}
307
+ onKeyDown={handleKeyDown}
131
308
  actions={
132
309
  readOnly ?
133
310
  { undo: false, redo: false, styling: false }
@@ -5,6 +5,7 @@ import { memo } from 'react';
5
5
  import {
6
6
  getTemplateTagStyle,
7
7
  describeTemplateReference,
8
+ templateTooltip,
8
9
  TTemplateMeta,
9
10
  } from '../../../../helpers/templates';
10
11
 
@@ -18,7 +19,8 @@ export interface IReadOnlyTemplateTagProps {
18
19
 
19
20
  /**
20
21
  * The read-only representation of a selected template — a picker-shaped chip:
21
- * the `$`-dollar icon, the resolved display name (raw value in the tooltip), the
22
+ * the `$`-dollar icon, the resolved display name (and what the value IS in the
23
+ * tooltip, not the reference that names it), the
22
24
  * app image when present, coloured by qorus-ide's scheme (info / qorus purple /
23
25
  * success — see `getTemplateTagStyle`). Shared by `TemplateField`'s disabled
24
26
  * branch and the compact read-first row so a template reads identically wherever
@@ -34,7 +36,7 @@ export const ReadOnlyTemplateTag = memo(
34
36
  icon='ExchangeDollarLine'
35
37
  leftIconProps={{ image: metadata?.image }}
36
38
  label={label}
37
- tooltip={value}
39
+ tooltip={templateTooltip(templates, value)}
38
40
  {...getTemplateTagStyle(metadata)}
39
41
  />
40
42
  </ReqoreTagGroup>
@@ -264,10 +264,20 @@ export const AutoComponent: StoryObj<typeof meta> = {
264
264
  docs: {
265
265
  description: {
266
266
  story:
267
- 'Renders TemplateField over an untyped (`auto`) field that accepts templates. While the field is empty it opens on the template selector rather than the data-type picker, so the author can say which value they mean before answering how it is stored.',
267
+ 'Renders TemplateField over an untyped (`auto`) field that accepts templates. While the field is empty it opens on an editor to type into, which offers the templates — never on a data-type picker, so the author can write the value they mean without answering how it is stored.',
268
268
  },
269
269
  },
270
270
  },
271
+ play: async ({ canvasElement }) => {
272
+ /* The editor itself, not a class: TemplateField puts `.template-selector`
273
+ on every control it draws, the data-type picker included, so asserting
274
+ the class passed while the story showed "Please select data type". */
275
+ await waitFor(() => expect(canvasElement.querySelector('[contenteditable="true"]')).toBeTruthy(), {
276
+ timeout: 5000,
277
+ });
278
+ expect(canvasElement.textContent).not.toContain('Please select data type');
279
+ expect(canvasElement.textContent).not.toContain('Select Template');
280
+ },
271
281
  };
272
282
 
273
283
  export const AllowedValuesWithTemplate: StoryObj<typeof meta> = {
@@ -674,7 +684,7 @@ export const TemplateCanBeSelected: StoryObj<typeof meta> = {
674
684
  docs: {
675
685
  description: {
676
686
  story:
677
- 'Renders TemplateField for a string, opens the templates popover and clicks the Interface ID template — the field switches to the $local:id template value inside the template-offering input (plain word tokens stay typeable; only braced context refs chip).',
687
+ 'Renders TemplateField for a string, opens the templates popover and clicks the Interface ID template — the field switches to template mode, where the $local:id reference is a chip named Interface ID inside an editor that can still be typed into.',
678
688
  },
679
689
  },
680
690
  },
@@ -684,9 +694,16 @@ export const TemplateCanBeSelected: StoryObj<typeof meta> = {
684
694
  await ShowsTemplatesListForString.play({ canvasElement, ...rest });
685
695
  await _testsClickButton({ label: 'Interface ID' });
686
696
 
687
- await sleep(100);
688
-
689
- await expect(canvas.getByDisplayValue('$local:id')).toBeInTheDocument();
697
+ // The chosen reference reads as the name it was chosen by, in an editor
698
+ // that can still be typed into — not as its spelling.
699
+ await waitFor(
700
+ () => {
701
+ const chip = canvasElement.querySelector('.template-selector [contenteditable="true"] .reqore-tag');
702
+ expect(chip?.textContent).toContain('Interface ID');
703
+ },
704
+ { timeout: 5000 }
705
+ );
706
+ await expect(canvas.queryByDisplayValue('$local:id')).toBeNull();
690
707
  },
691
708
  };
692
709
 
@@ -991,14 +1008,19 @@ export const EmptyAnyOpensOnTemplates: StoryObj<typeof meta> = {
991
1008
  docs: {
992
1009
  description: {
993
1010
  story:
994
- 'An untyped, empty field that accepts templates opens showing the template selector rather than a data-type picker.',
1011
+ 'An untyped, empty field that accepts templates opens on an editor to type into, which offers the templates — never on a data-type picker.',
995
1012
  },
996
1013
  },
997
1014
  },
998
1015
  play: async ({ canvasElement }) => {
999
- await waitFor(() =>
1000
- expect(canvasElement.querySelector('.template-selector')).toBeTruthy()
1001
- );
1016
+ /* The editor itself, not a class: TemplateField puts `.template-selector`
1017
+ on every control it draws, the data-type picker included, so asserting
1018
+ the class passed while the story showed "Please select data type". */
1019
+ await waitFor(() => expect(canvasElement.querySelector('[contenteditable="true"]')).toBeTruthy(), {
1020
+ timeout: 5000,
1021
+ });
1022
+ expect(canvasElement.textContent).not.toContain('Please select data type');
1023
+ expect(canvasElement.textContent).not.toContain('Select Template');
1002
1024
  },
1003
1025
  };
1004
1026