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

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 (215) 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/RENDER_EXPRESSION_TRANSPORT.md +18 -0
  7. package/dist/components/dpqlEditor/dpqlProbe.d.ts +37 -0
  8. package/dist/components/dpqlEditor/dpqlProbe.d.ts.map +1 -0
  9. package/dist/components/dpqlEditor/dpqlProbe.js +203 -0
  10. package/dist/components/dpqlEditor/dpqlProbe.js.map +1 -0
  11. package/dist/components/dpqlEditor/index.d.ts +4 -0
  12. package/dist/components/dpqlEditor/index.d.ts.map +1 -1
  13. package/dist/components/dpqlEditor/index.js +5 -1
  14. package/dist/components/dpqlEditor/index.js.map +1 -1
  15. package/dist/components/dpqlEditor/types.d.ts +29 -2
  16. package/dist/components/dpqlEditor/types.d.ts.map +1 -1
  17. package/dist/components/dpqlEditor/useDpqlProbe.d.ts +4 -0
  18. package/dist/components/dpqlEditor/useDpqlProbe.d.ts.map +1 -0
  19. package/dist/components/dpqlEditor/useDpqlProbe.js +29 -0
  20. package/dist/components/dpqlEditor/useDpqlProbe.js.map +1 -0
  21. package/dist/components/dpqlEditor/useDpqlSession.d.ts +5 -2
  22. package/dist/components/dpqlEditor/useDpqlSession.d.ts.map +1 -1
  23. package/dist/components/dpqlEditor/useDpqlSession.js +8 -2
  24. package/dist/components/dpqlEditor/useDpqlSession.js.map +1 -1
  25. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  26. package/dist/components/form/engine/CompactRow.js +186 -21
  27. package/dist/components/form/engine/CompactRow.js.map +1 -1
  28. package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
  29. package/dist/components/form/engine/CompactToolbar.js +5 -2
  30. package/dist/components/form/engine/CompactToolbar.js.map +1 -1
  31. package/dist/components/form/engine/FormEngine.d.ts +159 -11
  32. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  33. package/dist/components/form/engine/FormEngine.js +376 -75
  34. package/dist/components/form/engine/FormEngine.js.map +1 -1
  35. package/dist/components/form/engine/FormFieldsSkeleton.d.ts +48 -0
  36. package/dist/components/form/engine/FormFieldsSkeleton.d.ts.map +1 -0
  37. package/dist/components/form/engine/FormFieldsSkeleton.js +38 -0
  38. package/dist/components/form/engine/FormFieldsSkeleton.js.map +1 -0
  39. package/dist/components/form/engine/OptionFieldMessages.d.ts +17 -2
  40. package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
  41. package/dist/components/form/engine/OptionFieldMessages.js +61 -11
  42. package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
  43. package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
  44. package/dist/components/form/engine/_structuredData/SchemaDataView.js +7 -1
  45. package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
  46. package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
  47. package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
  48. package/dist/components/form/engine/readFirst.d.ts +42 -1
  49. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  50. package/dist/components/form/engine/readFirst.js +74 -7
  51. package/dist/components/form/engine/readFirst.js.map +1 -1
  52. package/dist/components/form/engine/rendererTypes.d.ts +13 -0
  53. package/dist/components/form/engine/rendererTypes.d.ts.map +1 -1
  54. package/dist/components/form/engine/rendererTypes.js.map +1 -1
  55. package/dist/components/form/engine/rowMenuContext.d.ts +78 -0
  56. package/dist/components/form/engine/rowMenuContext.d.ts.map +1 -0
  57. package/dist/components/form/engine/rowMenuContext.js +103 -0
  58. package/dist/components/form/engine/rowMenuContext.js.map +1 -0
  59. package/dist/components/form/engine/typeChoices.d.ts +37 -0
  60. package/dist/components/form/engine/typeChoices.d.ts.map +1 -0
  61. package/dist/components/form/engine/typeChoices.js +25 -0
  62. package/dist/components/form/engine/typeChoices.js.map +1 -0
  63. package/dist/components/form/expressions/ExpressionField.d.ts +7 -1
  64. package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
  65. package/dist/components/form/expressions/ExpressionField.js +181 -33
  66. package/dist/components/form/expressions/ExpressionField.js.map +1 -1
  67. package/dist/components/form/expressions/argumentPresence.d.ts +97 -0
  68. package/dist/components/form/expressions/argumentPresence.d.ts.map +1 -0
  69. package/dist/components/form/expressions/argumentPresence.js +203 -0
  70. package/dist/components/form/expressions/argumentPresence.js.map +1 -0
  71. package/dist/components/form/expressions/builder/argumentLabel.d.ts.map +1 -1
  72. package/dist/components/form/expressions/builder/argumentLabel.js +9 -4
  73. package/dist/components/form/expressions/builder/argumentLabel.js.map +1 -1
  74. package/dist/components/form/expressions/builder/index.d.ts +15 -2
  75. package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
  76. package/dist/components/form/expressions/builder/index.js +19 -57
  77. package/dist/components/form/expressions/builder/index.js.map +1 -1
  78. package/dist/components/form/expressions/dpqlMockLsp.d.ts +17 -0
  79. package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +1 -1
  80. package/dist/components/form/expressions/dpqlMockLsp.js +75 -9
  81. package/dist/components/form/expressions/dpqlMockLsp.js.map +1 -1
  82. package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
  83. package/dist/components/form/expressions/renderExpressionToText.js +94 -19
  84. package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
  85. package/dist/components/form/fields/Field.d.ts.map +1 -1
  86. package/dist/components/form/fields/Field.js +3 -1
  87. package/dist/components/form/fields/Field.js.map +1 -1
  88. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +11 -1
  89. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
  90. package/dist/components/form/fields/allowed-values/AllowedValues.js +14 -2
  91. package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
  92. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  93. package/dist/components/form/fields/auto/AutoFormField.js +13 -2
  94. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  95. package/dist/components/form/fields/rich-text/RichText.d.ts +11 -0
  96. package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
  97. package/dist/components/form/fields/rich-text/RichText.js +84 -5
  98. package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
  99. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts +2 -1
  100. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts.map +1 -1
  101. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js +3 -2
  102. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js.map +1 -1
  103. package/dist/components/form/fields/template/TemplateField.d.ts +31 -1
  104. package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
  105. package/dist/components/form/fields/template/TemplateField.js +507 -40
  106. package/dist/components/form/fields/template/TemplateField.js.map +1 -1
  107. package/dist/components/form/index.d.ts +1 -0
  108. package/dist/components/form/index.d.ts.map +1 -1
  109. package/dist/components/form/index.js +1 -0
  110. package/dist/components/form/index.js.map +1 -1
  111. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +1 -0
  112. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +1 -1
  113. package/dist/components/smartEditor/__fixtures__/mockLspServer.js +6 -1
  114. package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +1 -1
  115. package/dist/helpers/dpqlDetection.d.ts +58 -0
  116. package/dist/helpers/dpqlDetection.d.ts.map +1 -0
  117. package/dist/helpers/dpqlDetection.js +164 -0
  118. package/dist/helpers/dpqlDetection.js.map +1 -0
  119. package/dist/helpers/optionUiTypes.d.ts +1 -1
  120. package/dist/helpers/options.d.ts +37 -0
  121. package/dist/helpers/options.d.ts.map +1 -1
  122. package/dist/helpers/options.js +78 -1
  123. package/dist/helpers/options.js.map +1 -1
  124. package/dist/helpers/templateItems.d.ts +47 -0
  125. package/dist/helpers/templateItems.d.ts.map +1 -0
  126. package/dist/helpers/templateItems.js +88 -0
  127. package/dist/helpers/templateItems.js.map +1 -0
  128. package/dist/helpers/templates.d.ts +21 -0
  129. package/dist/helpers/templates.d.ts.map +1 -1
  130. package/dist/helpers/templates.js +49 -3
  131. package/dist/helpers/templates.js.map +1 -1
  132. package/dist/helpers/validations.d.ts.map +1 -1
  133. package/dist/helpers/validations.js +56 -9
  134. package/dist/helpers/validations.js.map +1 -1
  135. package/dist/hooks/useArgSchema.d.ts +2 -1
  136. package/dist/hooks/useArgSchema.d.ts.map +1 -1
  137. package/dist/hooks/useFetch/useFetch.d.ts +3 -1
  138. package/dist/hooks/useFetch/useFetch.d.ts.map +1 -1
  139. package/dist/hooks/useFetch/useFetch.js +47 -9
  140. package/dist/hooks/useFetch/useFetch.js.map +1 -1
  141. package/dist/hooks/useTemplates.d.ts +20 -0
  142. package/dist/hooks/useTemplates.d.ts.map +1 -1
  143. package/dist/hooks/useTemplates.js +34 -2
  144. package/dist/hooks/useTemplates.js.map +1 -1
  145. package/dist/index.d.ts +2 -1
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +5 -1
  148. package/dist/index.js.map +1 -1
  149. package/dist/stories/storyNetwork.d.ts +36 -0
  150. package/dist/stories/storyNetwork.d.ts.map +1 -0
  151. package/dist/stories/storyNetwork.js +82 -0
  152. package/dist/stories/storyNetwork.js.map +1 -0
  153. package/dist/utils/fetch.d.ts +151 -1
  154. package/dist/utils/fetch.d.ts.map +1 -1
  155. package/dist/utils/fetch.js +256 -44
  156. package/dist/utils/fetch.js.map +1 -1
  157. package/dist/utils/lspClient.d.ts +6 -0
  158. package/dist/utils/lspClient.d.ts.map +1 -1
  159. package/dist/utils/lspClient.js +62 -11
  160. package/dist/utils/lspClient.js.map +1 -1
  161. package/dist/utils/websocket.d.ts +6 -0
  162. package/dist/utils/websocket.d.ts.map +1 -1
  163. package/dist/utils/websocket.js +15 -7
  164. package/dist/utils/websocket.js.map +1 -1
  165. package/package.json +2 -2
  166. package/src/components/dpqlEditor/DpqlEditor.stories.tsx +0 -2
  167. package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
  168. package/src/components/dpqlEditor/index.ts +4 -0
  169. package/src/components/dpqlEditor/types.ts +26 -2
  170. package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
  171. package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
  172. package/src/components/form/engine/CompactRow.tsx +231 -11
  173. package/src/components/form/engine/CompactToolbar.tsx +10 -3
  174. package/src/components/form/engine/FormEngine.stories.tsx +331 -30
  175. package/src/components/form/engine/FormEngine.tsx +504 -87
  176. package/src/components/form/engine/FormFieldsSkeleton.tsx +76 -0
  177. package/src/components/form/engine/OptionFieldMessages.tsx +89 -11
  178. package/src/components/form/engine/_structuredData/SchemaDataView.tsx +11 -0
  179. package/src/components/form/engine/compactToolbarContext.ts +1 -1
  180. package/src/components/form/engine/readFirst.ts +86 -7
  181. package/src/components/form/engine/rendererTypes.ts +12 -0
  182. package/src/components/form/engine/rowMenuContext.ts +159 -0
  183. package/src/components/form/engine/typeChoices.ts +55 -0
  184. package/src/components/form/expressions/ExpressionField.stories.tsx +828 -31
  185. package/src/components/form/expressions/ExpressionField.tsx +234 -29
  186. package/src/components/form/expressions/argumentPresence.ts +200 -0
  187. package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
  188. package/src/components/form/expressions/builder/index.tsx +31 -75
  189. package/src/components/form/expressions/dpqlMockLsp.ts +81 -1
  190. package/src/components/form/expressions/renderExpressionToText.ts +106 -17
  191. package/src/components/form/fields/Field.tsx +3 -2
  192. package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -2
  193. package/src/components/form/fields/auto/AutoFormField.tsx +12 -2
  194. package/src/components/form/fields/rich-text/RichText.tsx +112 -19
  195. package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
  196. package/src/components/form/fields/template/TemplateField.tsx +667 -95
  197. package/src/components/form/index.tsx +1 -0
  198. package/src/components/log/Log.stories.tsx +2 -1
  199. package/src/components/smartEditor/__fixtures__/mockLspServer.ts +6 -1
  200. package/src/helpers/dpqlDetection.ts +209 -0
  201. package/src/helpers/options.ts +70 -0
  202. package/src/helpers/templateItems.ts +81 -0
  203. package/src/helpers/templates.ts +51 -2
  204. package/src/helpers/validations.ts +59 -9
  205. package/src/hooks/useFetch/useFetch.stories.tsx +6 -5
  206. package/src/hooks/useFetch/useFetch.tsx +53 -7
  207. package/src/hooks/useStorage/useStorage.stories.tsx +2 -1
  208. package/src/hooks/useTemplates.ts +41 -0
  209. package/src/hooks/useWebSocket/useWebsocket.stories.tsx +2 -1
  210. package/src/index.tsx +14 -1
  211. package/src/stores/currentUser/currentUser.stories.tsx +2 -1
  212. package/src/stories/storyNetwork.ts +92 -0
  213. package/src/utils/fetch.ts +311 -21
  214. package/src/utils/lspClient.ts +68 -11
  215. package/src/utils/websocket.ts +20 -9
