@qoretechnologies/reqraft 0.10.44-pr.95.gb67bfb7 → 0.10.44
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 +108 -20
- 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 +148 -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 +123 -0
- package/src/components/form/engine/_structuredData/SchemaDataView.tsx +217 -95
- 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 +153 -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
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ReqoreCollapsibleContent,
|
|
3
|
+
ReqoreIcon,
|
|
4
|
+
ReqoreLink,
|
|
5
|
+
ReqoreP,
|
|
6
|
+
ReqoreTag,
|
|
7
|
+
} from '@qoretechnologies/reqore';
|
|
2
8
|
import {
|
|
3
9
|
IQorusFormField,
|
|
4
10
|
IQorusFormSchema,
|
|
@@ -8,7 +14,12 @@ import React from 'react';
|
|
|
8
14
|
import styled from 'styled-components';
|
|
9
15
|
import { useContextSelector } from 'use-context-selector';
|
|
10
16
|
import { CompactRowContext } from '../compactRowContext';
|
|
11
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
MONO_FONT_STACK,
|
|
19
|
+
StyledCodePreview,
|
|
20
|
+
StyledRowLabel,
|
|
21
|
+
StyledRowValue,
|
|
22
|
+
} from '../compactRowStyles';
|
|
12
23
|
import {
|
|
13
24
|
fieldLabel,
|
|
14
25
|
findAllowedValueOption,
|
|
@@ -19,7 +30,7 @@ import {
|
|
|
19
30
|
recordIdentity,
|
|
20
31
|
titleKeyFor,
|
|
21
32
|
} from '../readFirst';
|
|
22
|
-
import { isUiEncodedValue } from './structuredData';
|
|
33
|
+
import { isEmptyUiEnvelope, isUiEncodedValue } from './structuredData';
|
|
23
34
|
|
|
24
35
|
/**
|
|
25
36
|
* A read-only rendering of a value THROUGH the schema that describes it.
|
|
@@ -35,10 +46,11 @@ import { isUiEncodedValue } from './structuredData';
|
|
|
35
46
|
* So a described value is rendered the way the form asked for it, in the form's
|
|
36
47
|
* own clothes:
|
|
37
48
|
*
|
|
38
|
-
* - Field names use `
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
49
|
+
* - Field names use `StyledFieldLabel` (a `StyledRowLabel` in the read-only
|
|
50
|
+
* eyebrow treatment) and values use `StyledRowValue` — the very components the
|
|
51
|
+
* option rows above are built from. Not a copy of their CSS: a copy is a
|
|
52
|
+
* second definition of "what a form label looks like", and the two drift the
|
|
53
|
+
* first time either is touched.
|
|
42
54
|
* - Values are formatted by the same `formatOptionValue` the rows use, so an
|
|
43
55
|
* `allowed_values` entry resolves to its display name, a bool to Yes/No, a
|
|
44
56
|
* date to the app's format. The same words appear in the summary, the preview
|
|
@@ -138,10 +150,38 @@ const StyledItemTitle = styled.div<{ $color: string; $mono: boolean; $accent: st
|
|
|
138
150
|
${({ $mono }) => ($mono ? `font-family: ${MONO_FONT_STACK}; font-size: 15px;` : '')}
|
|
139
151
|
`;
|
|
140
152
|
|
|
141
|
-
/**
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Does this record have anything to show BELOW its heading?
|
|
155
|
+
*
|
|
156
|
+
* The heading consumes the identifying field, so a record holding only that has
|
|
157
|
+
* an empty body — and a rule drawn down an empty body is a line to nowhere.
|
|
158
|
+
*/
|
|
159
|
+
const hasContentUnderHeading = (record: unknown, schema?: IQorusFormSchema): boolean => {
|
|
160
|
+
if (!isRecord(record)) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
const titleKey = titleKeyFor(record, schema);
|
|
164
|
+
|
|
165
|
+
return Object.entries(record).some(
|
|
166
|
+
([key, value]) => key !== titleKey && value !== undefined && value !== null && value !== ''
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/** The rule tying an item's fields to its number.
|
|
171
|
+
*
|
|
172
|
+
* Same 3px as the heading's accent bar directly above it: at 1px the two read as
|
|
173
|
+
* two different devices stacked on each other rather than one rule running down
|
|
174
|
+
* the item, and the padding drops to 8px so the text stays on the same column.
|
|
175
|
+
*
|
|
176
|
+
* The rule is drawn only when something is actually under the heading. The
|
|
177
|
+
* heading is pulled left of the rule by its own negative margin, so an item
|
|
178
|
+
* whose record holds nothing but its identity showed a 1-2px sliver of line and
|
|
179
|
+
* nothing else — which reads as a rendering artefact, because that is what it
|
|
180
|
+
* was. It keeps a transparent border in that case so the column does not shift.
|
|
181
|
+
*/
|
|
182
|
+
const StyledItemFields = styled.div<{ $border: string; $rule: boolean }>`
|
|
183
|
+
border-left: 3px solid ${({ $border, $rule }) => ($rule ? $border : 'transparent')};
|
|
184
|
+
padding-left: 8px;
|
|
145
185
|
min-width: 0;
|
|
146
186
|
`;
|
|
147
187
|
|
|
@@ -153,6 +193,30 @@ const StyledItemFields = styled.div<{ $border: string }>`
|
|
|
153
193
|
* and leave the items ragged against each other, which reads as a rendering
|
|
154
194
|
* fault rather than as a list.
|
|
155
195
|
*/
|
|
196
|
+
/**
|
|
197
|
+
* A record field's name, in the detail-page vocabulary.
|
|
198
|
+
*
|
|
199
|
+
* Uppercase, tracked and dimmed, so a field name reads as furniture and the
|
|
200
|
+
* VALUE beside it is what the eye lands on. Same treatment the read-only
|
|
201
|
+
* interface panels use, which is the point: an in-editor list of records and a
|
|
202
|
+
* detail-page panel showing the same shape should not look like two components.
|
|
203
|
+
*
|
|
204
|
+
* A variant rather than a change to `StyledRowLabel`: that one is the FORM's own
|
|
205
|
+
* option-row label, and the form's fields are not read-only furniture — they are
|
|
206
|
+
* the things you are about to edit. Composing it keeps the colour, weight base
|
|
207
|
+
* and wrapping behaviour in one place instead of copying them.
|
|
208
|
+
*
|
|
209
|
+
* Applies to the label only. A VALUE is never uppercased: values are frequently
|
|
210
|
+
* case-sensitive — a service method is `onOrderStatus`, not `ONORDERSTATUS` —
|
|
211
|
+
* and the item heading is a value.
|
|
212
|
+
*/
|
|
213
|
+
const StyledFieldLabel = styled(StyledRowLabel)`
|
|
214
|
+
font-size: 12px;
|
|
215
|
+
text-transform: uppercase;
|
|
216
|
+
letter-spacing: 0.06em;
|
|
217
|
+
opacity: 0.55;
|
|
218
|
+
`;
|
|
219
|
+
|
|
156
220
|
const StyledFields = styled.div`
|
|
157
221
|
/* The supporting fields step in under the heading, so the heading keeps the
|
|
158
222
|
left edge to itself. This is the half that answers "the name is written in
|
|
@@ -242,6 +306,12 @@ const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
|
242
306
|
* the `%` ceiling stops one long name from taking the whole row when the inset
|
|
243
307
|
* is narrow — a phone, or a deeply nested level.
|
|
244
308
|
*/
|
|
309
|
+
/** Approximate rendered width of one label character, in px. See `labelColumn`. */
|
|
310
|
+
const LABEL_CHAR_PX = 9.5;
|
|
311
|
+
|
|
312
|
+
/** Gap to the value, so the longest label is not flush against it. */
|
|
313
|
+
const LABEL_SLACK_PX = 14;
|
|
314
|
+
|
|
245
315
|
const labelColumn = (value: unknown, schema: IQorusFormSchema): string => {
|
|
246
316
|
const inner = unwrap(value);
|
|
247
317
|
const records = (Array.isArray(inner) ? inner.map(unwrap) : [inner]).filter(isRecord);
|
|
@@ -253,9 +323,26 @@ const labelColumn = (value: unknown, schema: IQorusFormSchema): string => {
|
|
|
253
323
|
);
|
|
254
324
|
return Math.max(widest, own);
|
|
255
325
|
}, 0);
|
|
256
|
-
|
|
326
|
+
// px, not `ch`. `ch` is the base font's `0` advance and knows nothing about
|
|
327
|
+
// the label's own treatment — uppercase, semibold, `letter-spacing: 0.06em`,
|
|
328
|
+
// at 12px — so it under-measures by about a tenth. With `overflow-wrap:
|
|
329
|
+
// anywhere` on the label, an under-measured column does not ellipsise, it
|
|
330
|
+
// BREAKS THE NAME IN HALF: `LEGACY_FLAG` came out over two lines the moment
|
|
331
|
+
// the labels became uppercase. Erring high costs a few px of gap; erring low
|
|
332
|
+
// costs a broken word.
|
|
333
|
+
return longest ? `min(${Math.ceil(longest * LABEL_CHAR_PX + LABEL_SLACK_PX)}px, 45%)` : '140px';
|
|
257
334
|
};
|
|
258
335
|
|
|
336
|
+
/**
|
|
337
|
+
* `true` when a value is an address a browser can open.
|
|
338
|
+
*
|
|
339
|
+
* Deliberately per VALUE, not per field: a field named `url` frequently holds
|
|
340
|
+
* something that is not one — a bare path, a regex, a template — and offering
|
|
341
|
+
* those a link promises a destination there is not.
|
|
342
|
+
*/
|
|
343
|
+
const isNavigableUrl = (value: unknown): value is string =>
|
|
344
|
+
typeof value === 'string' && /^(https?|wss?|ftps?):\/\//i.test(value);
|
|
345
|
+
|
|
259
346
|
/** One field's value, rendered the way its row above renders it. */
|
|
260
347
|
const FieldValue = ({
|
|
261
348
|
field,
|
|
@@ -301,7 +388,22 @@ const FieldValue = ({
|
|
|
301
388
|
style={{ flexShrink: 0, marginRight: 6, verticalAlign: 'middle' }}
|
|
302
389
|
/>
|
|
303
390
|
: null}
|
|
304
|
-
{
|
|
391
|
+
{isNavigableUrl(field.value) ?
|
|
392
|
+
// The address IS the link — it used to render as inert text with a
|
|
393
|
+
// separate icon button beside it to open it, which is two things
|
|
394
|
+
// where there is one. ReqoreLink renders a real anchor, so
|
|
395
|
+
// middle-click and "copy link address" still work;
|
|
396
|
+
// `stopPropagation` because the row around it toggles.
|
|
397
|
+
<ReqoreLink
|
|
398
|
+
className='schema-view-open-url'
|
|
399
|
+
href={field.value}
|
|
400
|
+
target='_blank'
|
|
401
|
+
rel='noreferrer noopener'
|
|
402
|
+
onClick={(event: React.MouseEvent) => event.stopPropagation()}
|
|
403
|
+
>
|
|
404
|
+
{formatted}
|
|
405
|
+
</ReqoreLink>
|
|
406
|
+
: formatted}
|
|
305
407
|
</span>
|
|
306
408
|
}
|
|
307
409
|
</StyledRowValue>
|
|
@@ -326,92 +428,109 @@ const SchemaRecord = ({
|
|
|
326
428
|
// The SAME host renderer the row above uses for a `code-editor` field. Read
|
|
327
429
|
// from context rather than threaded through props: a nested level is arbitrarily
|
|
328
430
|
// deep, and the renderer is a property of the form, not of any one level.
|
|
329
|
-
const codePreviewRenderer = useContextSelector(
|
|
330
|
-
CompactRowContext,
|
|
331
|
-
(v) => v.codePreviewRenderer
|
|
332
|
-
);
|
|
431
|
+
const codePreviewRenderer = useContextSelector(CompactRowContext, (v) => v.codePreviewRenderer);
|
|
333
432
|
const cHover = useContextSelector(CompactRowContext, (v) => v.cHover);
|
|
334
433
|
|
|
335
434
|
return (
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
435
|
+
<StyledFields className='schema-view-fields'>
|
|
436
|
+
{orderedKeys(record, schema)
|
|
437
|
+
.filter((key) => key !== skipKey)
|
|
438
|
+
.map((key) => {
|
|
439
|
+
const fieldSchema = schema[key] as TQorusFormFieldSchema | undefined;
|
|
440
|
+
const nested = (fieldSchema as { arg_schema?: IQorusFormSchema } | undefined)?.arg_schema;
|
|
441
|
+
// A schema-declared field the form materialised but never filled in
|
|
442
|
+
// arrives as `{type: 'int'}` with no `value` key. `unwrap` cannot see
|
|
443
|
+
// that — `isUiEncodedValue` requires a `value` — so it fell through as
|
|
444
|
+
// CONTENT and was wrapped as one below, and `formatOptionValue` counted
|
|
445
|
+
// the envelope's own `type` key: an untouched Runtime Defaults read
|
|
446
|
+
// "Timeout (Seconds) 1 field" three times over while the sub-form it
|
|
447
|
+
// previews correctly said 0/3 set. `fieldSchema` is what licenses the
|
|
448
|
+
// reading: the schema declares this field, so a `{type}`-only object
|
|
449
|
+
// under it is an empty envelope, never somebody's data.
|
|
450
|
+
const raw = record[key];
|
|
451
|
+
// `!nested` is the discriminator, and it is not a detail: a field that
|
|
452
|
+
// declares its OWN level describes what is inside it, so `{type: 'default'}`
|
|
453
|
+
// there is content — the scheme's Scheme Type, reading "Default RBAC" —
|
|
454
|
+
// not an envelope. Only a leaf can be empty in this sense.
|
|
455
|
+
const value =
|
|
456
|
+
fieldSchema && !nested && isEmptyUiEnvelope(raw) ? undefined : unwrap(raw);
|
|
457
|
+
const label = fieldLabel(key, fieldSchema);
|
|
458
|
+
const labelWithType =
|
|
459
|
+
showTypes && fieldSchema?.type ? `${label} · ${fieldSchema.type}` : label;
|
|
460
|
+
|
|
461
|
+
// A field that describes its own level renders AS that level, indented
|
|
462
|
+
// under its name — the same decision, one step down.
|
|
463
|
+
if (nested && (isRecord(value) || Array.isArray(value))) {
|
|
464
|
+
return (
|
|
465
|
+
<React.Fragment key={key}>
|
|
466
|
+
<StyledNestedLabel>
|
|
467
|
+
<StyledFieldLabel $color={colors.key}>{labelWithType}</StyledFieldLabel>
|
|
468
|
+
</StyledNestedLabel>
|
|
469
|
+
<StyledNested $border={colors.border}>
|
|
470
|
+
<SchemaLevel
|
|
471
|
+
value={value}
|
|
472
|
+
schema={nested}
|
|
473
|
+
showTypes={showTypes}
|
|
474
|
+
colors={colors}
|
|
475
|
+
/>
|
|
476
|
+
</StyledNested>
|
|
477
|
+
</React.Fragment>
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// A code value is source, and source is read as source: the host's renderer
|
|
482
|
+
// gives it the syntax highlighting (and read-only LSP hover) it has in the
|
|
483
|
+
// Source Code field, and "Show more" keeps a long body from taking over the
|
|
484
|
+
// preview. Flattening it to one mono line — which is what a plain value cell
|
|
485
|
+
// does — throws all of that away for a field whose whole content is code.
|
|
486
|
+
if (isCodeField(fieldSchema) && typeof value === 'string') {
|
|
487
|
+
return (
|
|
488
|
+
<React.Fragment key={key}>
|
|
489
|
+
<StyledNestedLabel>
|
|
490
|
+
<StyledFieldLabel $color={colors.key}>{labelWithType}</StyledFieldLabel>
|
|
491
|
+
</StyledNestedLabel>
|
|
492
|
+
<StyledCodeCell onClick={(event) => event.stopPropagation()}>
|
|
493
|
+
<ReqoreCollapsibleContent
|
|
494
|
+
maxCollapsedHeight={96}
|
|
495
|
+
buttonProps={{ className: 'options-readfirst-viewmore' }}
|
|
391
496
|
>
|
|
392
|
-
{
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
497
|
+
{codePreviewRenderer ?
|
|
498
|
+
codePreviewRenderer({
|
|
499
|
+
value,
|
|
500
|
+
name: key,
|
|
501
|
+
schema: fieldSchema,
|
|
502
|
+
options: schema,
|
|
503
|
+
values: record as never,
|
|
504
|
+
})
|
|
505
|
+
: <StyledCodePreview
|
|
506
|
+
className='options-readfirst-code'
|
|
507
|
+
$bg={cHover}
|
|
508
|
+
$border={`${colors.border}88`}
|
|
509
|
+
$fg={colors.key}
|
|
510
|
+
>
|
|
511
|
+
{value}
|
|
512
|
+
</StyledCodePreview>
|
|
513
|
+
}
|
|
514
|
+
</ReqoreCollapsibleContent>
|
|
515
|
+
</StyledCodeCell>
|
|
516
|
+
</React.Fragment>
|
|
517
|
+
);
|
|
518
|
+
}
|
|
400
519
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
520
|
+
return (
|
|
521
|
+
<React.Fragment key={key}>
|
|
522
|
+
<StyledFieldLabel $color={colors.key} title={label}>
|
|
523
|
+
{labelWithType}
|
|
524
|
+
</StyledFieldLabel>
|
|
525
|
+
<FieldValue
|
|
526
|
+
field={{ type: fieldSchema?.type, value } as IQorusFormField}
|
|
527
|
+
fieldSchema={fieldSchema}
|
|
528
|
+
color={colors.key}
|
|
529
|
+
/>
|
|
530
|
+
</React.Fragment>
|
|
531
|
+
);
|
|
532
|
+
})}
|
|
533
|
+
</StyledFields>
|
|
415
534
|
);
|
|
416
535
|
};
|
|
417
536
|
|
|
@@ -437,7 +556,10 @@ const SchemaLevel = ({
|
|
|
437
556
|
return (
|
|
438
557
|
<StyledItem key={index} className='schema-view-item'>
|
|
439
558
|
<StyledMarker $color={colors.muted}>{index + 1}.</StyledMarker>
|
|
440
|
-
<StyledItemFields
|
|
559
|
+
<StyledItemFields
|
|
560
|
+
$border={colors.border}
|
|
561
|
+
$rule={hasContentUnderHeading(record, schema)}
|
|
562
|
+
>
|
|
441
563
|
{isRecord(record) ?
|
|
442
564
|
<>
|
|
443
565
|
{(() => {
|
|
@@ -52,6 +52,26 @@ export const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
|
52
52
|
export const isStructuredContainerValue = (value: unknown): boolean =>
|
|
53
53
|
Array.isArray(value) || isRecord(value);
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* A field the form materialised but left UNSET: `{type: 'int'}`, no `value` key.
|
|
57
|
+
*
|
|
58
|
+
* `fixOptions` omits the key entirely when the value is undefined, so this is
|
|
59
|
+
* what an untouched sub-field of a schema-declared hash looks like on the wire.
|
|
60
|
+
* It is an EMPTY ENVELOPE, never content — and every renderer has to agree on
|
|
61
|
+
* that, because the ones that do not count the envelope's own `type` key and
|
|
62
|
+
* report an unset field as "1 field" while the sub-form correctly says 0 set.
|
|
63
|
+
*
|
|
64
|
+
* Deliberately separate from {@link isUiEncodedValue}, which requires a `value`
|
|
65
|
+
* and therefore cannot see this shape. Callers gate on knowing the field is
|
|
66
|
+
* schema-declared: without a schema saying so, a `{type: …}` object could
|
|
67
|
+
* legitimately be somebody's data.
|
|
68
|
+
*/
|
|
69
|
+
export const isEmptyUiEnvelope = (value: unknown): boolean =>
|
|
70
|
+
isRecord(value) &&
|
|
71
|
+
typeof value.type === 'string' &&
|
|
72
|
+
!('value' in value) &&
|
|
73
|
+
Object.keys(value).every((key) => UI_ENVELOPE_KEYS.has(key));
|
|
74
|
+
|
|
55
75
|
export const isUiEncodedValue = (value: unknown): value is IUiEncodedValue => {
|
|
56
76
|
if (!isRecord(value) || typeof value.type !== 'string' || !('value' in value)) {
|
|
57
77
|
return false;
|
|
@@ -61,6 +61,9 @@ export const PANEL_LEFT_CSS = `calc(${LABEL_COL} + ${COMPACT_ROW_PAD_X + COMPACT
|
|
|
61
61
|
export const StyledCompactPanel = styled(ReqorePanel)<{
|
|
62
62
|
$headerBg: string;
|
|
63
63
|
$nested?: boolean;
|
|
64
|
+
/** True when the toolbar renders no search row (hidden via `compactToolbar`,
|
|
65
|
+
* self-hidden on a single-option form, or no toolbar at all). */
|
|
66
|
+
$tightHeader?: boolean;
|
|
64
67
|
}>`
|
|
65
68
|
> .reqore-panel-title {
|
|
66
69
|
/* The blur + translateZ exist only to make the STICKY top-level toolbar ghost
|
|
@@ -71,7 +74,17 @@ export const StyledCompactPanel = styled(ReqorePanel)<{
|
|
|
71
74
|
'backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); transform: translateZ(0);'
|
|
72
75
|
)}
|
|
73
76
|
padding-top: ${GAP_FROM_SIZE[HEADER_GAP]}px;
|
|
74
|
-
|
|
77
|
+
/* A header with a SEARCH ROW keeps the \`big\` gap below it — a tall
|
|
78
|
+
interactive control earns separation from the content it filters. A
|
|
79
|
+
header without one (search hidden via \`compactToolbar\`, self-hidden on
|
|
80
|
+
a single-option form, or no toolbar at all) is a thin strip, and at
|
|
81
|
+
\`big\` its padding stacked onto the panel content's own 8px into a 26px
|
|
82
|
+
dead band between the completion meter and the first status box —
|
|
83
|
+
rejected on build #140 as "unnecessarily big". \`normal\` lands it at
|
|
84
|
+
the boxes' own 10px rhythm, and scoping the change here keeps every
|
|
85
|
+
search-bearing form pixel-identical. */
|
|
86
|
+
padding-bottom: ${({ $tightHeader }) =>
|
|
87
|
+
$tightHeader ? GAP_FROM_SIZE.normal : GAP_FROM_SIZE[HEADER_GAP]}px;
|
|
75
88
|
}
|
|
76
89
|
|
|
77
90
|
/* Group framing. The HORIZONTAL rule is a real element in the header
|
|
@@ -690,3 +703,27 @@ export const StyledGroupBody = styled.div<{
|
|
|
690
703
|
padding-bottom: 0;
|
|
691
704
|
}
|
|
692
705
|
`;
|
|
706
|
+
|
|
707
|
+
// One absorbed sibling, rendered inside its host row's container rather than
|
|
708
|
+
// on a row of its own (see `absorb_fields`). The pair this exists for is a code
|
|
709
|
+
// editor and its language: reading them as two unrelated rows one above the
|
|
710
|
+
// other said they were separate decisions, and they are not.
|
|
711
|
+
//
|
|
712
|
+
// Laid out as label-then-control on one line so it reads as a property of the
|
|
713
|
+
// element it now sits in, and separated from the editor below it by a hairline
|
|
714
|
+
// rather than a gap — the two share one container, so the seam has to look like
|
|
715
|
+
// a division inside it and not like two stacked things.
|
|
716
|
+
export const StyledAbsorbedField = styled.div`
|
|
717
|
+
display: flex;
|
|
718
|
+
align-items: center;
|
|
719
|
+
gap: 10px;
|
|
720
|
+
min-width: 0;
|
|
721
|
+
padding-bottom: 8px;
|
|
722
|
+
margin-bottom: 8px;
|
|
723
|
+
border-bottom: 1px solid ${({ theme }) => `${theme.main}22`};
|
|
724
|
+
`;
|
|
725
|
+
|
|
726
|
+
export const StyledAbsorbedLabel = styled(ReqoreP)`
|
|
727
|
+
flex: 0 0 auto;
|
|
728
|
+
margin: 0;
|
|
729
|
+
`;
|
|
@@ -24,6 +24,14 @@ export interface ICompactToolbarOptionalField {
|
|
|
24
24
|
*/
|
|
25
25
|
export interface ICompactToolbarContext {
|
|
26
26
|
readOnly?: boolean;
|
|
27
|
+
/** Which parts to render, already resolved from the engine's `compactToolbar`
|
|
28
|
+
* prop — every key present, no undefined to re-default here. */
|
|
29
|
+
parts: {
|
|
30
|
+
completion: boolean;
|
|
31
|
+
search: boolean;
|
|
32
|
+
fields: boolean;
|
|
33
|
+
help: boolean;
|
|
34
|
+
};
|
|
27
35
|
// Completion meter
|
|
28
36
|
invalidCount: number;
|
|
29
37
|
attentionCount: number;
|