@qoretechnologies/reqraft 0.10.65 → 0.10.66-pr.145.g124fc92

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 (161) hide show
  1. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  2. package/dist/components/form/engine/CompactRow.js +130 -25
  3. package/dist/components/form/engine/CompactRow.js.map +1 -1
  4. package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
  5. package/dist/components/form/engine/CompactToolbar.js +78 -15
  6. package/dist/components/form/engine/CompactToolbar.js.map +1 -1
  7. package/dist/components/form/engine/FormEngine.d.ts +48 -1
  8. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  9. package/dist/components/form/engine/FormEngine.js +260 -99
  10. package/dist/components/form/engine/FormEngine.js.map +1 -1
  11. package/dist/components/form/engine/OptionFieldMessages.d.ts +18 -2
  12. package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
  13. package/dist/components/form/engine/OptionFieldMessages.js +22 -3
  14. package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
  15. package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
  16. package/dist/components/form/engine/_structuredData/SchemaDataView.js +27 -1
  17. package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
  18. package/dist/components/form/engine/compactRowContext.d.ts +5 -0
  19. package/dist/components/form/engine/compactRowContext.d.ts.map +1 -1
  20. package/dist/components/form/engine/compactRowContext.js.map +1 -1
  21. package/dist/components/form/engine/compactRowStyles.d.ts +12 -0
  22. package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
  23. package/dist/components/form/engine/compactRowStyles.js +72 -39
  24. package/dist/components/form/engine/compactRowStyles.js.map +1 -1
  25. package/dist/components/form/engine/compactToolbarContext.d.ts +3 -0
  26. package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
  27. package/dist/components/form/engine/compactToolbarContext.js.map +1 -1
  28. package/dist/components/form/engine/formAppearance.d.ts +48 -0
  29. package/dist/components/form/engine/formAppearance.d.ts.map +1 -0
  30. package/dist/components/form/engine/formAppearance.js +50 -0
  31. package/dist/components/form/engine/formAppearance.js.map +1 -0
  32. package/dist/components/form/engine/readFirst.d.ts +14 -2
  33. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  34. package/dist/components/form/engine/readFirst.js +6 -2
  35. package/dist/components/form/engine/readFirst.js.map +1 -1
  36. package/dist/components/form/engine/rowFocus.d.ts +9 -1
  37. package/dist/components/form/engine/rowFocus.d.ts.map +1 -1
  38. package/dist/components/form/engine/rowFocus.js +17 -5
  39. package/dist/components/form/engine/rowFocus.js.map +1 -1
  40. package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
  41. package/dist/components/form/expressions/renderExpressionToText.js +21 -0
  42. package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
  43. package/dist/components/form/fields/ChoiceGroupHeading.d.ts +21 -0
  44. package/dist/components/form/fields/ChoiceGroupHeading.d.ts.map +1 -0
  45. package/dist/components/form/fields/ChoiceGroupHeading.js +33 -0
  46. package/dist/components/form/fields/ChoiceGroupHeading.js.map +1 -0
  47. package/dist/components/form/fields/Field.d.ts.map +1 -1
  48. package/dist/components/form/fields/Field.js +13 -11
  49. package/dist/components/form/fields/Field.js.map +1 -1
  50. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
  51. package/dist/components/form/fields/allowed-values/AllowedValues.js +20 -7
  52. package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
  53. package/dist/components/form/fields/allowed-values/savedValues.d.ts +30 -0
  54. package/dist/components/form/fields/allowed-values/savedValues.d.ts.map +1 -0
  55. package/dist/components/form/fields/allowed-values/savedValues.js +18 -0
  56. package/dist/components/form/fields/allowed-values/savedValues.js.map +1 -0
  57. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  58. package/dist/components/form/fields/auto/AutoFormField.js +18 -8
  59. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  60. package/dist/components/form/fields/long-string/LongString.d.ts +7 -1
  61. package/dist/components/form/fields/long-string/LongString.d.ts.map +1 -1
  62. package/dist/components/form/fields/long-string/LongString.js +9 -5
  63. package/dist/components/form/fields/long-string/LongString.js.map +1 -1
  64. package/dist/components/form/fields/multi-select/MultiSelectFormField.d.ts.map +1 -1
  65. package/dist/components/form/fields/multi-select/MultiSelectFormField.js +6 -5
  66. package/dist/components/form/fields/multi-select/MultiSelectFormField.js.map +1 -1
  67. package/dist/components/form/fields/number/Number.d.ts +4 -1
  68. package/dist/components/form/fields/number/Number.d.ts.map +1 -1
  69. package/dist/components/form/fields/number/Number.js +13 -5
  70. package/dist/components/form/fields/number/Number.js.map +1 -1
  71. package/dist/components/form/fields/radio-group/RadioGroup.d.ts.map +1 -1
  72. package/dist/components/form/fields/radio-group/RadioGroup.js +13 -7
  73. package/dist/components/form/fields/radio-group/RadioGroup.js.map +1 -1
  74. package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
  75. package/dist/components/form/fields/template/TemplateField.js +71 -42
  76. package/dist/components/form/fields/template/TemplateField.js.map +1 -1
  77. package/dist/components/form/index.d.ts +2 -0
  78. package/dist/components/form/index.d.ts.map +1 -1
  79. package/dist/components/form/index.js +2 -0
  80. package/dist/components/form/index.js.map +1 -1
  81. package/dist/components/imageLightbox/ImageLightbox.d.ts.map +1 -1
  82. package/dist/components/imageLightbox/ImageLightbox.js +2 -0
  83. package/dist/components/imageLightbox/ImageLightbox.js.map +1 -1
  84. package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +3 -2
  85. package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +1 -1
  86. package/dist/components/supportTicket/InterfaceReferenceTags.js.map +1 -1
  87. package/dist/components/supportTicket/ReferencePicker.d.ts +2 -1
  88. package/dist/components/supportTicket/ReferencePicker.d.ts.map +1 -1
  89. package/dist/components/supportTicket/ReferencePicker.js +19 -13
  90. package/dist/components/supportTicket/ReferencePicker.js.map +1 -1
  91. package/dist/components/supportTicket/TicketHeader.d.ts.map +1 -1
  92. package/dist/components/supportTicket/TicketHeader.js +4 -1
  93. package/dist/components/supportTicket/TicketHeader.js.map +1 -1
  94. package/dist/components/supportTicket/TicketReplyBox.d.ts.map +1 -1
  95. package/dist/components/supportTicket/TicketReplyBox.js +29 -10
  96. package/dist/components/supportTicket/TicketReplyBox.js.map +1 -1
  97. package/dist/components/supportTicket/useLoadedImages.d.ts +10 -0
  98. package/dist/components/supportTicket/useLoadedImages.d.ts.map +1 -1
  99. package/dist/components/supportTicket/useLoadedImages.js +63 -1
  100. package/dist/components/supportTicket/useLoadedImages.js.map +1 -1
  101. package/dist/components/ticketReferences/TicketReferences.d.ts.map +1 -1
  102. package/dist/components/ticketReferences/TicketReferences.js +6 -2
  103. package/dist/components/ticketReferences/TicketReferences.js.map +1 -1
  104. package/dist/components/ticketThread/TicketThread.d.ts.map +1 -1
  105. package/dist/components/ticketThread/TicketThread.js +10 -16
  106. package/dist/components/ticketThread/TicketThread.js.map +1 -1
  107. package/dist/helpers/allowedValues.d.ts +12 -0
  108. package/dist/helpers/allowedValues.d.ts.map +1 -1
  109. package/dist/helpers/allowedValues.js +20 -1
  110. package/dist/helpers/allowedValues.js.map +1 -1
  111. package/dist/helpers/fieldDescription.d.ts +16 -0
  112. package/dist/helpers/fieldDescription.d.ts.map +1 -0
  113. package/dist/helpers/fieldDescription.js +40 -0
  114. package/dist/helpers/fieldDescription.js.map +1 -0
  115. package/dist/helpers/singleLineString.d.ts.map +1 -1
  116. package/dist/helpers/singleLineString.js +1 -0
  117. package/dist/helpers/singleLineString.js.map +1 -1
  118. package/package.json +1 -1
  119. package/src/components/form/engine/CompactRow.tsx +143 -23
  120. package/src/components/form/engine/CompactToolbar.tsx +85 -16
  121. package/src/components/form/engine/FormEngine.stories.tsx +25 -13
  122. package/src/components/form/engine/FormEngine.tsx +367 -81
  123. package/src/components/form/engine/FormEngineAppearance.stories.tsx +540 -0
  124. package/src/components/form/engine/FormEngineStickyHeaders.stories.tsx +5 -1
  125. package/src/components/form/engine/OptionFieldMessages.tsx +73 -2
  126. package/src/components/form/engine/_structuredData/SchemaDataView.tsx +17 -4
  127. package/src/components/form/engine/compactRowContext.ts +5 -0
  128. package/src/components/form/engine/compactRowStyles.ts +218 -38
  129. package/src/components/form/engine/compactToolbarContext.ts +3 -0
  130. package/src/components/form/engine/formAppearance.ts +78 -0
  131. package/src/components/form/engine/readFirst.ts +16 -3
  132. package/src/components/form/engine/rowFocus.ts +17 -6
  133. package/src/components/form/expressions/renderExpressionToText.ts +15 -0
  134. package/src/components/form/fields/ChoiceGroupHeading.tsx +38 -0
  135. package/src/components/form/fields/Field.tsx +11 -9
  136. package/src/components/form/fields/allowed-values/AllowedValues.tsx +21 -9
  137. package/src/components/form/fields/allowed-values/savedValues.ts +41 -0
  138. package/src/components/form/fields/auto/AutoFormField.tsx +17 -4
  139. package/src/components/form/fields/long-string/LongString.stories.tsx +27 -0
  140. package/src/components/form/fields/long-string/LongString.tsx +14 -3
  141. package/src/components/form/fields/multi-select/MultiSelectFormField.tsx +5 -4
  142. package/src/components/form/fields/number/Number.tsx +19 -4
  143. package/src/components/form/fields/radio-group/RadioGroup.tsx +13 -7
  144. package/src/components/form/fields/template/TemplateField.tsx +32 -4
  145. package/src/components/form/index.tsx +2 -0
  146. package/src/components/imageLightbox/ImageLightbox.tsx +2 -0
  147. package/src/components/supportTicket/InterfaceReferenceTags.tsx +3 -2
  148. package/src/components/supportTicket/ReferencePicker.stories.tsx +0 -4
  149. package/src/components/supportTicket/ReferencePicker.tsx +22 -12
  150. package/src/components/supportTicket/TicketConversation.stories.tsx +0 -1
  151. package/src/components/supportTicket/TicketHeader.tsx +2 -1
  152. package/src/components/supportTicket/TicketReplyBox.stories.tsx +24 -21
  153. package/src/components/supportTicket/TicketReplyBox.tsx +27 -5
  154. package/src/components/supportTicket/useLoadedImages.ts +20 -0
  155. package/src/components/ticketReferences/TicketReferences.stories.tsx +26 -0
  156. package/src/components/ticketReferences/TicketReferences.tsx +12 -4
  157. package/src/components/ticketThread/TicketThread.stories.tsx +71 -2
  158. package/src/components/ticketThread/TicketThread.tsx +15 -28
  159. package/src/helpers/allowedValues.ts +19 -0
  160. package/src/helpers/fieldDescription.ts +39 -0
  161. package/src/helpers/singleLineString.ts +1 -0