@@ -0,0 +1,78 @@
1
+ /// <reference types="react" />
2
+ import { IReqoreDropdownItem } from '@qoretechnologies/reqore/dist/components/Dropdown/list';
3
+ /**
4
+ * SEAM (reqraft): an editor publishes its own actions into the row's ⋮ menu.
5
+ *
6
+ * A row already renders one overflow menu — Edit fullscreen, Remove field, and
7
+ * any `optionActions` the host injected — and the comment on it has always said
8
+ * why: *"this row already has a menu, so they reuse it rather than adding a
9
+ * second one beside it."* An EDITOR could not reach that menu, so an editor
10
+ * with affordances of its own had no choice but to draw a second ⋮ inside the
11
+ * value cell. The IDE's template field does exactly that, and the two menus
12
+ * ended up side by side on one control, at slightly different widths.
13
+ *
14
+ * `optionActions` does not solve it: that seam is host configuration, resolved
15
+ * from the option's name and schema before the editor exists, so it cannot know
16
+ * what the editor can offer *now* — whether its templates have loaded, whether
17
+ * the value is already an expression, which custom items the field was given.
18
+ *
19
+ * ## Why registration is keyed, and by whom
20
+ *
21
+ * Menu items carry `onClick` handlers, so they are a new array on every render
22
+ * and cannot be compared by value. An editor therefore publishes a `key`
23
+ * alongside them: a primitive describing WHICH items it is currently offering
24
+ * (`'expression,template'`). The row re-renders only when a publisher's key
25
+ * changes, so a re-rendering editor cannot drive the row into a state loop.
26
+ *
27
+ * Each registration also belongs to ONE publisher. A single shared slot let
28
+ * two publishers in the same row — an expression builder's operands — overwrite
29
+ * each other's key on every render, which re-rendered the row and both of them,
30
+ * which published again: a synchronous render loop that froze the page. And a
31
+ * slot that outlived its editor kept that editor's handlers after it unmounted,
32
+ * so a row closed and reopened offered items that did nothing. Registrations
33
+ * are therefore per publisher, withdrawn on unmount, and a click always reaches
34
+ * the handler from the publisher's latest render.
35
+ *
36
+ * Publish with `useRowMenuPublisher`; provide with `useRowMenuRegistry`.
37
+ */
38
+ export interface IRowMenuRegistration {
39
+ /**
40
+ * Publish a publisher's menu items into the containing row's ⋮.
41
+ *
42
+ * @param publisherId identifies the publishing editor (one per mounted editor)
43
+ * @param key a primitive describing which items are being offered; the row
44
+ * re-renders only when it changes
45
+ * @param items the items to merge, or an empty array to contribute none
46
+ */
47
+ registerRowMenuItems: (publisherId: string, key: string, items: IReqoreDropdownItem[]) => void;
48
+ /** Withdraw a publisher's items — when it unmounts or leaves the row. */
49
+ unregisterRowMenuItems: (publisherId: string) => void;
50
+ }
51
+ /**
52
+ * Absent outside a compact row — the classic (non-compact) render path has no
53
+ * row menu to merge into, and an editor there keeps drawing its own control.
54
+ */
55
+ export declare const RowMenuContext: import("react").Context<IRowMenuRegistration>;
56
+ /**
57
+ * The row-menu channel for the row this editor is inside, or `undefined` when
58
+ * there is no row menu to publish into.
59
+ *
60
+ * An editor that finds it should render no menu of its own; one that does not
61
+ * must keep rendering its own, or its actions become unreachable.
62
+ */
63
+ export declare const useRowMenu: () => IRowMenuRegistration | undefined;
64
+ /**
65
+ * Publish `items` into `rowMenu` for as long as the calling editor is mounted.
66
+ * `rowMenu` is passed rather than read here so an editor can claim the channel
67
+ * and hide it from what it renders (see `TemplateField`).
68
+ */
69
+ export declare const useRowMenuPublisher: (rowMenu: IRowMenuRegistration | undefined, key: string, items: IReqoreDropdownItem[]) => void;
70
+ /**
71
+ * The row side of the channel: the registration to provide, and the items the
72
+ * row's ⋮ should list.
73
+ */
74
+ export declare const useRowMenuRegistry: () => {
75
+ rowMenu: IRowMenuRegistration;
76
+ items: IReqoreDropdownItem[];
77
+ };
78
+ //# sourceMappingURL=rowMenuContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rowMenuContext.d.ts","sourceRoot":"","sources":["../../../../src/components/form/engine/rowMenuContext.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,wDAAwD,CAAC;AAY7F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;;OAOG;IACH,oBAAoB,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,IAAI,CAAC;IAC/F,yEAAyE;IACzE,sBAAsB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,+CAA6D,CAAC;AAEzF;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,QAAO,oBAAoB,GAAG,SAAuC,CAAC;AAE7F;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,YACrB,oBAAoB,GAAG,SAAS,OACpC,MAAM,SACJ,mBAAmB,EAAE,KAC3B,IAQF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAO;IACpC,OAAO,EAAE,oBAAoB,CAAC;IAC9B,KAAK,EAAE,mBAAmB,EAAE,CAAC;CAwD9B,CAAC"}
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
14
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
15
+ if (ar || !(i in from)) {
16
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
17
+ ar[i] = from[i];
18
+ }
19
+ }
20
+ return to.concat(ar || Array.prototype.slice.call(from));
21
+ };
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.useRowMenuRegistry = exports.useRowMenuPublisher = exports.useRowMenu = exports.RowMenuContext = void 0;
24
+ var react_1 = require("react");
25
+ /**
26
+ * Absent outside a compact row — the classic (non-compact) render path has no
27
+ * row menu to merge into, and an editor there keeps drawing its own control.
28
+ */
29
+ exports.RowMenuContext = (0, react_1.createContext)(undefined);
30
+ /**
31
+ * The row-menu channel for the row this editor is inside, or `undefined` when
32
+ * there is no row menu to publish into.
33
+ *
34
+ * An editor that finds it should render no menu of its own; one that does not
35
+ * must keep rendering its own, or its actions become unreachable.
36
+ */
37
+ var useRowMenu = function () { return (0, react_1.useContext)(exports.RowMenuContext); };
38
+ exports.useRowMenu = useRowMenu;
39
+ /**
40
+ * Publish `items` into `rowMenu` for as long as the calling editor is mounted.
41
+ * `rowMenu` is passed rather than read here so an editor can claim the channel
42
+ * and hide it from what it renders (see `TemplateField`).
43
+ */
44
+ var useRowMenuPublisher = function (rowMenu, key, items) {
45
+ var publisherId = (0, react_1.useId)();
46
+ (0, react_1.useEffect)(function () {
47
+ rowMenu === null || rowMenu === void 0 ? void 0 : rowMenu.registerRowMenuItems(publisherId, key, items);
48
+ }, [rowMenu, publisherId, key, items]);
49
+ (0, react_1.useEffect)(function () { return function () { return rowMenu === null || rowMenu === void 0 ? void 0 : rowMenu.unregisterRowMenuItems(publisherId); }; }, [rowMenu, publisherId]);
50
+ };
51
+ exports.useRowMenuPublisher = useRowMenuPublisher;
52
+ /**
53
+ * The row side of the channel: the registration to provide, and the items the
54
+ * row's ⋮ should list.
55
+ */
56
+ var useRowMenuRegistry = function () {
57
+ // Which publishers are offering which item SET — the only state, so only a
58
+ // change of set re-renders the row.
59
+ var _a = (0, react_1.useState)({}), keys = _a[0], setKeys = _a[1];
60
+ // Each publisher's items from its latest render, read at click time.
61
+ var latest = (0, react_1.useRef)({});
62
+ var registerRowMenuItems = (0, react_1.useCallback)(function (publisherId, key, items) {
63
+ latest.current[publisherId] = items;
64
+ setKeys(function (previous) {
65
+ var _a;
66
+ return previous[publisherId] === key ? previous : __assign(__assign({}, previous), (_a = {}, _a[publisherId] = key, _a));
67
+ });
68
+ }, []);
69
+ var unregisterRowMenuItems = (0, react_1.useCallback)(function (publisherId) {
70
+ delete latest.current[publisherId];
71
+ setKeys(function (previous) {
72
+ if (!(publisherId in previous)) {
73
+ return previous;
74
+ }
75
+ var next = __assign({}, previous);
76
+ delete next[publisherId];
77
+ return next;
78
+ });
79
+ }, []);
80
+ var rowMenu = (0, react_1.useMemo)(function () { return ({ registerRowMenuItems: registerRowMenuItems, unregisterRowMenuItems: unregisterRowMenuItems }); }, [registerRowMenuItems, unregisterRowMenuItems]);
81
+ var items = (0, react_1.useMemo)(function () {
82
+ return Object.keys(keys).flatMap(function (publisherId) {
83
+ var _a;
84
+ return ((_a = latest.current[publisherId]) !== null && _a !== void 0 ? _a : []).map(function (item, index) {
85
+ return item.onClick
86
+ ? __assign(__assign({}, item), {
87
+ // Same key, same item set, so the index still names this item;
88
+ // the handler is whatever the publisher rendered last.
89
+ onClick: (function () {
90
+ var _a, _b, _c;
91
+ var args = [];
92
+ for (var _i = 0; _i < arguments.length; _i++) {
93
+ args[_i] = arguments[_i];
94
+ }
95
+ return (_c = (_b = (_a = latest.current[publisherId]) === null || _a === void 0 ? void 0 : _a[index]) === null || _b === void 0 ? void 0 : _b.onClick) === null || _c === void 0 ? void 0 : _c.call.apply(_c, __spreadArray([_b], args, false));
96
+ }) }) : item;
97
+ });
98
+ });
99
+ }, [keys]);
100
+ return { rowMenu: rowMenu, items: items };
101
+ };
102
+ exports.useRowMenuRegistry = useRowMenuRegistry;
103
+ //# sourceMappingURL=rowMenuContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rowMenuContext.js","sourceRoot":"","sources":["../../../../src/components/form/engine/rowMenuContext.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Which options offer the per-type choices ("Integer", "Text", "Hash", …) in
3
+ * the row's menu.
4
+ *
5
+ * A field that pins no concrete type has to give the author some way to say
6
+ * what kind of value they mean, and the row menu is where that lives — the
7
+ * alternative, a standalone *"Please select data type"* control, replaces a
8
+ * typable field with a pick-only one.
9
+ *
10
+ * The rule was `ui_type === 'any'`, which is one of the three ways a schema
11
+ * says "untyped", so the choices went missing on the other two:
12
+ *
13
+ * - **`type: 'auto'` with no `ui_type` at all.** This is how the server
14
+ * declares an assertion's Expected Value, so that field never offered them.
15
+ * They appeared only inside an expression, where the OPERAND's schema does
16
+ * say `ui_type: 'any'` — so the same option offered them in the Visual view
17
+ * and not in the Text view, which reads as the menu losing items.
18
+ * - **A host's own untyped editor.** The IDE renders Expected Value through
19
+ * `test-reference`, a `ui_type` reqraft knows nothing about; the field is no
20
+ * less untyped for having a custom editor.
21
+ *
22
+ * `ui_type` is asked FIRST and on its own: a schema that names a concrete
23
+ * editor (`ui_type: 'string'`) has pinned the field even if its storage `type`
24
+ * is still `auto`.
25
+ */
26
+ import { IQorusFormField } from '@qoretechnologies/ts-toolkit';
27
+ /** The spellings that mean "no concrete type" wherever they appear. */
28
+ export declare const BuiltInAnyLikeTypes: string[];
29
+ /**
30
+ * Does this option leave its type up to the author?
31
+ *
32
+ * @param option the option's schema entry
33
+ * @param extraUiTypes host `ui_type` names that are untyped editors of their
34
+ * own — see `FormEngine`'s `anyLikeUiTypes`
35
+ */
36
+ export declare const offersTypeChoices: (option: Partial<IQorusFormField> | undefined, extraUiTypes?: string[]) => boolean;
37
+ //# sourceMappingURL=typeChoices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typeChoices.d.ts","sourceRoot":"","sources":["../../../../src/components/form/engine/typeChoices.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,UAAkB,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,WACpB,QAAQ,eAAe,CAAC,GAAG,SAAS,iBAC7B,MAAM,EAAE,KACtB,OAaF,CAAC"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.offersTypeChoices = exports.BuiltInAnyLikeTypes = void 0;
4
+ /** The spellings that mean "no concrete type" wherever they appear. */
5
+ exports.BuiltInAnyLikeTypes = ['any', 'auto'];
6
+ /**
7
+ * Does this option leave its type up to the author?
8
+ *
9
+ * @param option the option's schema entry
10
+ * @param extraUiTypes host `ui_type` names that are untyped editors of their
11
+ * own — see `FormEngine`'s `anyLikeUiTypes`
12
+ */
13
+ var offersTypeChoices = function (option, extraUiTypes) {
14
+ if (!option) {
15
+ return false;
16
+ }
17
+ var uiType = option.ui_type;
18
+ if (uiType) {
19
+ return exports.BuiltInAnyLikeTypes.includes(uiType) || !!(extraUiTypes === null || extraUiTypes === void 0 ? void 0 : extraUiTypes.includes(uiType));
20
+ }
21
+ // No `ui_type`: the storage type is the only thing that can pin the field.
22
+ return exports.BuiltInAnyLikeTypes.includes(option.type);
23
+ };
24
+ exports.offersTypeChoices = offersTypeChoices;
25
+ //# sourceMappingURL=typeChoices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typeChoices.js","sourceRoot":"","sources":["../../../../src/components/form/engine/typeChoices.ts"],"names":[],"mappings":";;;AA4BA,uEAAuE;AAC1D,QAAA,mBAAmB,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAEnD;;;;;;GAMG;AACI,IAAM,iBAAiB,GAAG,UAC/B,MAA4C,EAC5C,YAAuB;IAEvB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAM,MAAM,GAAI,MAA+B,CAAC,OAAO,CAAC;IAExD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,2BAAmB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,QAAQ,CAAC,MAAM,CAAC,CAAA,CAAC;IAClF,CAAC;IAED,2EAA2E;IAC3E,OAAO,2BAAmB,CAAC,QAAQ,CAAE,MAA4B,CAAC,IAAc,CAAC,CAAC;AACpF,CAAC,CAAC;AAhBW,QAAA,iBAAiB,qBAgB5B"}
@@ -38,10 +38,16 @@ export interface IExpressionFieldProps {
38
38
  recordType?: string;
39
39
  /** Initial editor mode (default `visual`). */
40
40
  defaultMode?: TExpressionMode;
41
+ /**
42
+ * SEAM (reqraft): the host's per-`ui_type` editors, forwarded to the
43
+ * builder's operand fields. Without them an operand typed with one of the
44
+ * CONSUMER's ui_types renders "Unknown type!".
45
+ */
46
+ componentOverrides?: Record<string, React.FC<any>>;
41
47
  size?: string;
42
48
  /** Forwarded to the builder — operand reordering for varargs expressions. */
43
49
  reorder?: TExpressionReorder;
44
50
  }
