@qoretechnologies/reqraft 0.10.46-pr.95.ge595a13 → 0.10.46
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.
- package/.claude/CLAUDE.md +12 -0
- package/.tasks/INDEX.md +1 -0
- package/.tasks/TEMPLATE_TOKEN_GRAMMAR.md +72 -0
- package/dist/components/composer/Composer.d.ts.map +1 -1
- package/dist/components/composer/Composer.js +5 -1
- package/dist/components/composer/Composer.js.map +1 -1
- package/dist/components/form/engine/CompactRow.d.ts +14 -0
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +199 -41
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
- package/dist/components/form/engine/CompactToolbar.js +72 -72
- package/dist/components/form/engine/CompactToolbar.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts +65 -1
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +276 -108
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.js +55 -13
- package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
- package/dist/components/form/engine/_structuredData/structuredData.d.ts +15 -0
- package/dist/components/form/engine/_structuredData/structuredData.d.ts.map +1 -1
- package/dist/components/form/engine/_structuredData/structuredData.js +22 -1
- package/dist/components/form/engine/_structuredData/structuredData.js.map +1 -1
- package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowStyles.js +15 -3
- package/dist/components/form/engine/compactRowStyles.js.map +1 -1
- package/dist/components/form/engine/compactToolbarContext.d.ts +8 -0
- package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
- package/dist/components/form/engine/compactToolbarContext.js.map +1 -1
- package/dist/components/form/engine/readFirst.d.ts +24 -0
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +84 -6
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/fields/Field.d.ts.map +1 -1
- package/dist/components/form/fields/Field.js +1 -1
- package/dist/components/form/fields/Field.js.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +12 -2
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.js +16 -6
- package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.js +64 -14
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/dist/components/form/fields/long-string/LongString.d.ts +9 -1
- package/dist/components/form/fields/long-string/LongString.d.ts.map +1 -1
- package/dist/components/form/fields/long-string/LongString.js +25 -4
- package/dist/components/form/fields/long-string/LongString.js.map +1 -1
- package/dist/components/form/fields/template/ExampleValueModal.d.ts +29 -0
- package/dist/components/form/fields/template/ExampleValueModal.d.ts.map +1 -0
- package/dist/components/form/fields/template/ExampleValueModal.js +122 -0
- package/dist/components/form/fields/template/ExampleValueModal.js.map +1 -0
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts.map +1 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.js +3 -3
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.js.map +1 -1
- package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
- package/dist/components/form/fields/template/TemplateField.js +81 -27
- package/dist/components/form/fields/template/TemplateField.js.map +1 -1
- package/dist/components/form/fields/template/__fixtures__/longExampleValue.d.ts +2 -0
- package/dist/components/form/fields/template/__fixtures__/longExampleValue.d.ts.map +1 -0
- package/dist/components/form/fields/template/__fixtures__/longExampleValue.js +9 -0
- package/dist/components/form/fields/template/__fixtures__/longExampleValue.js.map +1 -0
- package/dist/components/form/index.d.ts +1 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +1 -0
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/searchFilterBar/SearchFilterBar.d.ts +12 -1
- package/dist/components/searchFilterBar/SearchFilterBar.d.ts.map +1 -1
- package/dist/components/searchFilterBar/SearchFilterBar.js +2 -2
- package/dist/components/searchFilterBar/SearchFilterBar.js.map +1 -1
- package/dist/components/ticketThread/TicketThread.d.ts.map +1 -1
- package/dist/components/ticketThread/TicketThread.js +1 -1
- package/dist/components/ticketThread/TicketThread.js.map +1 -1
- package/dist/helpers/common.d.ts +8 -1
- package/dist/helpers/common.d.ts.map +1 -1
- package/dist/helpers/common.js +9 -1
- package/dist/helpers/common.js.map +1 -1
- package/dist/helpers/expressions.d.ts.map +1 -1
- package/dist/helpers/expressions.js +15 -2
- package/dist/helpers/expressions.js.map +1 -1
- package/dist/helpers/optionUiTypes.d.ts +30 -0
- package/dist/helpers/optionUiTypes.d.ts.map +1 -0
- package/dist/helpers/optionUiTypes.js +156 -0
- package/dist/helpers/optionUiTypes.js.map +1 -0
- package/dist/helpers/options.d.ts +19 -0
- package/dist/helpers/options.d.ts.map +1 -1
- package/dist/helpers/options.js +25 -1
- package/dist/helpers/options.js.map +1 -1
- package/dist/helpers/providerValue.d.ts +68 -0
- package/dist/helpers/providerValue.d.ts.map +1 -0
- package/dist/helpers/providerValue.js +50 -0
- package/dist/helpers/providerValue.js.map +1 -0
- package/dist/helpers/singleLineString.d.ts +37 -0
- package/dist/helpers/singleLineString.d.ts.map +1 -0
- package/dist/helpers/singleLineString.js +66 -0
- package/dist/helpers/singleLineString.js.map +1 -0
- package/dist/helpers/templates.d.ts +100 -0
- package/dist/helpers/templates.d.ts.map +1 -1
- package/dist/helpers/templates.js +237 -4
- package/dist/helpers/templates.js.map +1 -1
- package/dist/helpers/validations.d.ts +103 -2
- package/dist/helpers/validations.d.ts.map +1 -1
- package/dist/helpers/validations.js +858 -91
- package/dist/helpers/validations.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/composer/Composer.tsx +7 -3
- package/src/components/form/engine/CompactRow.tsx +279 -33
- package/src/components/form/engine/CompactToolbar.tsx +105 -98
- package/src/components/form/engine/FormEngine.stories.tsx +995 -12
- package/src/components/form/engine/FormEngine.tsx +377 -94
- package/src/components/form/engine/_structuredData/SchemaDataView.stories.tsx +104 -0
- package/src/components/form/engine/_structuredData/SchemaDataView.tsx +158 -101
- package/src/components/form/engine/_structuredData/structuredData.ts +20 -0
- package/src/components/form/engine/compactRowStyles.ts +55 -1
- package/src/components/form/engine/compactToolbarContext.ts +8 -0
- package/src/components/form/engine/readFirst.ts +125 -35
- package/src/components/form/fields/Field.tsx +3 -1
- package/src/components/form/fields/allowed-values/AllowedValues.tsx +24 -2
- package/src/components/form/fields/array/ArrayAutoField.stories.tsx +81 -0
- package/src/components/form/fields/auto/AutoFormField.stories.tsx +47 -1
- package/src/components/form/fields/auto/AutoFormField.tsx +73 -11
- package/src/components/form/fields/long-string/LongString.tsx +46 -5
- package/src/components/form/fields/schema-definition/SchemaDefinitionField.stories.tsx +5 -0
- package/src/components/form/fields/template/ExampleValueModal.stories.tsx +114 -0
- package/src/components/form/fields/template/ExampleValueModal.tsx +99 -0
- package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +8 -4
- package/src/components/form/fields/template/TemplateField.stories.tsx +389 -1
- package/src/components/form/fields/template/TemplateField.tsx +100 -35
- package/src/components/form/fields/template/__fixtures__/longExampleValue.ts +7 -0
- package/src/components/form/index.tsx +1 -0
- package/src/components/searchFilterBar/SearchFilterBar.stories.tsx +62 -0
- package/src/components/searchFilterBar/SearchFilterBar.tsx +13 -0
- package/src/components/ticketThread/TicketThread.tsx +7 -0
- package/src/helpers/common.ts +11 -1
- package/src/helpers/expressions.ts +16 -2
- package/src/helpers/optionUiTypes.ts +161 -0
- package/src/helpers/options.ts +23 -0
- package/src/helpers/providerValue.ts +125 -0
- package/src/helpers/singleLineString.ts +61 -0
- package/src/helpers/templates.ts +272 -3
- package/src/helpers/validations.ts +1177 -109
- package/src/hooks/useWebSocket/useWebsocket.stories.tsx +23 -2
- package/src/index.tsx +1 -0
|
@@ -23,16 +23,18 @@ import isEqual from 'lodash/isEqual';
|
|
|
23
23
|
import size from 'lodash/size';
|
|
24
24
|
import React, { memo } from 'react';
|
|
25
25
|
import { useContextSelector } from 'use-context-selector';
|
|
26
|
-
import { hasAllDependenciesFullfilled } from '../../../helpers/validations';
|
|
26
|
+
import { hasAllDependenciesFullfilled, parseDependency } from '../../../helpers/validations';
|
|
27
27
|
import {
|
|
28
28
|
findTemplate,
|
|
29
29
|
getTemplateTagStyle,
|
|
30
30
|
isValueTemplate,
|
|
31
|
+
describeTemplateReference,
|
|
32
|
+
splitTemplateTokens,
|
|
31
33
|
TTemplateMeta,
|
|
32
34
|
} from '../../../helpers/templates';
|
|
33
|
-
import { richtextToSegments } from '../../../helpers/common';
|
|
35
|
+
import { getDefaultValue, richtextToSegments, richtextToString } from '../../../helpers/common';
|
|
34
36
|
import { ReadOnlyTemplateTag } from '../fields/template/ReadOnlyTemplateTag';
|
|
35
|
-
import {
|
|
37
|
+
import { describeCodeSize, formatCodeChars, formatCodeLines } from '../../codeSize';
|
|
36
38
|
import { Description } from '../../Description';
|
|
37
39
|
import { useMarkdownRenderer } from '../../Description/markdownRendererContext';
|
|
38
40
|
import { FocusedEditing } from '../../FocusedEditing';
|
|
@@ -47,7 +49,6 @@ import {
|
|
|
47
49
|
StyledEditCard,
|
|
48
50
|
StyledLabelBlock,
|
|
49
51
|
StyledAbsorbedField,
|
|
50
|
-
StyledAbsorbedLabel,
|
|
51
52
|
StyledLabelDesc,
|
|
52
53
|
StyledRowActions,
|
|
53
54
|
StyledRowInset,
|
|
@@ -138,6 +139,26 @@ const MAX_INLINE_OPTION_ACTIONS = 2;
|
|
|
138
139
|
// One read-first row: label | value | action collapsed; the real editor (the
|
|
139
140
|
// classic renderOption) expanded. `hidden` = search-surfaced optional —
|
|
140
141
|
// activating the row adds the field first.
|
|
142
|
+
/**
|
|
143
|
+
* The text inside a rich-text envelope, when a list item is one.
|
|
144
|
+
*
|
|
145
|
+
* An option like Gmail's `to` is `type: "list"`, `element_type: "string"`,
|
|
146
|
+
* `supports_templates: true`: each element is a plain string that may carry
|
|
147
|
+
* template tags, so its editor is a rich-text one and the element value arrives
|
|
148
|
+
* wrapped as `{type: "richtext", value: [{type: "paragraph", …}]}`.
|
|
149
|
+
*
|
|
150
|
+
* That envelope is a string in a coat, not structured data. The hash-list test
|
|
151
|
+
* unwraps `item.value`, found the Slate document — an object — and classed a
|
|
152
|
+
* list of email addresses as a list of hashes, so the row drew an expandable
|
|
153
|
+
* `type / children / text` tree instead of the address the operator typed.
|
|
154
|
+
*/
|
|
155
|
+
export const richtextItemText = (item: unknown): string | undefined =>
|
|
156
|
+
item && typeof item === 'object' && (item as { type?: unknown }).type === 'richtext' ?
|
|
157
|
+
richtextToString(
|
|
158
|
+
(item as { value?: Parameters<typeof richtextToString>[0] }).value
|
|
159
|
+
)
|
|
160
|
+
: undefined;
|
|
161
|
+
|
|
141
162
|
export const CompactRow = memo(
|
|
142
163
|
({
|
|
143
164
|
optionName,
|
|
@@ -235,6 +256,27 @@ export const CompactRow = memo(
|
|
|
235
256
|
|
|
236
257
|
// Value-cell content: colour adds a swatch, file an icon + size; hash keeps
|
|
237
258
|
// its "N fields" summary (sub-fields reveal beneath the row).
|
|
259
|
+
/**
|
|
260
|
+
* Flattens the line breaks in one prose segment of a richtext SUMMARY.
|
|
261
|
+
*
|
|
262
|
+
* The read-first row is a single line: every other value type reaches it
|
|
263
|
+
* through `whiteSpace: 'nowrap'`, which collapses newlines for free. The
|
|
264
|
+
* richtext branch is the only one that opts into `'pre'` — it has to, or the
|
|
265
|
+
* spaces that separate a word from the chip beside it are dropped — and `pre`
|
|
266
|
+
* also honours the newlines, which `nowrap` would have eaten.
|
|
267
|
+
*
|
|
268
|
+
* So a genuinely multi-line value (an alert rule's Gmail message body is five
|
|
269
|
+
* `\n`-separated lines) gave each segment after the first a blank first line.
|
|
270
|
+
* The wrapper centres its items, so a two-line-tall box centred against
|
|
271
|
+
* one-line chips put every word 12px below the chip beside it, and the row
|
|
272
|
+
* read as a staircase. Measured on supah: prose boxes 30px against 14px chips.
|
|
273
|
+
*
|
|
274
|
+
* A space, not nothing: consecutive prose segments are merged before they get
|
|
275
|
+
* here, so a value with a break and no chip between its lines would otherwise
|
|
276
|
+
* lose the word boundary entirely.
|
|
277
|
+
*/
|
|
278
|
+
const collapseSummaryBreaks = (text: string): string => text.replace(/\s*\r?\n\s*/g, ' ');
|
|
279
|
+
|
|
238
280
|
const renderReadFirstValue = (
|
|
239
281
|
field: IQorusFormField,
|
|
240
282
|
schema: TQorusFormFieldSchema | undefined,
|
|
@@ -336,7 +378,6 @@ export const CompactRow = memo(
|
|
|
336
378
|
/>
|
|
337
379
|
);
|
|
338
380
|
})}
|
|
339
|
-
<ReqraftCodeSizeTag code={field.value} />
|
|
340
381
|
</ReqoreControlGroup>
|
|
341
382
|
);
|
|
342
383
|
}
|
|
@@ -385,6 +426,15 @@ export const CompactRow = memo(
|
|
|
385
426
|
return <ReadOnlyTemplateTag value={field.value} templates={templates} size='small' />;
|
|
386
427
|
}
|
|
387
428
|
|
|
429
|
+
// A LIST whose elements are rich-text envelopes is a list of strings, so it
|
|
430
|
+
// reads as those strings. Without this the row fell through to the generic
|
|
431
|
+
// list summary and printed a bare count ("1 item") above the structured
|
|
432
|
+
// tree, never the address itself.
|
|
433
|
+
if (Array.isArray(field?.value) && field.value.some((item) => richtextItemText(item) !== undefined)) {
|
|
434
|
+
const texts = field.value.map((item) => richtextItemText(item) ?? String(item ?? ''));
|
|
435
|
+
return <span style={textStyle}>{texts.filter((text) => text !== '').join(', ')}</span>;
|
|
436
|
+
}
|
|
437
|
+
|
|
388
438
|
// Richtext read-first summary: inline $-chips for embedded template tags
|
|
389
439
|
// with the prose around them (the IDE intent scheme via getTemplateTagStyle),
|
|
390
440
|
// kept lightweight instead of mounting the full editor — that lives in the
|
|
@@ -415,6 +465,49 @@ export const CompactRow = memo(
|
|
|
415
465
|
?.metadata as TTemplateMeta | undefined
|
|
416
466
|
)}
|
|
417
467
|
/>
|
|
468
|
+
: <span key={index} style={{ whiteSpace: 'pre' }}>
|
|
469
|
+
{collapseSummaryBreaks(segment.text)}
|
|
470
|
+
</span>
|
|
471
|
+
)}
|
|
472
|
+
</span>
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// An expression summarises to one line of text, and a template reference
|
|
478
|
+
// inside it printed as its own raw token — `$data:{…}` renders a value as
|
|
479
|
+
// code where a name belongs. Chip every reference here, on the richtext
|
|
480
|
+
// branch's pattern above, so the wrapper (`trim(`…`)`) still reads as the
|
|
481
|
+
// text it is while the reference reads as a name.
|
|
482
|
+
//
|
|
483
|
+
// Unconditionally: a surface with no catalogue (the Automation Hub
|
|
484
|
+
// template preview never fetches one) still shows the reference's own
|
|
485
|
+
// path, which is the point — a conditional chip is no chip at all
|
|
486
|
+
// exactly where the raw token looks worst.
|
|
487
|
+
if ((field as { is_expression?: boolean })?.is_expression && formatted) {
|
|
488
|
+
const segments = splitTemplateTokens(formatted);
|
|
489
|
+
const named = segments.map((segment) =>
|
|
490
|
+
segment.kind === 'token' ?
|
|
491
|
+
{ ...segment, ...describeTemplateReference(templates, segment.text) }
|
|
492
|
+
: segment
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
if (named.some((segment) => segment.kind === 'token')) {
|
|
496
|
+
return (
|
|
497
|
+
<span style={{ ...wrapStyle, gap: 4, overflow: 'hidden' }}>
|
|
498
|
+
{named.map((segment, index) =>
|
|
499
|
+
segment.kind === 'token' ?
|
|
500
|
+
<ReqoreTag
|
|
501
|
+
key={index}
|
|
502
|
+
size='tiny'
|
|
503
|
+
// Inherit the row's font size so the chip shares a baseline
|
|
504
|
+
// with the text around it — see the richtext branch above.
|
|
505
|
+
style={{ fontSize: 'inherit' }}
|
|
506
|
+
icon='ExchangeDollarLine'
|
|
507
|
+
label={segment.label}
|
|
508
|
+
tooltip={segment.text}
|
|
509
|
+
{...getTemplateTagStyle(segment.item?.metadata as TTemplateMeta | undefined)}
|
|
510
|
+
/>
|
|
418
511
|
: <span key={index} style={{ whiteSpace: 'pre' }}>
|
|
419
512
|
{segment.text}
|
|
420
513
|
</span>
|
|
@@ -833,6 +926,7 @@ export const CompactRow = memo(
|
|
|
833
926
|
className='options-readfirst-cancel'
|
|
834
927
|
size='small'
|
|
835
928
|
flat
|
|
929
|
+
minimal
|
|
836
930
|
fixed
|
|
837
931
|
icon='CloseLine'
|
|
838
932
|
intent='warning'
|
|
@@ -950,6 +1044,11 @@ export const CompactRow = memo(
|
|
|
950
1044
|
/>
|
|
951
1045
|
: null;
|
|
952
1046
|
|
|
1047
|
+
// The gap rides on the message itself rather than on the panel around it:
|
|
1048
|
+
// the panel is a vertical ReqoreControlGroup, which owns its own layout and
|
|
1049
|
+
// stacks children flush, and the class rule that used to carry a 4px gap
|
|
1050
|
+
// never reached this element at all — which is why two messages read as one
|
|
1051
|
+
// two-tone block with no space between them.
|
|
953
1052
|
const renderInfoStrip = (m: TInfoMsg, index: number) => (
|
|
954
1053
|
<ReqoreMessage
|
|
955
1054
|
key={`${m.content}-${index}`}
|
|
@@ -958,6 +1057,7 @@ export const CompactRow = memo(
|
|
|
958
1057
|
flat
|
|
959
1058
|
intent={m.intent as never}
|
|
960
1059
|
title={m.title}
|
|
1060
|
+
style={{ marginBottom: 8 }}
|
|
961
1061
|
>
|
|
962
1062
|
{m.content}
|
|
963
1063
|
</ReqoreMessage>
|
|
@@ -1050,10 +1150,10 @@ export const CompactRow = memo(
|
|
|
1050
1150
|
const absorbedField = availableOptions?.[absorbedName] as IQorusFormField | undefined;
|
|
1051
1151
|
if (!absorbedField) return null;
|
|
1052
1152
|
return (
|
|
1153
|
+
// No label element of its own: the editor now heads itself with
|
|
1154
|
+
// the field's name (see FieldAllowedValuesCheckGroup), so a label
|
|
1155
|
+
// beside it printed the same word twice.
|
|
1053
1156
|
<StyledAbsorbedField key={absorbedName} className='options-readfirst-absorbed'>
|
|
1054
|
-
<StyledAbsorbedLabel size='small' effect={{ opacity: 0.6 }}>
|
|
1055
|
-
{getAbsorbedLabel(absorbedName)}
|
|
1056
|
-
</StyledAbsorbedLabel>
|
|
1057
1157
|
<div style={{ minWidth: 0, flex: 1 }}>
|
|
1058
1158
|
{renderOption(absorbedName, absorbedField, 'small', true)}
|
|
1059
1159
|
</div>
|
|
@@ -1068,6 +1168,36 @@ export const CompactRow = memo(
|
|
|
1068
1168
|
// holding code.
|
|
1069
1169
|
const valueType = getValueType(optionField, schema);
|
|
1070
1170
|
|
|
1171
|
+
// How much source there is describes the FIELD, not its value, so the count
|
|
1172
|
+
// sits under the field's name — the same rule `lineCountNote` already
|
|
1173
|
+
// follows for a multi-line markdown value, and the same reason: the value
|
|
1174
|
+
// column carries the value and nothing else. It renders on the read row and
|
|
1175
|
+
// the open one alike, because "how much code is here?" is a question that
|
|
1176
|
+
// outlives opening the editor.
|
|
1177
|
+
//
|
|
1178
|
+
// It is a plain monospaced note rather than a tag, and deliberately the same
|
|
1179
|
+
// one `lineCountNote` renders: both answer "how big is this field's value?"
|
|
1180
|
+
// in the same corner of the same column, so stating one as a chip and the
|
|
1181
|
+
// other as text made two spellings of one idea.
|
|
1182
|
+
const codeSizeNode =
|
|
1183
|
+
valueType === 'code-editor' &&
|
|
1184
|
+
typeof optionField?.value === 'string' &&
|
|
1185
|
+
optionField.value !== '' ?
|
|
1186
|
+
(() => {
|
|
1187
|
+
const { lines, chars } = describeCodeSize(optionField.value as string);
|
|
1188
|
+
return (
|
|
1189
|
+
<ReqoreP
|
|
1190
|
+
className='options-readfirst-label-code-size'
|
|
1191
|
+
size='tiny'
|
|
1192
|
+
effect={{ opacity: 0.45, textAlign: 'left' }}
|
|
1193
|
+
style={{ fontFamily: 'monospace' }}
|
|
1194
|
+
>
|
|
1195
|
+
{`${formatCodeChars(chars)} ${formatCodeLines(lines)}`}
|
|
1196
|
+
</ReqoreP>
|
|
1197
|
+
);
|
|
1198
|
+
})()
|
|
1199
|
+
: null;
|
|
1200
|
+
|
|
1071
1201
|
if (isExpanded) {
|
|
1072
1202
|
if (inlineEditable) {
|
|
1073
1203
|
const collapse = () => toggleExpandedOption(optionName);
|
|
@@ -1083,7 +1213,7 @@ export const CompactRow = memo(
|
|
|
1083
1213
|
}
|
|
1084
1214
|
{...clusterHoverProps}
|
|
1085
1215
|
>
|
|
1086
|
-
<StyledLabelBlock>
|
|
1216
|
+
<StyledLabelBlock className='options-readfirst-label-block'>
|
|
1087
1217
|
<StyledRowLabel
|
|
1088
1218
|
role='button'
|
|
1089
1219
|
tabIndex={0}
|
|
@@ -1118,6 +1248,7 @@ export const CompactRow = memo(
|
|
|
1118
1248
|
{labelShortDesc}
|
|
1119
1249
|
</StyledLabelDesc>
|
|
1120
1250
|
: null}
|
|
1251
|
+
{codeSizeNode}
|
|
1121
1252
|
</StyledLabelBlock>
|
|
1122
1253
|
<div
|
|
1123
1254
|
ref={editorRef}
|
|
@@ -1145,15 +1276,32 @@ export const CompactRow = memo(
|
|
|
1145
1276
|
|
|
1146
1277
|
It renders here, at the top of the value cell, which is exactly where
|
|
1147
1278
|
it sits on the read row — so the switch moves nothing. */}
|
|
1279
|
+
{/* The field's own schema messages follow it into the editor.
|
|
1280
|
+
|
|
1281
|
+
A message is guidance about the value, so the moment it matters
|
|
1282
|
+
most is while the value is being changed — and a diagnostic is
|
|
1283
|
+
useless anywhere else: it names a line the author can only act
|
|
1284
|
+
on with the editor in front of them. They used to render on the
|
|
1285
|
+
READ row alone, so opening the field to fix what one said was
|
|
1286
|
+
what made it disappear. Worse for a host that opens a field on
|
|
1287
|
+
arrival: the message was never visible at all.
|
|
1288
|
+
|
|
1289
|
+
Above the editor rather than below it, and above the absorbed
|
|
1290
|
+
siblings, because it is the reason the author opened the row. */}
|
|
1291
|
+
{panelMessages.length ?
|
|
1292
|
+
<ReqoreControlGroup
|
|
1293
|
+
className='options-readfirst-info-panel'
|
|
1294
|
+
vertical
|
|
1295
|
+
fluid
|
|
1296
|
+
gapSize='normal'
|
|
1297
|
+
>
|
|
1298
|
+
{panelMessages.map(renderInfoStrip)}
|
|
1299
|
+
</ReqoreControlGroup>
|
|
1300
|
+
: null}
|
|
1148
1301
|
{/* Absorbed siblings sit at the TOP of the value cell, above the
|
|
1149
1302
|
editor they belong to — the language you are writing in is
|
|
1150
1303
|
read before the code, not after it. */}
|
|
1151
1304
|
{absorbedNodes}
|
|
1152
|
-
{valueType === 'code-editor' && typeof optionField?.value === 'string' ?
|
|
1153
|
-
<div className='options-readfirst-editing-summary'>
|
|
1154
|
-
<ReqraftCodeSizeTag code={optionField.value} />
|
|
1155
|
-
</div>
|
|
1156
|
-
: null}
|
|
1157
1305
|
{renderOption(optionName, optionField, 'small', true)}
|
|
1158
1306
|
</div>
|
|
1159
1307
|
<StyledRowActions>
|
|
@@ -1330,6 +1478,18 @@ export const CompactRow = memo(
|
|
|
1330
1478
|
{inlineOptionActions.map((action, index) =>
|
|
1331
1479
|
renderInjectedOptionAction(action, index)
|
|
1332
1480
|
)}
|
|
1481
|
+
{/* Revert and Cancel, in the same order and with the same meaning
|
|
1482
|
+
as the inline row's strip.
|
|
1483
|
+
|
|
1484
|
+
They were absent here, and which shape a field opens into is
|
|
1485
|
+
decided by its TYPE — so an `any`-typed option (a test
|
|
1486
|
+
assertion's Expected Value, a Qog action state option) opened
|
|
1487
|
+
into this card and had no way out that discarded: Done
|
|
1488
|
+
committed, clicking away committed, and Escape did nothing at
|
|
1489
|
+
all because the handler is on the inline row's editor. The
|
|
1490
|
+
only exit from a card kept whatever had been typed. */}
|
|
1491
|
+
{editRowRevertButton}
|
|
1492
|
+
{cancelEditButton}
|
|
1333
1493
|
{/* Clear-value sits before the More menu — the card analog of the
|
|
1334
1494
|
inline row's Clear. Empties the value (keeps the field). */}
|
|
1335
1495
|
{hasValue && !readOnly ?
|
|
@@ -1354,6 +1514,20 @@ export const CompactRow = memo(
|
|
|
1354
1514
|
not only the inline row — which shape a field opens into depends
|
|
1355
1515
|
on its type, and absorbing must not depend on that. */}
|
|
1356
1516
|
{absorbedNodes}
|
|
1517
|
+
{/* Escape discards here for the same reason it does on the inline
|
|
1518
|
+
row: it is the one keystroke every editor treats as "get me out
|
|
1519
|
+
of this without saving", and a card that ignored it made the
|
|
1520
|
+
keystroke a silent commit. Same handler, same `cancelEdit()`, so
|
|
1521
|
+
the two shapes cannot diverge. */}
|
|
1522
|
+
<div
|
|
1523
|
+
style={{ display: 'contents' }}
|
|
1524
|
+
onKeyDown={(event) => {
|
|
1525
|
+
if (event.key === 'Escape') {
|
|
1526
|
+
event.stopPropagation();
|
|
1527
|
+
cancelEdit();
|
|
1528
|
+
}
|
|
1529
|
+
}}
|
|
1530
|
+
>
|
|
1357
1531
|
{/* Same fullscreen focused-editing affordance as the classic cards —
|
|
1358
1532
|
the modal mounts when this option is focused. */}
|
|
1359
1533
|
<FocusedEditing
|
|
@@ -1370,12 +1544,33 @@ export const CompactRow = memo(
|
|
|
1370
1544
|
: null}
|
|
1371
1545
|
{renderOption(optionName, optionField)}
|
|
1372
1546
|
</FocusedEditing>
|
|
1547
|
+
</div>
|
|
1373
1548
|
</StyledEditCard>
|
|
1374
1549
|
);
|
|
1375
1550
|
}
|
|
1376
1551
|
|
|
1377
1552
|
const formatted = formatOptionValue(optionField, schema);
|
|
1378
1553
|
const empty = formatted === '';
|
|
1554
|
+
// A field with no value of its own but a declared default is not simply
|
|
1555
|
+
// unanswered: the default is what the server will use, and an em-dash says
|
|
1556
|
+
// the opposite. Render the default in its place, through the same formatter
|
|
1557
|
+
// so a list or an enum reads the way a real value would.
|
|
1558
|
+
//
|
|
1559
|
+
// This does NOT make the field count as answered. `empty` stays true, so the
|
|
1560
|
+
// row keeps the dimmed italic treatment that distinguishes a default from an
|
|
1561
|
+
// answer, and bucketing — which reads the field's own value, not this — still
|
|
1562
|
+
// files it under Optional and leaves it behind the collapse.
|
|
1563
|
+
//
|
|
1564
|
+
// `hidden` is included deliberately: it marks a field the form has not added
|
|
1565
|
+
// yet, which is the case that benefits most — the row is the author's first
|
|
1566
|
+
// sight of the field, and the default is what it will contribute if left
|
|
1567
|
+
// alone. Both flags only ever route to the em-dash below, so there is no
|
|
1568
|
+
// real value being displaced.
|
|
1569
|
+
const defaultValue = hidden || empty ? getDefaultValue(schema) : undefined;
|
|
1570
|
+
const formattedDefault =
|
|
1571
|
+
defaultValue === undefined || defaultValue === null ?
|
|
1572
|
+
''
|
|
1573
|
+
: formatOptionValue({ ...optionField, value: defaultValue }, schema);
|
|
1379
1574
|
// Inline reasons shown on the value line: validation / dependency / one-of /
|
|
1380
1575
|
// default-value hints, plus a read-only covered-by note (editable covered rows
|
|
1381
1576
|
// carry that in their chip instead).
|
|
@@ -1405,6 +1600,9 @@ export const CompactRow = memo(
|
|
|
1405
1600
|
Array.isArray(optionField?.value) &&
|
|
1406
1601
|
optionField.value.length > 0 &&
|
|
1407
1602
|
optionField.value.some((item) => {
|
|
1603
|
+
if (richtextItemText(item) !== undefined) {
|
|
1604
|
+
return false;
|
|
1605
|
+
}
|
|
1408
1606
|
const inner =
|
|
1409
1607
|
item && typeof item === 'object' && 'value' in (item as Record<string, unknown>) ?
|
|
1410
1608
|
(item as Record<string, unknown>).value
|
|
@@ -1422,6 +1620,19 @@ export const CompactRow = memo(
|
|
|
1422
1620
|
valueType === 'code-editor' &&
|
|
1423
1621
|
typeof optionField?.value === 'string' &&
|
|
1424
1622
|
optionField.value.length > 0;
|
|
1623
|
+
// A richtext value that renders as CHIPS is drawn in full by the row itself:
|
|
1624
|
+
// the chips are the value, and the reader is looking straight at it. The
|
|
1625
|
+
// native `title` below would then hover the raw source of the very thing the
|
|
1626
|
+
// chips replace ("[$fsminput:severity] $fsminput:alert_code") — and, because
|
|
1627
|
+
// each chip carries its own Reqore tooltip, hovering one fires BOTH: the chip
|
|
1628
|
+
// popover and the row's browser tooltip, in different places, saying almost
|
|
1629
|
+
// the same thing. Only the popover shows up in a screenshot, which is what
|
|
1630
|
+
// made the pair hard to report.
|
|
1631
|
+
const showsTemplateChips =
|
|
1632
|
+
!hidden &&
|
|
1633
|
+
valueType === 'richtext' &&
|
|
1634
|
+
Array.isArray(optionField?.value) &&
|
|
1635
|
+
richtextToSegments(optionField.value as never).some((segment) => segment.kind === 'tag');
|
|
1425
1636
|
// Markdown reads the same way for the same reason: the row itself can only
|
|
1426
1637
|
// show a line of text, and for markdown that line is the SOURCE — the reader
|
|
1427
1638
|
// gets `## ` and `**` where the point of the value is what it looks like
|
|
@@ -1458,35 +1669,45 @@ export const CompactRow = memo(
|
|
|
1458
1669
|
showFieldTypes ?
|
|
1459
1670
|
`<${(schema?.ui_type as string) || (schema?.type as string) || 'auto'}${(schema as { ui_element_type?: string } | undefined)?.ui_element_type ? `[${(schema as { ui_element_type?: string }).ui_element_type}]` : ''}>`
|
|
1460
1671
|
: null;
|
|
1461
|
-
//
|
|
1462
|
-
//
|
|
1672
|
+
// A `readonly` field is one the system owns — an assigned id, a recorded
|
|
1673
|
+
// provenance. The server states that on the field itself; only the older
|
|
1674
|
+
// compat projection also mapped it to `disabled`, so on this path the form
|
|
1675
|
+
// was handed `readonly` alone and rendered an editable control for a value
|
|
1676
|
+
// no answer of the user's can change.
|
|
1677
|
+
const fieldReadonly = !!(schema as { readonly?: boolean } | undefined)?.readonly;
|
|
1678
|
+
// Disabled rows (schema flag, readonly, or unmet deps) can't open — a lock +
|
|
1679
|
+
// reason renders instead. Form-level readOnly still opens in view mode (Close).
|
|
1463
1680
|
const fieldDisabled =
|
|
1464
1681
|
!hidden &&
|
|
1465
1682
|
!readOnly &&
|
|
1466
1683
|
(!!schema?.disabled ||
|
|
1684
|
+
fieldReadonly ||
|
|
1467
1685
|
!hasAllDependenciesFullfilled(schema?.depends_on, availableOptions, options || {}));
|
|
1468
1686
|
const fieldDisabledReason =
|
|
1469
|
-
schema?.disabled ? 'This field is disabled'
|
|
1687
|
+
schema?.disabled ? 'This field is disabled'
|
|
1688
|
+
: fieldReadonly ? 'Set by Qorus — not editable'
|
|
1689
|
+
: 'Disabled — dependencies are not fulfilled';
|
|
1470
1690
|
// Dependency contract: top-level entries must ALL hold; a nested array
|
|
1471
|
-
// means ANY of its entries; `name=value` requires that exact value
|
|
1691
|
+
// means ANY of its entries; `name=value` requires that exact value, and
|
|
1692
|
+
// `name!=value` requires any other answer. Parsed by the same helper the
|
|
1693
|
+
// predicate uses, so the lock cannot describe a rule the form does not apply.
|
|
1472
1694
|
const dependencyEntries =
|
|
1473
|
-
fieldDisabled && !schema?.disabled ?
|
|
1695
|
+
fieldDisabled && !schema?.disabled && !fieldReadonly ?
|
|
1474
1696
|
((schema?.depends_on || []) as (string | string[])[])
|
|
1475
1697
|
: [];
|
|
1476
1698
|
const describeDependency = (dep: string) => {
|
|
1477
|
-
const
|
|
1478
|
-
const depName = eqIndex === -1 ? dep : dep.slice(0, eqIndex);
|
|
1479
|
-
const expected = eqIndex === -1 ? undefined : dep.slice(eqIndex + 1);
|
|
1699
|
+
const { name: depName, op, value: expected } = parseDependency(dep);
|
|
1480
1700
|
const depLabel = (options?.[depName]?.display_name as string) || depName;
|
|
1481
1701
|
const depValue = (availableOptions as TQorusForm)?.[depName]?.value;
|
|
1482
1702
|
return {
|
|
1483
1703
|
name: depName,
|
|
1484
1704
|
exists: !!options?.[depName],
|
|
1485
|
-
label:
|
|
1705
|
+
label: !op ? depLabel : `${depLabel} ${op === '=' ? '=' : '≠'} ${expected}`,
|
|
1486
1706
|
fulfilled:
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
:
|
|
1707
|
+
!op ? !isOptionValueEmpty(depValue)
|
|
1708
|
+
: depValue == null ? false
|
|
1709
|
+
: op === '=' ? String(depValue) === expected
|
|
1710
|
+
: String(depValue) !== expected,
|
|
1490
1711
|
};
|
|
1491
1712
|
};
|
|
1492
1713
|
const depHighlightNames = (flatten(dependencyEntries as never[]) as string[])
|
|
@@ -1621,7 +1842,7 @@ export const CompactRow = memo(
|
|
|
1621
1842
|
{...clusterHoverProps}
|
|
1622
1843
|
>
|
|
1623
1844
|
{clusterNode}
|
|
1624
|
-
<StyledLabelBlock>
|
|
1845
|
+
<StyledLabelBlock className='options-readfirst-label-block'>
|
|
1625
1846
|
<StyledRowLabel title={schema?.short_desc || undefined} $color={cKey}>
|
|
1626
1847
|
{rowChromeIcon}
|
|
1627
1848
|
{/* label + asterisk + help flow as ONE inline run, so the asterisk
|
|
@@ -1652,6 +1873,7 @@ export const CompactRow = memo(
|
|
|
1652
1873
|
{labelShortDesc}
|
|
1653
1874
|
</StyledLabelDesc>
|
|
1654
1875
|
: null}
|
|
1876
|
+
{codeSizeNode}
|
|
1655
1877
|
{lineCountNote ?
|
|
1656
1878
|
<ReqoreP
|
|
1657
1879
|
className='options-readfirst-label-lines'
|
|
@@ -1667,14 +1889,17 @@ export const CompactRow = memo(
|
|
|
1667
1889
|
// A code field draws its whole value in the preview below, with
|
|
1668
1890
|
// "Show more" for the rest, so a hover carrying the same text is
|
|
1669
1891
|
// noise -- and a native tooltip holding a few hundred lines is
|
|
1670
|
-
// unreadable anyway.
|
|
1671
|
-
//
|
|
1892
|
+
// unreadable anyway. A chipped richtext value is the same case: the row
|
|
1893
|
+
// already draws it in full, and its chips carry their own tooltips.
|
|
1894
|
+
// Every other value keeps its hover, which is the only way to read one
|
|
1895
|
+
// that the row had to truncate.
|
|
1672
1896
|
title={
|
|
1673
1897
|
(
|
|
1674
1898
|
!empty &&
|
|
1675
1899
|
!hidden &&
|
|
1676
1900
|
!showCodePreview &&
|
|
1677
1901
|
!showMarkdownPreview &&
|
|
1902
|
+
!showsTemplateChips &&
|
|
1678
1903
|
typeof formatted === 'string'
|
|
1679
1904
|
) ?
|
|
1680
1905
|
formatted
|
|
@@ -1701,10 +1926,24 @@ export const CompactRow = memo(
|
|
|
1701
1926
|
reads less like a summary than like a second, disagreeing answer.
|
|
1702
1927
|
An UNDESCRIBED value keeps its summary: the data tree beneath it
|
|
1703
1928
|
says nothing about what the value MEANS, so the summary is still
|
|
1704
|
-
the only line that does.
|
|
1705
|
-
|
|
1929
|
+
the only line that does.
|
|
1930
|
+
|
|
1931
|
+
Both suppressions are conditional on the replacement ACTUALLY
|
|
1932
|
+
rendering. `previewWithSchema` only asks whether the value and its
|
|
1933
|
+
`arg_schema` COULD be drawn as a schema preview; whether one is
|
|
1934
|
+
drawn is `showStructuredPreview`, and the two disagree for a field
|
|
1935
|
+
whose value type is a host ui_type rather than a list or a hash —
|
|
1936
|
+
a test's `cases` (`ui_type: "test-cases"`, an array of case hashes
|
|
1937
|
+
with an `arg_schema`) could be previewed but is not, because
|
|
1938
|
+
`isHashList` only recognises `list`/`free-list`/`array`. The row
|
|
1939
|
+
then dropped its summary in favour of a preview that never came
|
|
1940
|
+
and rendered nothing at all: a test with cases read as a bare
|
|
1941
|
+
"Cases" and looked unset until it was opened. */}
|
|
1942
|
+
{showMarkdownPreview || (previewWithSchema && showStructuredPreview) ? null : (
|
|
1706
1943
|
<span className='options-readfirst-valuetext'>
|
|
1707
|
-
{hidden || empty ?
|
|
1944
|
+
{hidden || empty ?
|
|
1945
|
+
formattedDefault || '—'
|
|
1946
|
+
: renderReadFirstValue(optionField, schema, formatted)}
|
|
1708
1947
|
</span>
|
|
1709
1948
|
)}
|
|
1710
1949
|
{valueReasons.map((m, i) => (
|
|
@@ -1720,7 +1959,14 @@ export const CompactRow = memo(
|
|
|
1720
1959
|
value (full width of the value column) — never pushed down by the
|
|
1721
1960
|
label's short_desc. */}
|
|
1722
1961
|
{panelMessages.length ?
|
|
1723
|
-
<
|
|
1962
|
+
<ReqoreControlGroup
|
|
1963
|
+
className='options-readfirst-info-panel'
|
|
1964
|
+
vertical
|
|
1965
|
+
fluid
|
|
1966
|
+
gapSize='normal'
|
|
1967
|
+
>
|
|
1968
|
+
{panelMessages.map(renderInfoStrip)}
|
|
1969
|
+
</ReqoreControlGroup>
|
|
1724
1970
|
: null}
|
|
1725
1971
|
{/* The structured hash/list preview also lives in the value cell, so it
|
|
1726
1972
|
starts directly under the value summary ("N fields"), not below the
|