@@ -27,6 +27,7 @@ import { useContextSelector } from 'use-context-selector';
27
27
  import { hasAllDependenciesFullfilled, parseDependency } from '../../../helpers/validations';
28
28
  import { findTemplate, isValueTemplate } from '../../../helpers/templates';
29
29
  import { getDefaultValue, richtextToSegments, richtextToString } from '../../../helpers/common';
30
+ import { fieldHasLongDescription } from '../../../helpers/fieldDescription';
30
31
  import { hasTemplateText, templateTextSegments } from '../../../helpers/templateText';
31
32
  import { ReadOnlyTemplateTag } from '../fields/template/ReadOnlyTemplateTag';
32
33
  import { TemplateText } from '../fields/template/TemplateText';
@@ -35,6 +36,7 @@ import { Description } from '../../Description';
35
36
  import { useMarkdownRenderer } from '../../Description/markdownRendererContext';
36
37
  import { FocusedEditing } from '../../FocusedEditing';
37
38
  import { CompactRowContext } from './compactRowContext';
39
+ import { CALM_REQUIRED_LABEL, CALM_REQUIRED_READ_LABEL, useIsCalmForm } from './formAppearance';
38
40
  import { revealRow } from './revealRow';
39
41
  import {
40
42
  StyledActionSlot,
@@ -192,8 +194,17 @@ export const CompactRow = memo(
192
194
  clusterLast?: boolean;
193
195
  }) => {
194
196
  const readOnly = useContextSelector(CompactRowContext, (v) => v.readOnly);
197
+ // The look only — see `TFormEngineAppearance`. Nothing a row DOES reads it.
198
+ const calm = useIsCalmForm();
195
199
  const commitMode = useContextSelector(CompactRowContext, (v) => v.commitMode);
196
200
  const options = useContextSelector(CompactRowContext, (v) => v.options);
201
+ // Whether this field's emptiness is a fault yet: the person has opened it and left,
202
+ // or the host reported a submit attempt. Before that a required field is only to
203
+ // fill in, and says so in the quiet colour.
204
+ const visited = useContextSelector(
205
+ CompactRowContext,
206
+ (v) => v.revealRequired || !!v.visitedOptions?.includes(optionName)
207
+ );
197
208
 
198
209
  // An absorbed field keeps its own name — it is a different field, not a
199
210
  // property of its host — so the label comes from its schema exactly as it
@@ -296,6 +307,11 @@ export const CompactRow = memo(
296
307
  const cDanger = useContextSelector(CompactRowContext, (v) => v.cDanger);
297
308
  const cWarning = useContextSelector(CompactRowContext, (v) => v.cWarning);
298
309
  const cInfo = useContextSelector(CompactRowContext, (v) => v.cInfo);
310
+ const cText = useContextSelector(CompactRowContext, (v) => v.cText);
311
+ // A field's NAME: full text colour by default; grey in the calm look, so the
312
+ // value beside it is what the eye lands on. One colour for the read row, the
313
+ // open row and the edit card, so a name does not change as its field opens.
314
+ const cLabel = calm ? `${cText}b3` : cKey;
299
315
  // A markdown value renders as a document in the row's inset; the host's
300
316
  // renderer when it supplied one, reqraft's own otherwise.
301
317
 
@@ -402,6 +418,11 @@ export const CompactRow = memo(
402
418
  }
403
419
 
404
420
  if (valueType === 'bool' || valueType === 'boolean') {
421
+ // Calm: Yes and No are words. Neither is a status — "No" is not a
422
+ // failure — so neither is coloured.
423
+ if (calm) {
424
+ return <span style={textStyle}>{formatted}</span>;
425
+ }
405
426
  const truthy = field?.value === true || field?.value === 'true';
406
427
  return (
407
428
  <ReqoreTag
@@ -701,8 +722,11 @@ export const CompactRow = memo(
701
722
  );
702
723
  };
703
724
  const required = !!(schema?.required || schema?.required_groups);
704
- // The asterisk says "you have to fill this in". Read-only, nobody does.
705
- const requiredMark = required && !readOnly;
725
+ // The asterisk says "you have to fill this in". Read-only, nobody does. The
726
+ // calm look draws none at all: an empty required field says so in its
727
+ // value column instead ("Required · add a value"), and a filled one has
728
+ // nothing left to ask for.
729
+ const requiredMark = required && !readOnly && !calm;
706
730
  const removable =
707
731
  !readOnly && !schema?.preselected && !schema?.required && !schema?.required_groups;
708
732
  const changed = !hidden && !readOnly && hasOptionChanged(optionField?.value, optionName);
@@ -767,8 +791,15 @@ export const CompactRow = memo(
767
791
  minimal
768
792
  flat
769
793
  compact
770
- effect={{ uppercase: true, spaced: 1 }}
771
- intent={groupResolved ? 'success' : 'warning'}
794
+ effect={calm ? undefined : { uppercase: true, spaced: 1 }}
795
+ // Calm: a met group is nothing to point at — only the unmet one is
796
+ // coloured.
797
+ intent={
798
+ groupResolved ?
799
+ calm ? undefined
800
+ : 'success'
801
+ : 'warning'
802
+ }
772
803
  icon={groupResolved ? 'CheckLine' : 'LinkM'}
773
804
  label={
774
805
  !groupResolved ? 'One of'
@@ -921,16 +952,39 @@ export const CompactRow = memo(
921
952
  const editor = editorRef.current;
922
953
  if (!editor) return undefined;
923
954
  let settled = false;
955
+ /* A control that is not a text box holds the caret PROVISIONALLY. An editor that
956
+ takes text can mount after the row opens (a code editor loads late), and until
957
+ it does the only control there is its chrome: a fullscreen toggle. When the
958
+ text box arrives it takes the caret, unless the reader has moved it since. A
959
+ row that never grows one (a pick-one) simply keeps its control. */
960
+ let provisional: HTMLElement | null = null;
924
961
  const takeCaret = (): boolean => {
925
962
  if (settled) return true;
926
963
  const active = document.activeElement as HTMLElement | null;
964
+ if (provisional) {
965
+ if (active !== provisional) {
966
+ // The reader moved the caret themselves. Leave it there.
967
+ settled = true;
968
+ return true;
969
+ }
970
+ const textEntry = findRowFocusTarget(editor, { textEntryOnly: true });
971
+ if (!textEntry) return false;
972
+ textEntry.focus({ preventScroll: true });
973
+ // An editor's text box can be in the DOM before it can take focus (Monaco's
974
+ // edit area, mid-layout), and `focus()` then does nothing. Only a caret
975
+ // that landed counts; otherwise try again on the editor's next change.
976
+ if (document.activeElement !== textEntry) return false;
977
+ settled = true;
978
+ return true;
979
+ }
927
980
  if (active && active !== document.body && !rowRef.current?.contains(active)) {
928
981
  // Someone else holds the caret — a field the reader moved into while
929
982
  // this row's editor was still arriving. Leave it where it is.
930
983
  settled = true;
931
984
  return true;
932
985
  }
933
- const control = findRowFocusTarget(editor);
986
+ // A card hands the caret only to where text goes (see `findRowFocusTarget`).
987
+ const control = findRowFocusTarget(editor, { textEntryOnly: !inlineEditable });
934
988
  if (!control) return false;
935
989
  // The least scroll that shows the row clear of the pinned box header,
936
990
  // and none when it is already in view (see `revealRow`).
@@ -940,6 +994,9 @@ export const CompactRow = memo(
940
994
  // `preventScroll`, because the reveal above has already put the row
941
995
  // where it belongs; letting focus scroll as well moves it twice.
942
996
  control.focus({ preventScroll: true });
997
+ // A control that cannot take focus yet (still laying out) did not take
998
+ // the caret: try again on the editor's next change.
999
+ if (document.activeElement !== control) return false;
943
1000
  /* …and if the control the row offers is a PICKER, open it.
944
1001
  *
945
1002
  * A closed picker prints the value it holds, which is exactly what the
@@ -957,6 +1014,13 @@ export const CompactRow = memo(
957
1014
  if (openedByAuthor.current && inlineEditable && isClosedPickerTrigger(control)) {
958
1015
  openedByAuthor.current = false;
959
1016
  setOpenThePicker(true);
1017
+ settled = true;
1018
+ return true;
1019
+ }
1020
+ if (findRowFocusTarget(editor, { textEntryOnly: true }) !== control) {
1021
+ // Not a text box: hold it until one arrives (see `provisional`).
1022
+ provisional = control;
1023
+ return false;
960
1024
  }
961
1025
  settled = true;
962
1026
  return true;
@@ -965,7 +1029,9 @@ export const CompactRow = memo(
965
1029
  const observer = new MutationObserver(() => {
966
1030
  if (takeCaret()) observer.disconnect();
967
1031
  });
968
- observer.observe(editor, { childList: true, subtree: true });
1032
+ // Attributes too: an editor's text box can become focusable by an attribute or
1033
+ // style change after it is inserted (see the `focus()` checks above).
1034
+ observer.observe(editor, { childList: true, subtree: true, attributes: true });
969
1035
  return () => observer.disconnect();
970
1036
  }, [isExpanded, optionName, inlineEditable]);
971
1037
 
@@ -1150,7 +1216,7 @@ export const CompactRow = memo(
1150
1216
  fixed
1151
1217
  flat
1152
1218
  compact
1153
- effect={{ uppercase: true, spaced: 1 }}
1219
+ effect={calm ? undefined : { uppercase: true, spaced: 1 }}
1154
1220
  />
1155
1221
  : null;
1156
1222
  const isPassiveCloseAction = fixedAllowedValueOption;
@@ -1174,7 +1240,7 @@ export const CompactRow = memo(
1174
1240
  minimal
1175
1241
  fixed
1176
1242
  icon='CloseLine'
1177
- intent='warning'
1243
+ intent={calm ? undefined : 'warning'}
1178
1244
  // "Cancel edit", not "Cancel": a bare Cancel collides with the one
1179
1245
  // in Reqore's confirmation modal, and this one is specifically about
1180
1246
  // abandoning the edit rather than dismissing a dialog.
@@ -1195,7 +1261,9 @@ export const CompactRow = memo(
1195
1261
  <ReqoreButton
1196
1262
  className='options-readfirst-done'
1197
1263
  size='small'
1198
- intent={!isPassiveCloseAction ? 'success' : undefined}
1264
+ // Calm: a neutral button. Closing a field is not a healthy figure and
1265
+ // not the page's one primary action, so it takes neither colour.
1266
+ intent={!isPassiveCloseAction && !calm ? 'success' : undefined}
1199
1267
  flat={isPassiveCloseAction}
1200
1268
  minimal={isPassiveCloseAction}
1201
1269
  fixed
@@ -1236,6 +1304,10 @@ export const CompactRow = memo(
1236
1304
  allOptions: availableOptions,
1237
1305
  getType: getTypeForOption,
1238
1306
  isRendererOnly,
1307
+ calm,
1308
+ readOnly,
1309
+ // Until the person has been to the field, its requirement is stated plainly.
1310
+ requirementPending: !visited,
1239
1311
  }).map((m) => ({ intent: m.intent as string, content: String(m.label) }))
1240
1312
  : [];
1241
1313
  // TWO display channels, matching the Focus prototype:
@@ -1274,7 +1346,7 @@ export const CompactRow = memo(
1274
1346
  // with no route to the field's description at all. Stops propagation because
1275
1347
  // both labels are click targets themselves (the row expands / collapses).
1276
1348
  const helpIcon =
1277
- schema?.desc ?
1349
+ fieldHasLongDescription(schema) ?
1278
1350
  <ReqoreIcon
1279
1351
  icon='QuestionLine'
1280
1352
  size='12px'
@@ -1317,7 +1389,14 @@ export const CompactRow = memo(
1317
1389
  size='small'
1318
1390
  opaque={false}
1319
1391
  flat
1320
- intent={m.intent as never}
1392
+ // Calm: a note that only informs takes no colour — blue is kept for "a
1393
+ // filter is on", green for a healthy figure. A warning or a failure
1394
+ // keeps its intent: those are what a person has to act on.
1395
+ intent={
1396
+ (calm && (!m.intent || m.intent === 'info' || m.intent === 'success') ?
1397
+ undefined
1398
+ : m.intent) as never
1399
+ }
1321
1400
  title={m.title}
1322
1401
  style={{ marginBottom: 8 }}
1323
1402
  >
@@ -1493,7 +1572,7 @@ export const CompactRow = memo(
1493
1572
  tabIndex={0}
1494
1573
  aria-label={`Collapse ${label}`}
1495
1574
  title={schema?.short_desc || undefined}
1496
- $color={cKey}
1575
+ $color={cLabel}
1497
1576
  $pointer
1498
1577
  onClick={collapse}
1499
1578
  onKeyDown={(event) => {
@@ -1504,7 +1583,7 @@ export const CompactRow = memo(
1504
1583
  }}
1505
1584
  >
1506
1585
  {label}
1507
- {required ?
1586
+ {required && !calm ?
1508
1587
  <ReqoreIcon icon='Asterisk' color='danger' size='10px' />
1509
1588
  : null}
1510
1589
  {helpIcon}
@@ -1526,6 +1605,7 @@ export const CompactRow = memo(
1526
1605
  </StyledLabelBlock>
1527
1606
  <div
1528
1607
  ref={editorRef}
1608
+ className='options-readfirst-row-editor'
1529
1609
  style={{ minWidth: 0 }}
1530
1610
  onKeyDown={(event) => {
1531
1611
  if (event.key === 'Escape') {
@@ -1664,6 +1744,7 @@ export const CompactRow = memo(
1664
1744
  return (
1665
1745
  <StyledEditCard
1666
1746
  key={optionName}
1747
+ ref={rowRef}
1667
1748
  data-field={optionName}
1668
1749
  className={
1669
1750
  COMPACT_SINGLE_VALUE_TYPES.has(editType) ?
@@ -1672,6 +1753,7 @@ export const CompactRow = memo(
1672
1753
  }
1673
1754
  $bg={cHover}
1674
1755
  $border={schemaIntentColor ? `${schemaIntentColor}66` : `${cInfo}66`}
1756
+ $calm={calm}
1675
1757
  >
1676
1758
  <div
1677
1759
  style={{
@@ -1682,7 +1764,7 @@ export const CompactRow = memo(
1682
1764
  }}
1683
1765
  >
1684
1766
  <StyledCardHeading>
1685
- <StyledCardLabel $color={cKey}>
1767
+ <StyledCardLabel $color={cLabel}>
1686
1768
  {(
1687
1769
  (schema as { icon?: string } | undefined)?.icon ||
1688
1770
  (schema as { image?: string } | undefined)?.image
@@ -1695,7 +1777,7 @@ export const CompactRow = memo(
1695
1777
  : null}
1696
1778
  <span>
1697
1779
  {label}
1698
- {required ?
1780
+ {required && !calm ?
1699
1781
  <ReqoreIcon icon='Asterisk' color='danger' size='10px' />
1700
1782
  : null}
1701
1783
  {/* The `?` belongs here too. The read row and the inline
@@ -1802,6 +1884,11 @@ export const CompactRow = memo(
1802
1884
  keystroke a silent commit. Same handler, same `cancelEdit()`, so
1803
1885
  the two shapes cannot diverge. */}
1804
1886
  <div
1887
+ // The card's editor, as the inline row's is: an opened card takes the
1888
+ // caret too (the tap-to-edit rule above), so opening Description or
1889
+ // Source Code by a click or by a blocked submit puts the cursor in it.
1890
+ ref={editorRef}
1891
+ className='options-readfirst-row-editor'
1805
1892
  style={{ display: 'contents' }}
1806
1893
  onKeyDown={(event) => {
1807
1894
  if (event.key === 'Escape') {
@@ -1861,7 +1948,7 @@ export const CompactRow = memo(
1861
1948
  const valueReasons: TInfoMsg[] = [
1862
1949
  ...inlineMessages,
1863
1950
  ...(empty && coveredByLabel ?
1864
- [{ content: `Covered by “${coveredByLabel}”`, intent: 'success' }]
1951
+ [{ content: `Covered by “${coveredByLabel}”`, intent: calm ? undefined : 'success' }]
1865
1952
  : []),
1866
1953
  ];
1867
1954
  // A hash row reveals its sub-fields as read-only sub-rows under a "view
@@ -2207,10 +2294,28 @@ export const CompactRow = memo(
2207
2294
  covered: !!coveredByLabel || groupResolved,
2208
2295
  invalid: worstIntent === 'danger',
2209
2296
  warned: worstIntent === 'warning',
2297
+ visited,
2210
2298
  });
2211
2299
  // No dot at all in a read-only form: the dot reports progress on the
2212
2300
  // form, and a read view has no progress to report.
2213
- const dotStatus = hidden || fieldDisabled || readOnly ? undefined : rowStatus;
2301
+ //
2302
+ // Calm: no dot on a healthy row, and none on an empty required one either —
2303
+ // its value column already says "Required · add a value" in amber, and one
2304
+ // thing is said once. Only a failing value keeps the red dot beside the
2305
+ // reason it gives.
2306
+ const dotStatus =
2307
+ hidden || fieldDisabled || readOnly || (calm && rowStatus !== 'invalid') ?
2308
+ undefined
2309
+ : rowStatus;
2310
+ // Calm: an empty required value is said IN the value column, in amber,
2311
+ // where the value would be — instead of a dash plus a red reason beside it.
2312
+ // Not on a locked field (its lock and reason speak for it) or one that
2313
+ // shows the default it will fall back to.
2314
+ const calmRequiredWord =
2315
+ calm && empty && !hidden && !!schema?.required && !fieldDisabled && !formattedDefault ?
2316
+ readOnly ? CALM_REQUIRED_READ_LABEL
2317
+ : CALM_REQUIRED_LABEL
2318
+ : undefined;
2214
2319
  const dotColor =
2215
2320
  dotStatus === 'invalid' ? cDanger
2216
2321
  : dotStatus === 'todo' ? cWarning
@@ -2252,7 +2357,7 @@ export const CompactRow = memo(
2252
2357
  >
2253
2358
  {clusterNode}
2254
2359
  <StyledLabelBlock className='options-readfirst-label-block'>
2255
- <StyledRowLabel title={schema?.short_desc || undefined} $color={cKey}>
2360
+ <StyledRowLabel title={schema?.short_desc || undefined} $color={cLabel}>
2256
2361
  {rowChromeIcon}
2257
2362
  {/* label + asterisk + help flow as ONE inline run, so the asterisk
2258
2363
  stays right after the last word even when the name wraps. */}
@@ -2322,8 +2427,15 @@ export const CompactRow = memo(
2322
2427
  // actual data — crucial when stacked under a (muted) description on
2323
2428
  // mobile, where a dim value blends into the prose. The bold name still
2324
2429
  // outranks it; the empty dash stays faint.
2325
- $color={empty || hidden ? `${cMuted}66` : cKey}
2326
- $empty={empty || hidden}
2430
+ $color={
2431
+ calmRequiredWord ?
2432
+ rowStatus === 'pending' ?
2433
+ `${cMuted}a6`
2434
+ : cWarning
2435
+ : empty || hidden ? `${cMuted}66`
2436
+ : cKey
2437
+ }
2438
+ $empty={(empty || hidden) && !calmRequiredWord}
2327
2439
  >
2328
2440
  {/* A markdown value with a host renderer draws in full in the inset
2329
2441
  below, so the row prints no value line at all: the stripped-prose
@@ -2358,13 +2470,16 @@ export const CompactRow = memo(
2358
2470
  null
2359
2471
  : <span className='options-readfirst-valuetext'>
2360
2472
  {hidden || empty ?
2361
- formattedDefault || '—'
2473
+ calmRequiredWord || formattedDefault || '—'
2362
2474
  : renderReadFirstValue(optionField, schema, formatted, readOnly)}
2363
2475
  </span>}
2364
2476
  {readOnlyChoiceDesc ?
2365
2477
  <span className='options-readfirst-choice-desc'>{readOnlyChoiceDesc}</span>
2366
2478
  : null}
2367
- {valueReasons.map((m, i) => (
2479
+ {(calmRequiredWord ?
2480
+ valueReasons.filter((m) => m.content !== calmRequiredWord)
2481
+ : valueReasons
2482
+ ).map((m, i) => (
2368
2483
  <span
2369
2484
  key={`${m.content}-${i}`}
2370
2485
  className='options-readfirst-reason'
@@ -2606,7 +2721,12 @@ export const CompactRow = memo(
2606
2721
  }}
2607
2722
  />
2608
2723
  : dotColor ?
2609
- <StyledStatusDot $color={dotColor} $ring={dotStatus !== 'set'} aria-hidden />
2724
+ <StyledStatusDot
2725
+ className='options-readfirst-status-dot'
2726
+ $color={dotColor}
2727
+ $ring={dotStatus !== 'set'}
2728
+ aria-hidden
2729
+ />
2610
2730
  : null}
2611
2731
  </StyledActionSlot>
2612
2732
  </StyledRowActions>
@@ -11,9 +11,10 @@ import {
11
11
  } from '@qoretechnologies/reqore/dist/components/Dropdown/list';
12
12
  import { useReqoreTheme } from '@qoretechnologies/reqore/dist/hooks/useTheme';
13
13
  import React, { memo } from 'react';
14
- import styled from 'styled-components';
14
+ import styled, { css } from 'styled-components';
15
15
  import { useContext } from 'use-context-selector';
16
16
  import { CompactToolbarContext, TCompactSort } from './compactToolbarContext';
17
+ import { useIsCalmForm } from './formAppearance';
17
18
 
18
19
  // Field sort modes, listed in the Fields menu's "Sort by" submenu. 'schema' is
19
20
  // the default (the schema's declared order); the rest reorder within each group.
@@ -28,11 +29,21 @@ const SORT_MODES: { value: TCompactSort; label: string; tooltip: string }[] = [
28
29
  // "Focus" header: a summary line ({pct}% complete · {set}/{total} set ·
29
30
  // {attention} need attention →) above a SEGMENTED meter — a green run (set) then
30
31
  // an amber run (needs attention) then the empty remainder.
31
- const StyledCompletion = styled.div`
32
+ const StyledCompletion = styled.div<{ $calm?: boolean }>`
32
33
  display: flex;
33
34
  flex-flow: column;
34
35
  gap: 8px;
35
36
  padding: 0 2px;
37
+ /* Calm: one line — the summary, then a short meter beside it — that wraps
38
+ the meter under the words where there is no room for both. */
39
+ ${({ $calm }) =>
40
+ $calm ?
41
+ css`
42
+ flex-flow: row wrap;
43
+ align-items: center;
44
+ gap: 6px 14px;
45
+ `
46
+ : ''}
36
47
  `;
37
48
  const StyledCompletionLine = styled.div`
38
49
  display: flex;
@@ -46,11 +57,20 @@ const StyledMeter = styled.div<{
46
57
  $track: string;
47
58
  $set_c: string;
48
59
  $att_c: string;
60
+ /** Calm: a short meter beside the summary instead of a full-width bar. */
61
+ $calm?: boolean;
49
62
  }>`
50
63
  position: relative;
51
64
  height: 4px;
52
- border-radius: 3px;
65
+ border-radius: ${({ $calm }) => ($calm ? 2 : 3)}px;
53
66
  width: 100%;
67
+ ${({ $calm }) =>
68
+ $calm ?
69
+ css`
70
+ flex: 1 1 160px;
71
+ max-width: 360px;
72
+ `
73
+ : ''}
54
74
  overflow: hidden;
55
75
  background: ${({ $track }) => $track};
56
76
  /* No per-segment radius — the container's overflow:hidden + radius rounds only
@@ -113,7 +133,9 @@ export const CompactToolbar = memo((reqoreProps: Partial<IReqoreControlGroupProp
113
133
  readOnly,
114
134
  invalidCount,
115
135
  attentionCount,
136
+ attentionFaultCount,
116
137
  completion,
138
+ showInvalidOnly,
117
139
  onToggleInvalidOnly,
118
140
  hasMultipleOptions,
119
141
  compactQuery,
@@ -135,16 +157,40 @@ export const CompactToolbar = memo((reqoreProps: Partial<IReqoreControlGroupProp
135
157
  } = useContext(CompactToolbarContext);
136
158
 
137
159
  const theme = useReqoreTheme();
160
+ const calm = useIsCalmForm();
138
161
  const intents = (theme.intents || {}) as Record<string, string>;
162
+ /* "N need attention →" / "N to fill in →" is a toggle (show only those fields), so it
163
+ is one: a button a keyboard reaches, which says whether it is on. A host that turns
164
+ it on for a person (a blocked submit) reads `aria-pressed` rather than guessing,
165
+ since the toolbar draws a different link once something becomes a fault. */
166
+ const attentionToggleProps = {
167
+ className: 'options-readfirst-attention-link',
168
+ role: 'button',
169
+ tabIndex: 0,
170
+ 'aria-pressed': !!showInvalidOnly,
171
+ onClick: onToggleInvalidOnly,
172
+ onKeyDown: (event: React.KeyboardEvent) => {
173
+ if (event.key === 'Enter' || event.key === ' ') {
174
+ event.preventDefault();
175
+ onToggleInvalidOnly?.();
176
+ }
177
+ },
178
+ };
139
179
  const cSuccess = intents.success || '#4a7110';
140
180
  const cWarning = intents.warning || '#d17c29';
141
181
  const cText = (theme.text?.color as string) || '#e8e8e8';
142
182
  const cTrack = `${cText}1f`;
183
+ // Calm: what is done is grey — finished work needs nobody — and only the
184
+ // outstanding part (the amber run, the "need attention" link) is coloured.
185
+ const cDone = calm ? `${cText}8c` : cSuccess;
186
+ const cQuiet = `${cText}a6`;
143
187
  // The GREEN run is what is done, not merely what has a value: a field that is
144
188
  // set but flagged belongs to the amber run, and counting it twice pushed that
145
189
  // run off the end of the track.
146
190
  const setPct = completion.total ? (completion.done / completion.total) * 100 : 0;
147
- const attentionPct = completion.total ? (attentionCount / completion.total) * 100 : 0;
191
+ // The amber run is what is WRONG. On a new form the required fields are only still
192
+ // to fill in, so the track shows them as not done yet, without amber.
193
+ const attentionPct = completion.total ? (attentionFaultCount / completion.total) * 100 : 0;
148
194
 
149
195
  return (
150
196
  <ReqoreControlGroup
@@ -161,11 +207,19 @@ export const CompactToolbar = memo((reqoreProps: Partial<IReqoreControlGroupProp
161
207
  work. The read view lists what is set, and that is the whole
162
208
  answer. */}
163
209
  {parts.completion && completion.total && !readOnly ?
164
- <StyledCompletion className='options-readfirst-completion'>
210
+ <StyledCompletion className='options-readfirst-completion' $calm={calm}>
165
211
  <StyledCompletionLine>
166
212
  <StyledSummary
167
213
  className='options-readfirst-status'
168
- $color={invalidCount || attentionCount ? cWarning : cSuccess}
214
+ // Calm: the word in the text colour. "Ready" is not a healthy
215
+ // figure to celebrate in green, and "Incomplete" is said in amber
216
+ // by the link beside it — only the outstanding part is coloured.
217
+ $color={
218
+ calm ? cText
219
+ : invalidCount || attentionFaultCount ? cWarning
220
+ : attentionCount ? cText
221
+ : cSuccess
222
+ }
169
223
  style={{ fontWeight: 600 }}
170
224
  >
171
225
  {/* "Ready" is a claim about the whole form, so anything the form
@@ -179,28 +233,43 @@ export const CompactToolbar = memo((reqoreProps: Partial<IReqoreControlGroupProp
179
233
  batched mode's per-row chip alone. */}
180
234
  {invalidCount || attentionCount ? 'Incomplete' : 'Ready'}
181
235
  </StyledSummary>
182
- <StyledSummary style={{ opacity: 0.5 }}>
236
+ <StyledSummary style={calm ? { color: cQuiet } : { opacity: 0.5 }}>
183
237
  · {completion.set}/{completion.total} set
184
238
  {attentionCount ? ' ·' : ''}
185
239
  </StyledSummary>
186
- {attentionCount ?
187
- <StyledAttentionLink
188
- $color={cWarning}
189
- className='options-readfirst-attention-link'
190
- onClick={onToggleInvalidOnly}
191
- >
192
- {attentionCount} need attention →
240
+ {/* Amber only for what is a fault. A new form's required fields are still to
241
+ fill in, which is the next step, not a warning: they are counted apart,
242
+ in the quiet colour. */}
243
+ {attentionFaultCount ?
244
+ <StyledAttentionLink $color={cWarning} {...attentionToggleProps}>
245
+ {attentionFaultCount} need attention →
193
246
  </StyledAttentionLink>
194
247
  : null}
195
- <StyledPct>{completion.pct}%</StyledPct>
248
+ {attentionCount > attentionFaultCount ?
249
+ attentionFaultCount ?
250
+ <StyledSummary style={{ color: cQuiet }}>
251
+ · {attentionCount - attentionFaultCount} to fill in
252
+ </StyledSummary>
253
+ : <StyledAttentionLink $color={cQuiet} {...attentionToggleProps}>
254
+ {attentionCount} to fill in →
255
+ </StyledAttentionLink>
256
+ : null}
257
+ {/* Calm: the percentage is one more quiet figure on the line, not
258
+ a large bold number pinned to the far edge. */}
259
+ {calm ?
260
+ <StyledSummary className='options-readfirst-pct' style={{ color: cQuiet }}>
261
+ · {completion.pct}%
262
+ </StyledSummary>
263
+ : <StyledPct className='options-readfirst-pct'>{completion.pct}%</StyledPct>}
196
264
  </StyledCompletionLine>
197
265
  <StyledMeter
198
266
  className='options-readfirst-completion-bar'
199
267
  $set={setPct}
200
268
  $attention={attentionPct}
201
269
  $track={cTrack}
202
- $set_c={cSuccess}
270
+ $set_c={cDone}
203
271
  $att_c={cWarning}
272
+ $calm={calm}
204
273
  />
205
274
  </StyledCompletion>
206
275
  : null}