45
- export declare const ExpressionField: import("react").MemoExoticComponent<({ value, onChange, type, returnType, readOnly, expressions: expressionsOverride, expressionsUrl, localTemplates, serverHandled, extraActions, provider, recordType, defaultMode, size, reorder, }: IExpressionFieldProps) => import("react/jsx-runtime").JSX.Element>;
51
+ export declare const ExpressionField: import("react").MemoExoticComponent<({ value, onChange, type, returnType, readOnly, expressions: expressionsOverride, expressionsUrl, localTemplates, serverHandled, extraActions, provider, recordType, defaultMode, size, componentOverrides, reorder, }: IExpressionFieldProps) => import("react/jsx-runtime").JSX.Element>;
46
52
  export default ExpressionField;
47
53
  //# sourceMappingURL=ExpressionField.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpressionField.d.ts","sourceRoot":"","sources":["../../../../src/components/form/expressions/ExpressionField.tsx"],"names":[],"mappings":";AAYA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mDAAmD,CAAC;AAGzF,OAAO,EAAqB,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAoB,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAI/F,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,MAAM,CAAC;AAShD,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;OAGG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACzD,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,uEAAuE;IACvE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,uBAAuB,CAAC,cAAc,CAAC,CAAC;IACvD,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED,eAAO,MAAM,eAAe,0OAiBvB,qBAAqB,6CAgMzB,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"ExpressionField.d.ts","sourceRoot":"","sources":["../../../../src/components/form/expressions/ExpressionField.tsx"],"names":[],"mappings":";AAaA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mDAAmD,CAAC;AAGzF,OAAO,EAAqB,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAoB,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAI/F,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,MAAM,CAAC;AAkBhD,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;OAGG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACzD,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,uEAAuE;IACvE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,uBAAuB,CAAC,cAAc,CAAC,CAAC;IACvD,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B;AAED,eAAO,MAAM,eAAe,8PAkBvB,qBAAqB,6CA4XzB,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -43,8 +43,9 @@ var jsx_runtime_1 = require("react/jsx-runtime");
