@qoretechnologies/reqraft 0.10.45-pr.95.gc02205d → 0.10.45
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 +20 -1
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +279 -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 +48 -1
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +203 -98
- 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 +2 -0
- package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowStyles.js +21 -4
- 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 +35 -6
- 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/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 +391 -29
- package/src/components/form/engine/CompactToolbar.tsx +105 -98
- package/src/components/form/engine/FormEngine.stories.tsx +863 -12
- package/src/components/form/engine/FormEngine.tsx +231 -68
- 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 +38 -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 +314 -1
- package/src/components/form/fields/template/TemplateField.tsx +47 -8
- 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/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
|
@@ -19,19 +19,22 @@ import {
|
|
|
19
19
|
TQorusFormFieldSchema,
|
|
20
20
|
} from '@qoretechnologies/ts-toolkit';
|
|
21
21
|
import flatten from 'lodash/flatten';
|
|
22
|
+
import isEqual from 'lodash/isEqual';
|
|
22
23
|
import size from 'lodash/size';
|
|
23
24
|
import React, { memo } from 'react';
|
|
24
25
|
import { useContextSelector } from 'use-context-selector';
|
|
25
|
-
import { hasAllDependenciesFullfilled } from '../../../helpers/validations';
|
|
26
|
+
import { hasAllDependenciesFullfilled, parseDependency } from '../../../helpers/validations';
|
|
26
27
|
import {
|
|
27
28
|
findTemplate,
|
|
28
29
|
getTemplateTagStyle,
|
|
29
30
|
isValueTemplate,
|
|
31
|
+
describeTemplateReference,
|
|
32
|
+
splitTemplateTokens,
|
|
30
33
|
TTemplateMeta,
|
|
31
34
|
} from '../../../helpers/templates';
|
|
32
|
-
import { richtextToSegments } from '../../../helpers/common';
|
|
35
|
+
import { richtextToSegments, richtextToString } from '../../../helpers/common';
|
|
33
36
|
import { ReadOnlyTemplateTag } from '../fields/template/ReadOnlyTemplateTag';
|
|
34
|
-
import {
|
|
37
|
+
import { describeCodeSize, formatCodeChars, formatCodeLines } from '../../codeSize';
|
|
35
38
|
import { Description } from '../../Description';
|
|
36
39
|
import { useMarkdownRenderer } from '../../Description/markdownRendererContext';
|
|
37
40
|
import { FocusedEditing } from '../../FocusedEditing';
|
|
@@ -45,6 +48,7 @@ import {
|
|
|
45
48
|
StyledColumn,
|
|
46
49
|
StyledEditCard,
|
|
47
50
|
StyledLabelBlock,
|
|
51
|
+
StyledAbsorbedField,
|
|
48
52
|
StyledLabelDesc,
|
|
49
53
|
StyledRowActions,
|
|
50
54
|
StyledRowInset,
|
|
@@ -135,6 +139,26 @@ const MAX_INLINE_OPTION_ACTIONS = 2;
|
|
|
135
139
|
// One read-first row: label | value | action collapsed; the real editor (the
|
|
136
140
|
// classic renderOption) expanded. `hidden` = search-surfaced optional —
|
|
137
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
|
+
|
|
138
162
|
export const CompactRow = memo(
|
|
139
163
|
({
|
|
140
164
|
optionName,
|
|
@@ -143,10 +167,16 @@ export const CompactRow = memo(
|
|
|
143
167
|
clustered = false,
|
|
144
168
|
clusterFirst = false,
|
|
145
169
|
clusterLast = false,
|
|
170
|
+
absorbedFields,
|
|
146
171
|
}: {
|
|
147
172
|
optionName: string;
|
|
148
173
|
optionField: IQorusFormField;
|
|
149
174
|
hidden?: boolean;
|
|
175
|
+
/** Siblings this row renders inside its own container instead of leaving
|
|
176
|
+
* them a row each — declared by the schema's `absorb_fields`. The pair it
|
|
177
|
+
* exists for is a code editor and its language: one decision, so one
|
|
178
|
+
* element. */
|
|
179
|
+
absorbedFields?: string[];
|
|
150
180
|
// Rendered inside a required-group cluster: leading status node + rail, and no
|
|
151
181
|
// per-row "one of" chip (the cluster header carries it). first/last trim the
|
|
152
182
|
// rail segment so it spans node-to-node, not past the end members.
|
|
@@ -157,6 +187,13 @@ export const CompactRow = memo(
|
|
|
157
187
|
const readOnly = useContextSelector(CompactRowContext, (v) => v.readOnly);
|
|
158
188
|
const commitMode = useContextSelector(CompactRowContext, (v) => v.commitMode);
|
|
159
189
|
const options = useContextSelector(CompactRowContext, (v) => v.options);
|
|
190
|
+
|
|
191
|
+
// An absorbed field keeps its own name — it is a different field, not a
|
|
192
|
+
// property of its host — so the label comes from its schema exactly as it
|
|
193
|
+
// would on its own row.
|
|
194
|
+
const getAbsorbedLabel = (absorbedName: string): string =>
|
|
195
|
+
(options?.[absorbedName] as { display_name?: string } | undefined)?.display_name ||
|
|
196
|
+
absorbedName;
|
|
160
197
|
const codePreviewRenderer = useContextSelector(CompactRowContext, (v) => v.codePreviewRenderer);
|
|
161
198
|
const markdownRenderer = useMarkdownRenderer();
|
|
162
199
|
const operators = useContextSelector(CompactRowContext, (v) => v.operators);
|
|
@@ -219,6 +256,27 @@ export const CompactRow = memo(
|
|
|
219
256
|
|
|
220
257
|
// Value-cell content: colour adds a swatch, file an icon + size; hash keeps
|
|
221
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
|
+
|
|
222
280
|
const renderReadFirstValue = (
|
|
223
281
|
field: IQorusFormField,
|
|
224
282
|
schema: TQorusFormFieldSchema | undefined,
|
|
@@ -296,7 +354,32 @@ export const CompactRow = memo(
|
|
|
296
354
|
// see `showCodePreview` below. Keeps the row height fixed while making
|
|
297
355
|
// multi-line source readable without opening the full editor.
|
|
298
356
|
if (valueType === 'code-editor' && typeof field?.value === 'string') {
|
|
299
|
-
|
|
357
|
+
// An absorbed sibling has no row of its own, so a COLLAPSED host has to
|
|
358
|
+
// carry its value or it disappears from the form entirely when closed.
|
|
359
|
+
// It reads as a chip beside the size chip — "Qore · 62 lines" — which
|
|
360
|
+
// is the same summary the read-only interface pages already show.
|
|
361
|
+
return (
|
|
362
|
+
<ReqoreControlGroup gapSize='tiny' verticalAlign='center' wrap>
|
|
363
|
+
{(absorbedFields ?? []).map((absorbedName) => {
|
|
364
|
+
const absorbedValue = (
|
|
365
|
+
availableOptions?.[absorbedName] as IQorusFormField | undefined
|
|
366
|
+
)?.value;
|
|
367
|
+
if (absorbedValue === undefined || absorbedValue === null || absorbedValue === '') {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
return (
|
|
371
|
+
<ReqoreTag
|
|
372
|
+
key={absorbedName}
|
|
373
|
+
className='options-readfirst-absorbed-tag'
|
|
374
|
+
size='small'
|
|
375
|
+
minimal
|
|
376
|
+
label={String(absorbedValue)}
|
|
377
|
+
tooltip={getAbsorbedLabel(absorbedName)}
|
|
378
|
+
/>
|
|
379
|
+
);
|
|
380
|
+
})}
|
|
381
|
+
</ReqoreControlGroup>
|
|
382
|
+
);
|
|
300
383
|
}
|
|
301
384
|
|
|
302
385
|
// Markdown read summary: the row prints the value's prose (the markdown
|
|
@@ -343,6 +426,15 @@ export const CompactRow = memo(
|
|
|
343
426
|
return <ReadOnlyTemplateTag value={field.value} templates={templates} size='small' />;
|
|
344
427
|
}
|
|
345
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
|
+
|
|
346
438
|
// Richtext read-first summary: inline $-chips for embedded template tags
|
|
347
439
|
// with the prose around them (the IDE intent scheme via getTemplateTagStyle),
|
|
348
440
|
// kept lightweight instead of mounting the full editor — that lives in the
|
|
@@ -373,6 +465,49 @@ export const CompactRow = memo(
|
|
|
373
465
|
?.metadata as TTemplateMeta | undefined
|
|
374
466
|
)}
|
|
375
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
|
+
/>
|
|
376
511
|
: <span key={index} style={{ whiteSpace: 'pre' }}>
|
|
377
512
|
{segment.text}
|
|
378
513
|
</span>
|
|
@@ -589,6 +724,41 @@ export const CompactRow = memo(
|
|
|
589
724
|
return () => window.clearTimeout(id);
|
|
590
725
|
}, [isExpanded, optionName, autoFocusNameRef]);
|
|
591
726
|
|
|
727
|
+
// What the field held when it was opened — the baseline Cancel restores.
|
|
728
|
+
//
|
|
729
|
+
// Deliberately NOT `originalValue`, which is the whole form's load-time
|
|
730
|
+
// snapshot. That answers "undo every change ever made to this field", which
|
|
731
|
+
// is a different question from "forget what I just typed"; a Cancel sitting
|
|
732
|
+
// next to Done has to mean the second one, or it silently throws away edits
|
|
733
|
+
// the user made earlier and had already accepted.
|
|
734
|
+
//
|
|
735
|
+
// The dependency list is `isExpanded` alone on purpose: adding the value
|
|
736
|
+
// would re-take the snapshot on every keystroke, leaving nothing to cancel.
|
|
737
|
+
const editEntryValue = React.useRef<{ value: any; type?: string } | undefined>(undefined);
|
|
738
|
+
React.useEffect(() => {
|
|
739
|
+
if (isExpanded) {
|
|
740
|
+
editEntryValue.current = { value: optionField?.value, type: optionField?.type };
|
|
741
|
+
} else {
|
|
742
|
+
editEntryValue.current = undefined;
|
|
743
|
+
}
|
|
744
|
+
}, [isExpanded]);
|
|
745
|
+
|
|
746
|
+
// True while the open editor holds something other than what it opened with.
|
|
747
|
+
const editedSinceOpened =
|
|
748
|
+
isExpanded &&
|
|
749
|
+
!!editEntryValue.current &&
|
|
750
|
+
!isEqual(editEntryValue.current.value, optionField?.value);
|
|
751
|
+
|
|
752
|
+
// Discard whatever was typed since the field was opened and close it. Used
|
|
753
|
+
// by both the Cancel button and Escape, so the two cannot diverge.
|
|
754
|
+
const cancelEdit = React.useCallback(() => {
|
|
755
|
+
const snapshot = editEntryValue.current;
|
|
756
|
+
if (snapshot && !isEqual(snapshot.value, optionField?.value)) {
|
|
757
|
+
handleValueChange(optionName, snapshot.value, snapshot.type);
|
|
758
|
+
}
|
|
759
|
+
toggleExpandedOption(optionName);
|
|
760
|
+
}, [handleValueChange, optionField?.value, optionName, toggleExpandedOption]);
|
|
761
|
+
|
|
592
762
|
// The sub-schema describing this field's value, when it has one. A value the
|
|
593
763
|
// form knows the shape of is previewed THROUGH that shape (see
|
|
594
764
|
// `SchemaDataView`); only genuinely undescribed data falls through to the
|
|
@@ -646,6 +816,18 @@ export const CompactRow = memo(
|
|
|
646
816
|
[optionField?.value, previewArgSchema]
|
|
647
817
|
);
|
|
648
818
|
|
|
819
|
+
// The form-load revert: back to the value the FORM was loaded with.
|
|
820
|
+
//
|
|
821
|
+
// In an open editor this is offered only when it would do something
|
|
822
|
+
// `cancelEditButton` does not — that is, when the field was already
|
|
823
|
+
// modified BEFORE it was opened, so "undo this edit" and "undo every edit"
|
|
824
|
+
// are different answers. On the usual path (open an untouched field, type,
|
|
825
|
+
// change your mind) the two are identical, and two buttons with the same
|
|
826
|
+
// effect and different names are worse than one.
|
|
827
|
+
//
|
|
828
|
+
// It is unconditional on the READ row, where it renders inline in the
|
|
829
|
+
// status-dot column — there is no edit in progress there to cancel, so the
|
|
830
|
+
// whole-field revert is the only undo that makes sense.
|
|
649
831
|
const revertButton =
|
|
650
832
|
changed ?
|
|
651
833
|
<ReqoreButton
|
|
@@ -727,6 +909,43 @@ export const CompactRow = memo(
|
|
|
727
909
|
/>
|
|
728
910
|
: null;
|
|
729
911
|
const isPassiveCloseAction = fixedAllowedValueOption || readOnly;
|
|
912
|
+
// Cancel — the counterpart to Done, and the answer to "how do I get out of
|
|
913
|
+
// here without keeping this". Done was the only way to leave an open field,
|
|
914
|
+
// so every way out committed: the check, clicking away, and Escape (which
|
|
915
|
+
// closed without discarding, so it committed too, silently).
|
|
916
|
+
//
|
|
917
|
+
// Shown only while there is something to discard. On an untouched field
|
|
918
|
+
// Cancel and Done would do exactly the same thing, and two buttons that
|
|
919
|
+
// differ in name but not in effect are worse than one.
|
|
920
|
+
//
|
|
921
|
+
// Not offered when the close action is passive (read-only, or a fixed
|
|
922
|
+
// choice) — nothing can have been typed, so there is nothing to cancel.
|
|
923
|
+
const cancelEditButton =
|
|
924
|
+
!isPassiveCloseAction && editedSinceOpened ?
|
|
925
|
+
<ReqoreButton
|
|
926
|
+
className='options-readfirst-cancel'
|
|
927
|
+
size='small'
|
|
928
|
+
flat
|
|
929
|
+
minimal
|
|
930
|
+
fixed
|
|
931
|
+
icon='CloseLine'
|
|
932
|
+
intent='warning'
|
|
933
|
+
// "Cancel edit", not "Cancel": a bare Cancel collides with the one
|
|
934
|
+
// in Reqore's confirmation modal, and this one is specifically about
|
|
935
|
+
// abandoning the edit rather than dismissing a dialog.
|
|
936
|
+
aria-label='Cancel edit'
|
|
937
|
+
tooltip='Cancel edit — discard the changes made since this field was opened'
|
|
938
|
+
onClick={(event: React.MouseEvent) => {
|
|
939
|
+
event.stopPropagation();
|
|
940
|
+
cancelEdit();
|
|
941
|
+
}}
|
|
942
|
+
/>
|
|
943
|
+
: null;
|
|
944
|
+
// See `revertButton` above for why this is conditional in the edit row.
|
|
945
|
+
const openedWithFormLoadValue =
|
|
946
|
+
!editEntryValue.current ||
|
|
947
|
+
isEqual(editEntryValue.current.value, originalValue.current?.[optionName]?.value);
|
|
948
|
+
const editRowRevertButton = openedWithFormLoadValue ? null : revertButton;
|
|
730
949
|
const closeExpandedFieldButton = (
|
|
731
950
|
<ReqoreButton
|
|
732
951
|
className='options-readfirst-done'
|
|
@@ -825,6 +1044,11 @@ export const CompactRow = memo(
|
|
|
825
1044
|
/>
|
|
826
1045
|
: null;
|
|
827
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.
|
|
828
1052
|
const renderInfoStrip = (m: TInfoMsg, index: number) => (
|
|
829
1053
|
<ReqoreMessage
|
|
830
1054
|
key={`${m.content}-${index}`}
|
|
@@ -833,6 +1057,7 @@ export const CompactRow = memo(
|
|
|
833
1057
|
flat
|
|
834
1058
|
intent={m.intent as never}
|
|
835
1059
|
title={m.title}
|
|
1060
|
+
style={{ marginBottom: 8 }}
|
|
836
1061
|
>
|
|
837
1062
|
{m.content}
|
|
838
1063
|
</ReqoreMessage>
|
|
@@ -911,11 +1136,68 @@ export const CompactRow = memo(
|
|
|
911
1136
|
/>
|
|
912
1137
|
);
|
|
913
1138
|
|
|
1139
|
+
// The absorbed siblings' controls, rendered inside this row.
|
|
1140
|
+
//
|
|
1141
|
+
// Each keeps its own label so it is still identifiable — it is a different
|
|
1142
|
+
// field, not a property of this one — but it sits inside this row's
|
|
1143
|
+
// container, above the editor, so the pair reads as the single decision it
|
|
1144
|
+
// is. `renderOption` is the same function the row uses for its own editor,
|
|
1145
|
+
// so an absorbed field renders exactly as it would have on its own row.
|
|
1146
|
+
const absorbedNodes =
|
|
1147
|
+
absorbedFields?.length ?
|
|
1148
|
+
absorbedFields
|
|
1149
|
+
.map((absorbedName) => {
|
|
1150
|
+
const absorbedField = availableOptions?.[absorbedName] as IQorusFormField | undefined;
|
|
1151
|
+
if (!absorbedField) return null;
|
|
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.
|
|
1156
|
+
<StyledAbsorbedField key={absorbedName} className='options-readfirst-absorbed'>
|
|
1157
|
+
<div style={{ minWidth: 0, flex: 1 }}>
|
|
1158
|
+
{renderOption(absorbedName, absorbedField, 'small', true)}
|
|
1159
|
+
</div>
|
|
1160
|
+
</StyledAbsorbedField>
|
|
1161
|
+
);
|
|
1162
|
+
})
|
|
1163
|
+
.filter(Boolean)
|
|
1164
|
+
: null;
|
|
1165
|
+
|
|
914
1166
|
// Declared here rather than beside the hash rows below: the `isExpanded`
|
|
915
1167
|
// branch returns before that point, and its editor needs to know whether it is
|
|
916
1168
|
// holding code.
|
|
917
1169
|
const valueType = getValueType(optionField, schema);
|
|
918
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
|
+
|
|
919
1201
|
if (isExpanded) {
|
|
920
1202
|
if (inlineEditable) {
|
|
921
1203
|
const collapse = () => toggleExpandedOption(optionName);
|
|
@@ -931,7 +1213,7 @@ export const CompactRow = memo(
|
|
|
931
1213
|
}
|
|
932
1214
|
{...clusterHoverProps}
|
|
933
1215
|
>
|
|
934
|
-
<StyledLabelBlock>
|
|
1216
|
+
<StyledLabelBlock className='options-readfirst-label-block'>
|
|
935
1217
|
<StyledRowLabel
|
|
936
1218
|
role='button'
|
|
937
1219
|
tabIndex={0}
|
|
@@ -966,6 +1248,7 @@ export const CompactRow = memo(
|
|
|
966
1248
|
{labelShortDesc}
|
|
967
1249
|
</StyledLabelDesc>
|
|
968
1250
|
: null}
|
|
1251
|
+
{codeSizeNode}
|
|
969
1252
|
</StyledLabelBlock>
|
|
970
1253
|
<div
|
|
971
1254
|
ref={editorRef}
|
|
@@ -973,7 +1256,11 @@ export const CompactRow = memo(
|
|
|
973
1256
|
onKeyDown={(event) => {
|
|
974
1257
|
if (event.key === 'Escape') {
|
|
975
1258
|
event.stopPropagation();
|
|
976
|
-
collapse()
|
|
1259
|
+
// Escape discards. It used to call `collapse()`, which closed
|
|
1260
|
+
// the field and KEPT whatever had been typed — the one
|
|
1261
|
+
// keystroke every editor treats as "get me out of this
|
|
1262
|
+
// without saving" was the quietest way to commit.
|
|
1263
|
+
cancelEdit();
|
|
977
1264
|
}
|
|
978
1265
|
}}
|
|
979
1266
|
>
|
|
@@ -989,11 +1276,32 @@ export const CompactRow = memo(
|
|
|
989
1276
|
|
|
990
1277
|
It renders here, at the top of the value cell, which is exactly where
|
|
991
1278
|
it sits on the read row — so the switch moves nothing. */}
|
|
992
|
-
{
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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>
|
|
996
1300
|
: null}
|
|
1301
|
+
{/* Absorbed siblings sit at the TOP of the value cell, above the
|
|
1302
|
+
editor they belong to — the language you are writing in is
|
|
1303
|
+
read before the code, not after it. */}
|
|
1304
|
+
{absorbedNodes}
|
|
997
1305
|
{renderOption(optionName, optionField, 'small', true)}
|
|
998
1306
|
</div>
|
|
999
1307
|
<StyledRowActions>
|
|
@@ -1002,7 +1310,8 @@ export const CompactRow = memo(
|
|
|
1002
1310
|
OptionFieldMessages strip below already says it — showing
|
|
1003
1311
|
both was redundant. The tag stays on READ rows, where no
|
|
1004
1312
|
message strip is visible. */}
|
|
1005
|
-
{
|
|
1313
|
+
{editRowRevertButton}
|
|
1314
|
+
{cancelEditButton}
|
|
1006
1315
|
{clearValueButton}
|
|
1007
1316
|
{inlineOptionActions.map((action, index) =>
|
|
1008
1317
|
renderInjectedOptionAction(action, index)
|
|
@@ -1189,6 +1498,10 @@ export const CompactRow = memo(
|
|
|
1189
1498
|
{closeExpandedFieldButton}
|
|
1190
1499
|
</ReqoreControlGroup>
|
|
1191
1500
|
</div>
|
|
1501
|
+
{/* Absorbed siblings render in the CARD form of an open field too,
|
|
1502
|
+
not only the inline row — which shape a field opens into depends
|
|
1503
|
+
on its type, and absorbing must not depend on that. */}
|
|
1504
|
+
{absorbedNodes}
|
|
1192
1505
|
{/* Same fullscreen focused-editing affordance as the classic cards —
|
|
1193
1506
|
the modal mounts when this option is focused. */}
|
|
1194
1507
|
<FocusedEditing
|
|
@@ -1240,6 +1553,9 @@ export const CompactRow = memo(
|
|
|
1240
1553
|
Array.isArray(optionField?.value) &&
|
|
1241
1554
|
optionField.value.length > 0 &&
|
|
1242
1555
|
optionField.value.some((item) => {
|
|
1556
|
+
if (richtextItemText(item) !== undefined) {
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1243
1559
|
const inner =
|
|
1244
1560
|
item && typeof item === 'object' && 'value' in (item as Record<string, unknown>) ?
|
|
1245
1561
|
(item as Record<string, unknown>).value
|
|
@@ -1257,6 +1573,19 @@ export const CompactRow = memo(
|
|
|
1257
1573
|
valueType === 'code-editor' &&
|
|
1258
1574
|
typeof optionField?.value === 'string' &&
|
|
1259
1575
|
optionField.value.length > 0;
|
|
1576
|
+
// A richtext value that renders as CHIPS is drawn in full by the row itself:
|
|
1577
|
+
// the chips are the value, and the reader is looking straight at it. The
|
|
1578
|
+
// native `title` below would then hover the raw source of the very thing the
|
|
1579
|
+
// chips replace ("[$fsminput:severity] $fsminput:alert_code") — and, because
|
|
1580
|
+
// each chip carries its own Reqore tooltip, hovering one fires BOTH: the chip
|
|
1581
|
+
// popover and the row's browser tooltip, in different places, saying almost
|
|
1582
|
+
// the same thing. Only the popover shows up in a screenshot, which is what
|
|
1583
|
+
// made the pair hard to report.
|
|
1584
|
+
const showsTemplateChips =
|
|
1585
|
+
!hidden &&
|
|
1586
|
+
valueType === 'richtext' &&
|
|
1587
|
+
Array.isArray(optionField?.value) &&
|
|
1588
|
+
richtextToSegments(optionField.value as never).some((segment) => segment.kind === 'tag');
|
|
1260
1589
|
// Markdown reads the same way for the same reason: the row itself can only
|
|
1261
1590
|
// show a line of text, and for markdown that line is the SOURCE — the reader
|
|
1262
1591
|
// gets `## ` and `**` where the point of the value is what it looks like
|
|
@@ -1293,35 +1622,45 @@ export const CompactRow = memo(
|
|
|
1293
1622
|
showFieldTypes ?
|
|
1294
1623
|
`<${(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}]` : ''}>`
|
|
1295
1624
|
: null;
|
|
1296
|
-
//
|
|
1297
|
-
//
|
|
1625
|
+
// A `readonly` field is one the system owns — an assigned id, a recorded
|
|
1626
|
+
// provenance. The server states that on the field itself; only the older
|
|
1627
|
+
// compat projection also mapped it to `disabled`, so on this path the form
|
|
1628
|
+
// was handed `readonly` alone and rendered an editable control for a value
|
|
1629
|
+
// no answer of the user's can change.
|
|
1630
|
+
const fieldReadonly = !!(schema as { readonly?: boolean } | undefined)?.readonly;
|
|
1631
|
+
// Disabled rows (schema flag, readonly, or unmet deps) can't open — a lock +
|
|
1632
|
+
// reason renders instead. Form-level readOnly still opens in view mode (Close).
|
|
1298
1633
|
const fieldDisabled =
|
|
1299
1634
|
!hidden &&
|
|
1300
1635
|
!readOnly &&
|
|
1301
1636
|
(!!schema?.disabled ||
|
|
1637
|
+
fieldReadonly ||
|
|
1302
1638
|
!hasAllDependenciesFullfilled(schema?.depends_on, availableOptions, options || {}));
|
|
1303
1639
|
const fieldDisabledReason =
|
|
1304
|
-
schema?.disabled ? 'This field is disabled'
|
|
1640
|
+
schema?.disabled ? 'This field is disabled'
|
|
1641
|
+
: fieldReadonly ? 'Set by Qorus — not editable'
|
|
1642
|
+
: 'Disabled — dependencies are not fulfilled';
|
|
1305
1643
|
// Dependency contract: top-level entries must ALL hold; a nested array
|
|
1306
|
-
// means ANY of its entries; `name=value` requires that exact value
|
|
1644
|
+
// means ANY of its entries; `name=value` requires that exact value, and
|
|
1645
|
+
// `name!=value` requires any other answer. Parsed by the same helper the
|
|
1646
|
+
// predicate uses, so the lock cannot describe a rule the form does not apply.
|
|
1307
1647
|
const dependencyEntries =
|
|
1308
|
-
fieldDisabled && !schema?.disabled ?
|
|
1648
|
+
fieldDisabled && !schema?.disabled && !fieldReadonly ?
|
|
1309
1649
|
((schema?.depends_on || []) as (string | string[])[])
|
|
1310
1650
|
: [];
|
|
1311
1651
|
const describeDependency = (dep: string) => {
|
|
1312
|
-
const
|
|
1313
|
-
const depName = eqIndex === -1 ? dep : dep.slice(0, eqIndex);
|
|
1314
|
-
const expected = eqIndex === -1 ? undefined : dep.slice(eqIndex + 1);
|
|
1652
|
+
const { name: depName, op, value: expected } = parseDependency(dep);
|
|
1315
1653
|
const depLabel = (options?.[depName]?.display_name as string) || depName;
|
|
1316
1654
|
const depValue = (availableOptions as TQorusForm)?.[depName]?.value;
|
|
1317
1655
|
return {
|
|
1318
1656
|
name: depName,
|
|
1319
1657
|
exists: !!options?.[depName],
|
|
1320
|
-
label:
|
|
1658
|
+
label: !op ? depLabel : `${depLabel} ${op === '=' ? '=' : '≠'} ${expected}`,
|
|
1321
1659
|
fulfilled:
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
:
|
|
1660
|
+
!op ? !isOptionValueEmpty(depValue)
|
|
1661
|
+
: depValue == null ? false
|
|
1662
|
+
: op === '=' ? String(depValue) === expected
|
|
1663
|
+
: String(depValue) !== expected,
|
|
1325
1664
|
};
|
|
1326
1665
|
};
|
|
1327
1666
|
const depHighlightNames = (flatten(dependencyEntries as never[]) as string[])
|
|
@@ -1456,7 +1795,7 @@ export const CompactRow = memo(
|
|
|
1456
1795
|
{...clusterHoverProps}
|
|
1457
1796
|
>
|
|
1458
1797
|
{clusterNode}
|
|
1459
|
-
<StyledLabelBlock>
|
|
1798
|
+
<StyledLabelBlock className='options-readfirst-label-block'>
|
|
1460
1799
|
<StyledRowLabel title={schema?.short_desc || undefined} $color={cKey}>
|
|
1461
1800
|
{rowChromeIcon}
|
|
1462
1801
|
{/* label + asterisk + help flow as ONE inline run, so the asterisk
|
|
@@ -1487,6 +1826,7 @@ export const CompactRow = memo(
|
|
|
1487
1826
|
{labelShortDesc}
|
|
1488
1827
|
</StyledLabelDesc>
|
|
1489
1828
|
: null}
|
|
1829
|
+
{codeSizeNode}
|
|
1490
1830
|
{lineCountNote ?
|
|
1491
1831
|
<ReqoreP
|
|
1492
1832
|
className='options-readfirst-label-lines'
|
|
@@ -1502,14 +1842,17 @@ export const CompactRow = memo(
|
|
|
1502
1842
|
// A code field draws its whole value in the preview below, with
|
|
1503
1843
|
// "Show more" for the rest, so a hover carrying the same text is
|
|
1504
1844
|
// noise -- and a native tooltip holding a few hundred lines is
|
|
1505
|
-
// unreadable anyway.
|
|
1506
|
-
//
|
|
1845
|
+
// unreadable anyway. A chipped richtext value is the same case: the row
|
|
1846
|
+
// already draws it in full, and its chips carry their own tooltips.
|
|
1847
|
+
// Every other value keeps its hover, which is the only way to read one
|
|
1848
|
+
// that the row had to truncate.
|
|
1507
1849
|
title={
|
|
1508
1850
|
(
|
|
1509
1851
|
!empty &&
|
|
1510
1852
|
!hidden &&
|
|
1511
1853
|
!showCodePreview &&
|
|
1512
1854
|
!showMarkdownPreview &&
|
|
1855
|
+
!showsTemplateChips &&
|
|
1513
1856
|
typeof formatted === 'string'
|
|
1514
1857
|
) ?
|
|
1515
1858
|
formatted
|
|
@@ -1536,8 +1879,20 @@ export const CompactRow = memo(
|
|
|
1536
1879
|
reads less like a summary than like a second, disagreeing answer.
|
|
1537
1880
|
An UNDESCRIBED value keeps its summary: the data tree beneath it
|
|
1538
1881
|
says nothing about what the value MEANS, so the summary is still
|
|
1539
|
-
the only line that does.
|
|
1540
|
-
|
|
1882
|
+
the only line that does.
|
|
1883
|
+
|
|
1884
|
+
Both suppressions are conditional on the replacement ACTUALLY
|
|
1885
|
+
rendering. `previewWithSchema` only asks whether the value and its
|
|
1886
|
+
`arg_schema` COULD be drawn as a schema preview; whether one is
|
|
1887
|
+
drawn is `showStructuredPreview`, and the two disagree for a field
|
|
1888
|
+
whose value type is a host ui_type rather than a list or a hash —
|
|
1889
|
+
a test's `cases` (`ui_type: "test-cases"`, an array of case hashes
|
|
1890
|
+
with an `arg_schema`) could be previewed but is not, because
|
|
1891
|
+
`isHashList` only recognises `list`/`free-list`/`array`. The row
|
|
1892
|
+
then dropped its summary in favour of a preview that never came
|
|
1893
|
+
and rendered nothing at all: a test with cases read as a bare
|
|
1894
|
+
"Cases" and looked unset until it was opened. */}
|
|
1895
|
+
{showMarkdownPreview || (previewWithSchema && showStructuredPreview) ? null : (
|
|
1541
1896
|
<span className='options-readfirst-valuetext'>
|
|
1542
1897
|
{hidden || empty ? '—' : renderReadFirstValue(optionField, schema, formatted)}
|
|
1543
1898
|
</span>
|
|
@@ -1555,7 +1910,14 @@ export const CompactRow = memo(
|
|
|
1555
1910
|
value (full width of the value column) — never pushed down by the
|
|
1556
1911
|
label's short_desc. */}
|
|
1557
1912
|
{panelMessages.length ?
|
|
1558
|
-
<
|
|
1913
|
+
<ReqoreControlGroup
|
|
1914
|
+
className='options-readfirst-info-panel'
|
|
1915
|
+
vertical
|
|
1916
|
+
fluid
|
|
1917
|
+
gapSize='normal'
|
|
1918
|
+
>
|
|
1919
|
+
{panelMessages.map(renderInfoStrip)}
|
|
1920
|
+
</ReqoreControlGroup>
|
|
1559
1921
|
: null}
|
|
1560
1922
|
{/* The structured hash/list preview also lives in the value cell, so it
|
|
1561
1923
|
starts directly under the value summary ("N fields"), not below the
|