43
43
  // Owns the `IExpression` value and offers two views of the same AST —
44
44
  // Visual (the builder, Phase 3) and Text (DPQL, this phase). The DPQL text
45
45
  // editor bridges to the AST via the LSP `dpql/parse` (text → AST on edit)
46
- // and `dpql/serialize` (AST → text on entering Text mode); a live "Parsed"
47
- // line renders the current AST (`dpql/renderExpression`).
46
+ // and `dpql/serialize` (AST → text on entering Text mode); a debounced
47
+ // "Preview" box renders the current AST (`dpql/renderExpression`) when that
48
+ // rendering differs from the text the author typed.
48
49
  var reqore_1 = require("@qoretechnologies/reqore");
49
50
  var react_1 = require("react");
50
51
  var dpqlEditor_1 = require("../../dpqlEditor");
@@ -52,13 +53,22 @@ var builder_1 = require("./builder");
52
53
  var useExpressions_1 = require("./useExpressions");
53
54
  var useRenderExpression_1 = require("./useRenderExpression");
54
55
  var PARSE_DEBOUNCE_MS = 300;
56
+ /**
57
+ * How long the Preview waits before catching up with the editor.
58
+ *
59
+ * It mirrors the AST, so without this it re-renders on every keystroke — a box
60
+ * appearing, changing and vanishing under the line being typed. Slightly
61
+ * SLOWER than the parse it follows, so the preview settles once after the text
62
+ * has stopped moving rather than twitching on the way there.
63
+ */
64
+ var PREVIEW_DEBOUNCE_MS = 400;
55
65
  /** Retry cadence for seeding the Text editor while its LSP session opens. */
56
66
  var SEED_RETRY_MS = 400;
57
67
  var SEED_MAX_TRIES = 20;
58
68
  var noop = function () { return undefined; };
59
69
  exports.ExpressionField = (0, react_1.memo)(function (_a) {
60
70
  var _b;
61
- var value = _a.value, onChange = _a.onChange, type = _a.type, returnType = _a.returnType, readOnly = _a.readOnly, expressionsOverride = _a.expressions, expressionsUrl = _a.expressionsUrl, localTemplates = _a.localTemplates, serverHandled = _a.serverHandled, extraActions = _a.extraActions, provider = _a.provider, recordType = _a.recordType, _c = _a.defaultMode, defaultMode = _c === void 0 ? 'visual' : _c, size = _a.size, reorder = _a.reorder;
71
+ var value = _a.value, onChange = _a.onChange, type = _a.type, returnType = _a.returnType, readOnly = _a.readOnly, expressionsOverride = _a.expressions, expressionsUrl = _a.expressionsUrl, localTemplates = _a.localTemplates, serverHandled = _a.serverHandled, extraActions = _a.extraActions, provider = _a.provider, recordType = _a.recordType, _c = _a.defaultMode, defaultMode = _c === void 0 ? 'visual' : _c, size = _a.size, componentOverrides = _a.componentOverrides, reorder = _a.reorder;
62
72
  var expressions = (0, useExpressions_1.useExpressions)({
63
73
  override: expressionsOverride,
64
74
  expressionsUrl: expressionsUrl,
@@ -72,65 +82,196 @@ exports.ExpressionField = (0, react_1.memo)(function (_a) {
72
82
  // Text mode state. `text` is the DPQL string the editor shows; the AST
73
83
  // (`value`) stays the source of truth, kept in sync via parse-on-edit.
74
84
  var _f = (0, react_1.useState)(''), text = _f[0], setText = _f[1];
85
+ /* The text the current AST was parsed FROM.
86
+ *
87
+ * The preview renders the AST, and the AST only moves on a SUCCESSFUL
88
+ * parse — so while an edit is mid-flight or simply invalid, the AST still
89
+ * describes the last thing that parsed. Showing it then states something
90
+ * the editor does not contain: deleting the `2` from `1 + 2` left
91
+ * "Unexpected end of input" sitting directly above a Preview confidently
92
+ * reading `1 + 2`. */
93
+ var _g = (0, react_1.useState)(''), astText = _g[0], setAstText = _g[1];
94
+ /**
95
+ * The server's answer to "can this expression's result satisfy the type
96
+ * this field declares?". `null` when nothing has been asked, or when the
97
+ * field declares no type worth asking about.
98
+ */
99
+ var _h = (0, react_1.useState)(null), typeCheck = _h[0], setTypeCheck = _h[1];
75
100
  var dpqlRef = (0, react_1.useRef)(null);
76
101
  var parseTimer = (0, react_1.useRef)(null);
77
102
  var ast = (0, react_1.useMemo)(function () { return value === null || value === void 0 ? void 0 : value.value; }, [value]);
103
+ /**
104
+ * The type the expression's result has to satisfy, sent with every parse.
105
+ *
106
+ * `auto` and `any` are left out deliberately: they accept anything, so the
107
+ * server would answer "compatible" for everything and the round trip would
108
+ * buy nothing. A field offering a CHOICE of return types has no single
109
+ * answer either, so it asks nothing rather than asking about the wrong one.
110
+ */
111
+ var targetType = (0, react_1.useMemo)(function () {
112
+ var declared = Array.isArray(returnType) ? undefined : (returnType !== null && returnType !== void 0 ? returnType : type);
113
+ return declared && declared !== 'auto' && declared !== 'any' ? declared : undefined;
114
+ }, [returnType, type]);
115
+ /** Read the type analysis off a parse result, or clear it. */
116
+ var readTypeCheck = (0, react_1.useCallback)(function (result) {
117
+ var _a;
118
+ if (!result || result.type_compatible === undefined) {
119
+ setTypeCheck(null);
120
+ return;
121
+ }
122
+ setTypeCheck({
123
+ compatible: !!result.type_compatible,
124
+ mayFail: !!result.coercion_may_fail,
125
+ inferred: result.inferred_type,
126
+ target: result.target_type,
127
+ fix: (_a = result.suggested_fix) === null || _a === void 0 ? void 0 : _a.text,
128
+ });
129
+ }, []);
78
130
  // Keep a readable rendering of the current expression in sync — the
79
131
  // single live mirror of the AST (server `dpql/renderExpression` when
80
132
  // reachable, the client-side approximation otherwise).
133
+ //
134
+ // Debounced: it follows the AST, which follows the text, so rendering it
135
+ // eagerly put a box under the cursor that appeared, changed and vanished
136
+ // while the author was still typing.
81
137
  (0, react_1.useEffect)(function () {
82
138
  var live = true;
83
- renderRich(ast !== null && ast !== void 0 ? ast : {}, expressions).then(function (r) {
84
- if (live)
85
- setPreview({ text: r.text, server: r.server });
86
- });
139
+ var timer = setTimeout(function () {
140
+ renderRich(ast !== null && ast !== void 0 ? ast : {}, expressions).then(function (r) {
141
+ if (live)
142
+ setPreview({ text: r.text, server: r.server });
143
+ });
144
+ }, PREVIEW_DEBOUNCE_MS);
87
145
  return function () {
88
146
  live = false;
147
+ clearTimeout(timer);
89
148
  };
90
149
  }, [renderRich, ast, expressions]);
91
150
  // Tracks whether the user has typed since entering Text mode, so a
92
151
  // slow seed response can't clobber their input.
93
152
  var userTypedRef = (0, react_1.useRef)(false);
94
- var astRef = (0, react_1.useRef)(ast);
153
+ /* A cleared value must clear the editor with it.
154
+ *
155
+ * "Clear value" empties the option (`handleValueChange(name, undefined)`),
156
+ * but the DPQL text is local state seeded once on entering Text mode, and
157
+ * the seeding effect returns early for an empty AST — it exists to fill the
158
+ * editor, not to empty it. So the value went and the text stayed: the row
159
+ * reported "This field is required" while still showing `1 + 2`, which
160
+ * reads as the clear having silently failed.
161
+ *
162
+ * Only an AST that HAD content and lost it clears the box. A field that
163
+ * simply starts empty is left alone, so this cannot wipe what an author is
164
+ * part-way through typing before their first parse lands.
165
+ */
166
+ var hadExpression = (0, react_1.useRef)(false);
95
167
  (0, react_1.useEffect)(function () {
96
- astRef.current = ast;
168
+ /* GONE, not merely incomplete.
169
+ *
170
+ * The first cut asked whether the AST still had an `exp`, and that is a
171
+ * state the value passes THROUGH while the author types: a half-written
172
+ * expression parses to something without one. So typing `1 + 2` cleared
173
+ * the editor mid-keystroke and the text vanished under the cursor.
174
+ *
175
+ * A clear removes the value itself, so that is what to watch: `value`
176
+ * empty, not an AST that is between shapes. Typing never produces that —
177
+ * `handleDpqlChange` only emits on a SUCCESSFUL parse, so an incomplete
178
+ * expression leaves the previous value in place. */
179
+ if (ast) {
180
+ hadExpression.current = true;
181
+ return;
182
+ }
183
+ if (hadExpression.current) {
184
+ hadExpression.current = false;
185
+ userTypedRef.current = false;
186
+ setText('');
187
+ }
97
188
  }, [ast]);
98
189
  // Text mode: parse the DPQL into the AST (debounced).
190
+ /* The session may not be attached when the debounce fires — the editor
191
+ mounts on the render that flips the mode, and its LSP session comes up
192
+ after that. `dpqlRef.current?.parse?.()` is optional all the way down, so
193
+ an early call resolves `undefined`, `readTypeCheck` CLEARS the analysis,
194
+ and nothing asks again: the author types once into a fresh Text field and
195
+ is told nothing about the type until they happen to type another
196
+ character.
197
+
198
+ The seeding effect below already retries for this exact reason
199
+ ("`serialize` resolves '' until the editor's LSP session is ready"); the
200
+ parse path needed the same care and did not have it. Same budget as
201
+ seeding, so a session that never arrives gives up rather than spinning.
202
+
203
+ Found by CI, not locally: the session is up before the debounce on a
204
+ fast machine, so the story asserting the type message passed here every
205
+ time and failed on the runner. */
99
206
  var handleDpqlChange = (0, react_1.useCallback)(function (next) {
100
207
  userTypedRef.current = true;
101
208
  setText(next);
102
209
  if (parseTimer.current)
103
210
  clearTimeout(parseTimer.current);
104
- parseTimer.current = setTimeout(function () { return __awaiter(void 0, void 0, void 0, function () {
105
- var result;
106
- var _a, _b;
107
- return __generator(this, function (_c) {
108
- switch (_c.label) {
109
- case 0: return [4 /*yield*/, ((_b = (_a = dpqlRef.current) === null || _a === void 0 ? void 0 : _a.parse) === null || _b === void 0 ? void 0 : _b.call(_a, next))];
211
+ var tries = 0;
212
+ var runParse = function () { return __awaiter(void 0, void 0, void 0, function () {
213
+ var parse, result;
214
+ var _a;
215
+ return __generator(this, function (_b) {
216
+ switch (_b.label) {
217
+ case 0:
218
+ parse = (_a = dpqlRef.current) === null || _a === void 0 ? void 0 : _a.parse;
219
+ if (!parse) {
220
+ if (++tries < SEED_MAX_TRIES) {
221
+ parseTimer.current = setTimeout(runParse, SEED_RETRY_MS);
222
+ }
223
+ return [2 /*return*/];
224
+ }
225
+ return [4 /*yield*/, parse(next, targetType)];
110
226
  case 1:
111
- result = _c.sent();
227
+ result = _b.sent();
228
+ readTypeCheck(result);
112
229
  if ((result === null || result === void 0 ? void 0 : result.success) && result.expression) {
230
+ setAstText(next);
113
231
  // `dpql/parse` returns the field-ready `{ is_expression, value }`.
114
232
  onChange(result.expression);
115
233
  }
116
234
  return [2 /*return*/];
117
235
  }
118
236
  });
119
- }); }, PARSE_DEBOUNCE_MS);
120
- }, [onChange]);
121
- // Seed the Text editor from the AST whenever Text mode becomes active
122
- // (including a `defaultMode='text'` mount). This must run as an
123
- // effect: the editor only mounts on the same render that flips the
124
- // mode (so `dpqlRef` is null inside the click handler), and
125
- // `serialize` resolves '' until the editor's LSP session is ready —
126
- // hence the brief retry loop.
237
+ }); };
238
+ parseTimer.current = setTimeout(runParse, PARSE_DEBOUNCE_MS);
239
+ }, [onChange, targetType, readTypeCheck]);
240
+ /** Whether this visit to Text mode has already put the AST in the editor. */
241
+ var seededRef = (0, react_1.useRef)(false);
242
+ /* Seed the Text editor from the AST whenever Text mode becomes active
243
+ * (including a `defaultMode='text'` mount). This must run as an effect: the
244
+ * editor only mounts on the same render that flips the mode (so `dpqlRef`
245
+ * is null inside the click handler), and `serialize` resolves '' until the
246
+ * editor's LSP session is ready — hence the brief retry loop.
247
+ *
248
+ * It waits for an AST, because the AST can arrive AFTER the mode does. A
249
+ * host flips this shell into Text mode on the very render that accepts an
250
+ * expression, and the value it passes on that render is still the TEXT the
251
+ * author typed — the parsed AST only comes back through the form one render
252
+ * later. Keyed on the mode alone, the effect had already had its only turn
253
+ * by then: it found nothing to serialize, returned, and nothing asked
254
+ * again. The author landed in an empty editor with a Preview of their own
255
+ * expression sitting beside it, which reads as the text having been thrown
256
+ * away.
257
+ *
258
+ * Seeding happens at most ONCE per visit, and that is what makes watching
259
+ * the AST safe: `userTypedRef` guards what is being typed, but the AST
260
+ * MOVES as the author types — every successful parse replaces it — so an
261
+ * effect that re-seeded on each new AST would fight them for the editor. */
127
262
  (0, react_1.useEffect)(function () {
128
- if (mode !== 'text')
263
+ if (mode !== 'text') {
264
+ // Leaving Text asks the question again on the next visit; the typing
265
+ // flag goes with it, or a stale one would suppress that visit's seed.
266
+ seededRef.current = false;
267
+ userTypedRef.current = false;
268
+ return undefined;
269
+ }
270
+ if (seededRef.current || userTypedRef.current)
129
271
  return undefined;
130
- var seedAst = astRef.current;
131
- if (!(seedAst === null || seedAst === void 0 ? void 0 : seedAst.exp))
272
+ if (!(ast === null || ast === void 0 ? void 0 : ast.exp))
132
273
  return undefined;
133
- userTypedRef.current = false;
274
+ var seedAst = ast;
134
275
  var cancelled = false;
135
276
  var timer = null;
136
277
  var tries = 0;
@@ -145,7 +286,9 @@ exports.ExpressionField = (0, react_1.memo)(function (_a) {
145
286
  if (cancelled || userTypedRef.current)
146
287
  return [2 /*return*/];
147
288
  if (t) {
289
+ seededRef.current = true;
148
290
  setText(t);
291
+ setAstText(t);
149
292
  }
150
293
  else if (++tries < SEED_MAX_TRIES) {
151
294
  timer = setTimeout(seed, SEED_RETRY_MS);
@@ -160,7 +303,7 @@ exports.ExpressionField = (0, react_1.memo)(function (_a) {
160
303
  if (timer)
161
304
  clearTimeout(timer);
162
305
  };
163
- }, [mode]);
306
+ }, [mode, ast]);
164
307
  // Switch to Text: the seeding effect above serializes the AST once
165
308
  // the editor's session is up.
166
309
  var enterTextMode = (0, react_1.useCallback)(function () {
@@ -176,9 +319,10 @@ exports.ExpressionField = (0, react_1.memo)(function (_a) {
176
319
  if (parseTimer.current)
177
320
  clearTimeout(parseTimer.current);
178
321
  if (!(mode === 'text' && text)) return [3 /*break*/, 2];
179
- return [4 /*yield*/, ((_b = (_a = dpqlRef.current) === null || _a === void 0 ? void 0 : _a.parse) === null || _b === void 0 ? void 0 : _b.call(_a, text))];
322
+ return [4 /*yield*/, ((_b = (_a = dpqlRef.current) === null || _a === void 0 ? void 0 : _a.parse) === null || _b === void 0 ? void 0 : _b.call(_a, text, targetType))];
180
323
  case 1:
181
324
  result = _c.sent();
325
+ readTypeCheck(result);
182
326
  if ((result === null || result === void 0 ? void 0 : result.success) && result.expression) {
183
327
  onChange(result.expression);
184
328
  }
@@ -188,12 +332,16 @@ exports.ExpressionField = (0, react_1.memo)(function (_a) {
188
332
  return [2 /*return*/];
189
333
  }
190
334
  });
191
- }); }, [mode, text, onChange]);
335
+ }); }, [mode, text, onChange, targetType, readTypeCheck]);
192
336
  (0, react_1.useEffect)(function () { return function () {
193
337
  if (parseTimer.current)
194
338
  clearTimeout(parseTimer.current);
195
339
  }; }, []);
196
- return ((0, jsx_runtime_1.jsxs)(reqore_1.ReqoreControlGroup, { vertical: true, fluid: true, gapSize: 'small', className: 'expression-field', children: [(0, jsx_runtime_1.jsxs)(reqore_1.ReqoreControlGroup, { gapSize: 'small', fluid: true, children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreButton, { icon: 'NodeTree', active: mode === 'visual', onClick: enterVisualMode, disabled: readOnly, size: size, children: "Visual" }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreButton, { icon: 'CodeLine', active: mode === 'text', onClick: enterTextMode, disabled: readOnly, size: size, children: "Text" })] }), mode === 'text' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(dpqlEditor_1.DpqlEditor, { ref: dpqlRef, value: text, onChange: handleDpqlChange, provider: provider, recordType: recordType, readOnly: readOnly, height: '48px' }), preview.text ? ((0, jsx_runtime_1.jsx)(reqore_1.ReqoreMessage, { intent: 'info', size: 'small', title: 'Parsed', flat: true, opaque: false, children: preview.server ? (
340
+ return ((0, jsx_runtime_1.jsxs)(reqore_1.ReqoreControlGroup, { vertical: true, fluid: true, gapSize: 'small', className: 'expression-field', children: [(0, jsx_runtime_1.jsxs)(reqore_1.ReqoreControlGroup, { gapSize: 'small', fluid: true, children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreButton, { icon: 'NodeTree', active: mode === 'visual', onClick: enterVisualMode, disabled: readOnly, size: size, children: "Visual" }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreButton, { icon: 'CodeLine', active: mode === 'text', onClick: enterTextMode, disabled: readOnly, size: size, children: "Text" })] }), mode === 'text' ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(dpqlEditor_1.DpqlEditor, { ref: dpqlRef, value: text, onChange: handleDpqlChange, provider: provider, recordType: recordType, readOnly: readOnly, height: '48px' }), typeCheck && !typeCheck.compatible && ((0, jsx_runtime_1.jsxs)(reqore_1.ReqoreMessage, { intent: typeCheck.mayFail ? 'warning' : 'danger', size: 'small', flat: true, opaque: false, title: typeCheck.mayFail ? 'This may not fit' : 'This does not fit', children: ["The expression returns ".concat(typeCheck.inferred, ", and this field holds ").concat(typeCheck.target, "."), typeCheck.mayFail ?
341
+ ' That conversion is attempted rather than guaranteed, so the value itself is checked when it runs.'
342
+ : '', typeCheck.fix ? ((0, jsx_runtime_1.jsx)("div", { style: { marginTop: '6px' }, children: (0, jsx_runtime_1.jsx)("code", { "data-testid": 'expression-type-fix', children: typeCheck.fix }) })) : null] })), preview.text &&
343
+ astText.trim() === text.trim() &&
344
+ preview.text.trim() !== text.trim() ? ((0, jsx_runtime_1.jsx)(reqore_1.ReqoreMessage, { intent: 'info', size: 'small', title: 'Preview', flat: true, opaque: false, children: preview.server ? (
197
345
  // Server rendering shown through a read-only DpqlEditor:
198
346
  // template-ref chips + LSP token colours; diagnostics off
199
347
  // (the rendering is readable text, not parseable DPQL).
@@ -201,7 +349,7 @@ exports.ExpressionField = (0, react_1.memo)(function (_a) {
201
349
  // Default to `auto` when the field declares no return type, so a
202
350
  // valid expression renders `muted` rather than `danger` (the
203
351
  // builder's intent requires a truthy returnType).
204
- returnType: ((_b = returnType !== null && returnType !== void 0 ? returnType : type) !== null && _b !== void 0 ? _b : 'auto'), readOnly: readOnly, localTemplates: localTemplates !== null && localTemplates !== void 0 ? localTemplates : { items: [] }, serverHandled: serverHandled, extraActions: extraActions, size: size, reorder: reorder }))] }));
352
+ returnType: ((_b = returnType !== null && returnType !== void 0 ? returnType : type) !== null && _b !== void 0 ? _b : 'auto'), readOnly: readOnly, localTemplates: localTemplates !== null && localTemplates !== void 0 ? localTemplates : { items: [] }, serverHandled: serverHandled, componentOverrides: componentOverrides, extraActions: extraActions, size: size, reorder: reorder }))] }));
205
353
  });
206
354
  exports.default = exports.ExpressionField;
207
355
  //# sourceMappingURL=ExpressionField.js.map