@qoretechnologies/reqraft 0.10.47-pr.108.gf8e602d → 0.10.47
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/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +81 -34
- 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 +2 -4
- package/dist/components/form/engine/CompactToolbar.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +139 -112
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowStyles.js +5 -2
- package/dist/components/form/engine/compactRowStyles.js.map +1 -1
- package/package.json +1 -1
- package/src/components/form/engine/CompactRow.tsx +140 -63
- package/src/components/form/engine/CompactToolbar.tsx +16 -14
- package/src/components/form/engine/FormEngine.stories.tsx +290 -69
- package/src/components/form/engine/FormEngine.tsx +226 -184
- package/src/components/form/engine/compactRowStyles.ts +32 -0
- package/src/components/form/fields/template/TemplateField.stories.tsx +1 -1
|
@@ -2734,7 +2734,11 @@ const FormEngineImpl = ({
|
|
|
2734
2734
|
const absorbedBy: Record<string, string> = {};
|
|
2735
2735
|
forEach(options, (option, optionName) => {
|
|
2736
2736
|
const absorb = (option as { absorb_fields?: string[] } | undefined)?.absorb_fields;
|
|
2737
|
-
|
|
2737
|
+
// A read-only form absorbs nothing. A host row shows an absorbed
|
|
2738
|
+
// sibling only inside its EDITOR, and a read view never opens one — so
|
|
2739
|
+
// the language a code field had absorbed would be nowhere on the page.
|
|
2740
|
+
// Every set field gets a row of its own instead.
|
|
2741
|
+
if (!absorb?.length || readOnly) return;
|
|
2738
2742
|
absorb.forEach((absorbedName) => {
|
|
2739
2743
|
// Only absorb a sibling that exists and is not already spoken for; a
|
|
2740
2744
|
// schema naming a missing field must not make the field disappear.
|
|
@@ -2769,11 +2773,15 @@ const FormEngineImpl = ({
|
|
|
2769
2773
|
// only). availableOptions (listed) and filteredOptions (these) are disjoint —
|
|
2770
2774
|
// the former is built from fixedValue keys, the latter excludes them — so a
|
|
2771
2775
|
// field is never both a listed and a hidden row.
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2776
|
+
// Not in a read-only form: a not-yet-added field is an offer to add it,
|
|
2777
|
+
// and a reader cannot take it up.
|
|
2778
|
+
if (!readOnly) {
|
|
2779
|
+
forEach(filteredOptions, (_schema, optionName) => {
|
|
2780
|
+
if (matchesFilters(optionName)) {
|
|
2781
|
+
pushRow(optionName, true);
|
|
2782
|
+
}
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2777
2785
|
|
|
2778
2786
|
// User sort (Fields menu → "Sort by"), applied WITHIN each group so the
|
|
2779
2787
|
// group sections and the required-group rails are preserved. Schema order is
|
|
@@ -2859,9 +2867,20 @@ const FormEngineImpl = ({
|
|
|
2859
2867
|
settledBucket.current[entry.name] = fresh;
|
|
2860
2868
|
return fresh;
|
|
2861
2869
|
};
|
|
2870
|
+
// Read-only: one list, not three boxes. "Needs attention" is an
|
|
2871
|
+
// instruction, and a reader has nothing to do with it — a required field
|
|
2872
|
+
// nobody filled in is shown unset, without the amber. "Optional" holds
|
|
2873
|
+
// the fields nobody set, which in a read view is a list of things that
|
|
2874
|
+
// are not there, so it is left out altogether.
|
|
2875
|
+
const readBucketOf = (entry: TRowEntry): TFormEngineBoxKey | undefined => {
|
|
2876
|
+
const fresh = stableBucketOf(entry);
|
|
2877
|
+
if (!readOnly) return fresh;
|
|
2878
|
+
return fresh === 'optional' ? undefined : 'set';
|
|
2879
|
+
};
|
|
2862
2880
|
groupKeys.forEach((groupName) => {
|
|
2863
2881
|
grouped[groupName].forEach((entry) => {
|
|
2864
|
-
const b =
|
|
2882
|
+
const b = readBucketOf(entry);
|
|
2883
|
+
if (!b) return;
|
|
2865
2884
|
if (!buckets[b][groupName]) {
|
|
2866
2885
|
buckets[b][groupName] = [];
|
|
2867
2886
|
bucketGroups[b].push(groupName);
|
|
@@ -3003,11 +3022,7 @@ const FormEngineImpl = ({
|
|
|
3003
3022
|
/** @param only when given, render just these rows of the group — the group
|
|
3004
3023
|
can be split across the shown and folded halves of a capped box, and its
|
|
3005
3024
|
label must appear above whichever half holds its first row. */
|
|
3006
|
-
const renderBoxGroup = (
|
|
3007
|
-
boxKey: TFormEngineBoxKey,
|
|
3008
|
-
groupName: string,
|
|
3009
|
-
only?: Set<string>
|
|
3010
|
-
) => {
|
|
3025
|
+
const renderBoxGroup = (boxKey: TFormEngineBoxKey, groupName: string, only?: Set<string>) => {
|
|
3011
3026
|
const groupConfig = groups?.[groupName];
|
|
3012
3027
|
const rows =
|
|
3013
3028
|
only ?
|
|
@@ -3093,142 +3108,23 @@ const FormEngineImpl = ({
|
|
|
3093
3108
|
...compactPanelProps?.contentStyle,
|
|
3094
3109
|
}}
|
|
3095
3110
|
>
|
|
3096
|
-
{size(groupKeys) === 0 ?
|
|
3111
|
+
{size(groupKeys) === 0 || (readOnly && !bucketCount('set')) ?
|
|
3097
3112
|
<ReqoreMessage flat opaque={false} size='small'>
|
|
3098
|
-
|
|
3113
|
+
{readOnly && !query ?
|
|
3114
|
+
'Nothing is set.'
|
|
3115
|
+
: 'No fields match the current filters.'}
|
|
3099
3116
|
</ReqoreMessage>
|
|
3100
3117
|
: null}
|
|
3101
3118
|
|
|
3102
|
-
{
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
// Only a box that was opened SOLELY to reveal a preselected
|
|
3112
|
-
// row splits its body; a box that renders open anyway shows
|
|
3113
|
-
// all of its groups as before.
|
|
3114
|
-
const splitForPreselected = wouldCollapse && hasPreselectedRows;
|
|
3115
|
-
|
|
3116
|
-
// Which rows this box shows, and which fold away, decided at
|
|
3117
|
-
// ROW level because `maxFieldsShown` counts fields — a
|
|
3118
|
-
// group-level split can only ever cut at a group boundary.
|
|
3119
|
-
// Group order is preserved so the visible rows read in the
|
|
3120
|
-
// same order they would if nothing were folded.
|
|
3121
|
-
const orderedRows: TRowEntry[] = groupsInBox.flatMap(
|
|
3122
|
-
(groupName) => buckets[box.key][groupName] || []
|
|
3123
|
-
);
|
|
3124
|
-
// Optional's own rule first: a box opened solely to reveal
|
|
3125
|
-
// preselected rows offers those, not an arbitrary first-N.
|
|
3126
|
-
const candidateRows =
|
|
3127
|
-
splitForPreselected ? orderedRows.filter(isPreselectedRow) : orderedRows;
|
|
3128
|
-
// …then the cap applies to whatever that left.
|
|
3129
|
-
const capped =
|
|
3130
|
-
maxFieldsShown !== undefined && candidateRows.length > maxFieldsShown;
|
|
3131
|
-
const shownRows = capped ? candidateRows.slice(0, maxFieldsShown) : candidateRows;
|
|
3132
|
-
const shownRowSet = new Set(shownRows.map((entry) => entry.name));
|
|
3133
|
-
// Everything the box holds that is not on show. Once the
|
|
3134
|
-
// reader asks for more they get the whole box — withholding
|
|
3135
|
-
// the non-preselected remainder behind a second control is
|
|
3136
|
-
// the same defect one level down.
|
|
3137
|
-
const deferredRowSet = new Set(
|
|
3138
|
-
orderedRows
|
|
3139
|
-
.filter((entry) => !shownRowSet.has(entry.name))
|
|
3140
|
-
.map((entry) => entry.name)
|
|
3141
|
-
);
|
|
3142
|
-
const deferredCount = deferredRowSet.size;
|
|
3143
|
-
const shownGroups = groupsInBox.filter((groupName) =>
|
|
3144
|
-
(buckets[box.key][groupName] || []).some((entry) =>
|
|
3145
|
-
shownRowSet.has(entry.name)
|
|
3146
|
-
)
|
|
3147
|
-
);
|
|
3148
|
-
const deferredGroups = groupsInBox.filter((groupName) =>
|
|
3149
|
-
(buckets[box.key][groupName] || []).some(
|
|
3150
|
-
(entry) => !shownRowSet.has(entry.name)
|
|
3151
|
-
)
|
|
3152
|
-
);
|
|
3153
|
-
const moreOpen = Boolean(query) || expandedMoreBoxes.includes(box.key);
|
|
3154
|
-
const accent =
|
|
3155
|
-
box.key === 'attention' ? cWarning
|
|
3156
|
-
: box.key === 'set' ? cSuccess
|
|
3157
|
-
: cMuted;
|
|
3158
|
-
// The muted "Optional" box reads as a quieter, recessed
|
|
3159
|
-
// surface — a touch darker than the page rather than the
|
|
3160
|
-
// faint grey tint the accent would give.
|
|
3161
|
-
const boxBg =
|
|
3162
|
-
box.key === 'optional' ?
|
|
3163
|
-
changeDarkness(getMainBackgroundColor(theme), 0.06)
|
|
3164
|
-
: undefined;
|
|
3165
|
-
return (
|
|
3166
|
-
<StyledStatusBox
|
|
3167
|
-
$accent={accent}
|
|
3168
|
-
$bg={boxBg}
|
|
3169
|
-
key={box.key}
|
|
3170
|
-
ref={(node: HTMLDivElement | null) => {
|
|
3171
|
-
statusBoxRefs.current[box.key] = node;
|
|
3172
|
-
}}
|
|
3173
|
-
// Opening arms the reveal; collapsing disarms it, so a
|
|
3174
|
-
// close never scrolls.
|
|
3175
|
-
onCollapseChange={(collapsed) =>
|
|
3176
|
-
setOpenedStatusBox(collapsed ? undefined : box.key)
|
|
3177
|
-
}
|
|
3178
|
-
flat
|
|
3179
|
-
minimal
|
|
3180
|
-
collapseButtonProps={{ flat: true, minimal: true, size: 'small' }}
|
|
3181
|
-
collapsible
|
|
3182
|
-
// Which boxes start closed is the consumer's call
|
|
3183
|
-
// (`compactCollapsedGroups`, default `['optional']`: the
|
|
3184
|
-
// Optional box holds every not-yet-added field, so a form
|
|
3185
|
-
// opens on what is in use). Two rules override it:
|
|
3186
|
-
//
|
|
3187
|
-
// A SEARCH forces every box open — ReqorePanel unmounts
|
|
3188
|
-
// collapsed content, so a match inside a closed box would
|
|
3189
|
-
// be unreachable. (isCollapsed is the panel's controllable
|
|
3190
|
-
// state; manual toggling still works with no query set.)
|
|
3191
|
-
//
|
|
3192
|
-
// The Optional box stays open when it is the ONLY box,
|
|
3193
|
-
// because collapsing it renders a card that looks empty and
|
|
3194
|
-
// broken: an auth profile's Authorization block, whose nine
|
|
3195
|
-
// requirement fields are all optional, showed as a titled
|
|
3196
|
-
// card containing one collapsed "Optional 9" strip and
|
|
3197
|
-
// nothing else. This does not generalise to the other two —
|
|
3198
|
-
// a lone Set box collapsed is a deliberate ask, and its
|
|
3199
|
-
// header still reports what is in there.
|
|
3200
|
-
//
|
|
3201
|
-
// A PRESELECTED row overrides it too: the schema asked
|
|
3202
|
-
// for that field to be visible on a new form, so the
|
|
3203
|
-
// box it lands in opens. The groups it does NOT appear
|
|
3204
|
-
// in stay behind an inner collapse below, so opening
|
|
3205
|
-
// the box reveals the preselected fields without also
|
|
3206
|
-
// dumping every not-yet-added optional field on the
|
|
3207
|
-
// author.
|
|
3208
|
-
isCollapsed={wouldCollapse && !hasPreselectedRows}
|
|
3209
|
-
label={
|
|
3210
|
-
<StyledGroupHeader>
|
|
3211
|
-
<ReqoreP effect={{ weight: 'bold' }} size='normal'>
|
|
3212
|
-
{box.label}
|
|
3213
|
-
</ReqoreP>
|
|
3214
|
-
<ReqoreTag
|
|
3215
|
-
size='small'
|
|
3216
|
-
minimal
|
|
3217
|
-
compact
|
|
3218
|
-
intent={box.intent}
|
|
3219
|
-
label={String(count)}
|
|
3220
|
-
/>
|
|
3221
|
-
</StyledGroupHeader>
|
|
3222
|
-
}
|
|
3223
|
-
icon={box.icon}
|
|
3224
|
-
className='options-readfirst-group'
|
|
3225
|
-
padded={false}
|
|
3226
|
-
contentStyle={{ padding: '4px 4px 6px' }}
|
|
3227
|
-
>
|
|
3228
|
-
{/* ONE group body per box: every field block (and the
|
|
3229
|
-
thin schema sub-labels) is a direct flex child, so the
|
|
3230
|
-
inter-field gap is identical everywhere — including
|
|
3231
|
-
across schema-group boundaries. */}
|
|
3119
|
+
{/* A read-only form is one flat list of what is set: no
|
|
3120
|
+
"Set" heading with a count over it (there is nothing else
|
|
3121
|
+
it could be), no green tint (a colour that means "done"
|
|
3122
|
+
on a form nobody is doing), nothing to collapse — and no
|
|
3123
|
+
`maxFieldsShown` fold: "12 more fields" behind a click is
|
|
3124
|
+
the reader's record withheld, and a page scrolls. */}
|
|
3125
|
+
{readOnly ?
|
|
3126
|
+
bucketCount('set') ?
|
|
3127
|
+
<div className='options-readfirst-read' style={{ padding: '4px 4px 6px' }}>
|
|
3232
3128
|
<StyledGroupBody
|
|
3233
3129
|
$divider={cDivider}
|
|
3234
3130
|
$hover={cHover}
|
|
@@ -3239,52 +3135,198 @@ const FormEngineImpl = ({
|
|
|
3239
3135
|
$moreBorder={cMoreBorder}
|
|
3240
3136
|
className={compactNarrow ? 'readfirst-narrow' : undefined}
|
|
3241
3137
|
>
|
|
3242
|
-
{
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3138
|
+
{bucketGroups.set.map((groupName) => renderBoxGroup('set', groupName))}
|
|
3139
|
+
</StyledGroupBody>
|
|
3140
|
+
</div>
|
|
3141
|
+
: null
|
|
3142
|
+
: STATUS_BOXES.map((box) => {
|
|
3143
|
+
const groupsInBox = bucketGroups[box.key];
|
|
3144
|
+
const count = bucketCount(box.key);
|
|
3145
|
+
if (!count) return null;
|
|
3146
|
+
const hasPreselectedRows = boxHasPreselectedRows(box.key);
|
|
3147
|
+
const wouldCollapse =
|
|
3148
|
+
compactCollapsedGroups.includes(box.key) &&
|
|
3149
|
+
!query &&
|
|
3150
|
+
!(box.key === 'optional' && onlyOptionalRows);
|
|
3151
|
+
// Only a box that was opened SOLELY to reveal a preselected
|
|
3152
|
+
// row splits its body; a box that renders open anyway shows
|
|
3153
|
+
// all of its groups as before.
|
|
3154
|
+
const splitForPreselected = wouldCollapse && hasPreselectedRows;
|
|
3155
|
+
|
|
3156
|
+
// Which rows this box shows, and which fold away, decided at
|
|
3157
|
+
// ROW level because `maxFieldsShown` counts fields — a
|
|
3158
|
+
// group-level split can only ever cut at a group boundary.
|
|
3159
|
+
// Group order is preserved so the visible rows read in the
|
|
3160
|
+
// same order they would if nothing were folded.
|
|
3161
|
+
const orderedRows: TRowEntry[] = groupsInBox.flatMap(
|
|
3162
|
+
(groupName) => buckets[box.key][groupName] || []
|
|
3163
|
+
);
|
|
3164
|
+
// Optional's own rule first: a box opened solely to reveal
|
|
3165
|
+
// preselected rows offers those, not an arbitrary first-N.
|
|
3166
|
+
const candidateRows =
|
|
3167
|
+
splitForPreselected ? orderedRows.filter(isPreselectedRow) : orderedRows;
|
|
3168
|
+
// …then the cap applies to whatever that left.
|
|
3169
|
+
const capped =
|
|
3170
|
+
maxFieldsShown !== undefined && candidateRows.length > maxFieldsShown;
|
|
3171
|
+
const shownRows =
|
|
3172
|
+
capped ? candidateRows.slice(0, maxFieldsShown) : candidateRows;
|
|
3173
|
+
const shownRowSet = new Set(shownRows.map((entry) => entry.name));
|
|
3174
|
+
// Everything the box holds that is not on show. Once the
|
|
3175
|
+
// reader asks for more they get the whole box — withholding
|
|
3176
|
+
// the non-preselected remainder behind a second control is
|
|
3177
|
+
// the same defect one level down.
|
|
3178
|
+
const deferredRowSet = new Set(
|
|
3179
|
+
orderedRows
|
|
3180
|
+
.filter((entry) => !shownRowSet.has(entry.name))
|
|
3181
|
+
.map((entry) => entry.name)
|
|
3182
|
+
);
|
|
3183
|
+
const deferredCount = deferredRowSet.size;
|
|
3184
|
+
const shownGroups = groupsInBox.filter((groupName) =>
|
|
3185
|
+
(buckets[box.key][groupName] || []).some((entry) =>
|
|
3186
|
+
shownRowSet.has(entry.name)
|
|
3187
|
+
)
|
|
3188
|
+
);
|
|
3189
|
+
const deferredGroups = groupsInBox.filter((groupName) =>
|
|
3190
|
+
(buckets[box.key][groupName] || []).some(
|
|
3191
|
+
(entry) => !shownRowSet.has(entry.name)
|
|
3192
|
+
)
|
|
3193
|
+
);
|
|
3194
|
+
const moreOpen = Boolean(query) || expandedMoreBoxes.includes(box.key);
|
|
3195
|
+
const accent =
|
|
3196
|
+
box.key === 'attention' ? cWarning
|
|
3197
|
+
: box.key === 'set' ? cSuccess
|
|
3198
|
+
: cMuted;
|
|
3199
|
+
// The muted "Optional" box reads as a quieter, recessed
|
|
3200
|
+
// surface — a touch darker than the page rather than the
|
|
3201
|
+
// faint grey tint the accent would give.
|
|
3202
|
+
const boxBg =
|
|
3203
|
+
box.key === 'optional' ?
|
|
3204
|
+
changeDarkness(getMainBackgroundColor(theme), 0.06)
|
|
3205
|
+
: undefined;
|
|
3206
|
+
return (
|
|
3207
|
+
<StyledStatusBox
|
|
3208
|
+
$accent={accent}
|
|
3209
|
+
$bg={boxBg}
|
|
3210
|
+
key={box.key}
|
|
3211
|
+
ref={(node: HTMLDivElement | null) => {
|
|
3212
|
+
statusBoxRefs.current[box.key] = node;
|
|
3213
|
+
}}
|
|
3214
|
+
// Opening arms the reveal; collapsing disarms it, so a
|
|
3215
|
+
// close never scrolls.
|
|
3216
|
+
onCollapseChange={(collapsed) =>
|
|
3217
|
+
setOpenedStatusBox(collapsed ? undefined : box.key)
|
|
3218
|
+
}
|
|
3219
|
+
flat
|
|
3220
|
+
minimal
|
|
3221
|
+
collapseButtonProps={{ flat: true, minimal: true, size: 'small' }}
|
|
3222
|
+
collapsible
|
|
3223
|
+
// Which boxes start closed is the consumer's call
|
|
3224
|
+
// (`compactCollapsedGroups`, default `['optional']`: the
|
|
3225
|
+
// Optional box holds every not-yet-added field, so a form
|
|
3226
|
+
// opens on what is in use). Two rules override it:
|
|
3227
|
+
//
|
|
3228
|
+
// A SEARCH forces every box open — ReqorePanel unmounts
|
|
3229
|
+
// collapsed content, so a match inside a closed box would
|
|
3230
|
+
// be unreachable. (isCollapsed is the panel's controllable
|
|
3231
|
+
// state; manual toggling still works with no query set.)
|
|
3232
|
+
//
|
|
3233
|
+
// The Optional box stays open when it is the ONLY box,
|
|
3234
|
+
// because collapsing it renders a card that looks empty and
|
|
3235
|
+
// broken: an auth profile's Authorization block, whose nine
|
|
3236
|
+
// requirement fields are all optional, showed as a titled
|
|
3237
|
+
// card containing one collapsed "Optional 9" strip and
|
|
3238
|
+
// nothing else. This does not generalise to the other two —
|
|
3239
|
+
// a lone Set box collapsed is a deliberate ask, and its
|
|
3240
|
+
// header still reports what is in there.
|
|
3241
|
+
//
|
|
3242
|
+
// A PRESELECTED row overrides it too: the schema asked
|
|
3243
|
+
// for that field to be visible on a new form, so the
|
|
3244
|
+
// box it lands in opens. The groups it does NOT appear
|
|
3245
|
+
// in stay behind an inner collapse below, so opening
|
|
3246
|
+
// the box reveals the preselected fields without also
|
|
3247
|
+
// dumping every not-yet-added optional field on the
|
|
3248
|
+
// author.
|
|
3249
|
+
isCollapsed={wouldCollapse && !hasPreselectedRows}
|
|
3250
|
+
label={
|
|
3251
|
+
<StyledGroupHeader>
|
|
3252
|
+
<ReqoreP effect={{ weight: 'bold' }} size='normal'>
|
|
3253
|
+
{box.label}
|
|
3254
|
+
</ReqoreP>
|
|
3255
|
+
<ReqoreTag
|
|
3256
|
+
size='small'
|
|
3257
|
+
minimal
|
|
3258
|
+
compact
|
|
3259
|
+
intent={box.intent}
|
|
3260
|
+
label={String(count)}
|
|
3261
|
+
/>
|
|
3262
|
+
</StyledGroupHeader>
|
|
3263
|
+
}
|
|
3264
|
+
icon={box.icon}
|
|
3265
|
+
className='options-readfirst-group'
|
|
3266
|
+
padded={false}
|
|
3267
|
+
contentStyle={{ padding: '4px 4px 6px' }}
|
|
3268
|
+
>
|
|
3269
|
+
{/* ONE group body per box: every field block (and the
|
|
3270
|
+
thin schema sub-labels) is a direct flex child, so the
|
|
3271
|
+
inter-field gap is identical everywhere — including
|
|
3272
|
+
across schema-group boundaries. */}
|
|
3273
|
+
<StyledGroupBody
|
|
3274
|
+
$divider={cDivider}
|
|
3275
|
+
$hover={cHover}
|
|
3276
|
+
$focus={cWarning}
|
|
3277
|
+
$success={cSuccess}
|
|
3278
|
+
$rowBg={cRowBg}
|
|
3279
|
+
$lineColor={cGroupLine}
|
|
3280
|
+
$moreBorder={cMoreBorder}
|
|
3281
|
+
className={compactNarrow ? 'readfirst-narrow' : undefined}
|
|
3282
|
+
>
|
|
3283
|
+
{shownGroups.map((groupName) =>
|
|
3284
|
+
renderBoxGroup(box.key, groupName, shownRowSet)
|
|
3285
|
+
)}
|
|
3286
|
+
{/* The control lives INSIDE the group body, as the last
|
|
3246
3287
|
row, so it inherits the rows' grid, padding and
|
|
3247
3288
|
hover and lines up with them. Outside it was a bare
|
|
3248
3289
|
line of text in the box's corner, which is what the
|
|
3249
3290
|
build #154 review rejected. */}
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
}
|
|
3262
|
-
>
|
|
3263
|
-
<ReqoreP size='small' effect={{ opacity: 0.8 }}>
|
|
3264
|
-
{moreOpen ?
|
|
3265
|
-
'Show fewer'
|
|
3266
|
-
: `${deferredCount} more ${box.key === 'optional' ? 'optional ' : ''}field${deferredCount === 1 ? '' : 's'}`
|
|
3291
|
+
{deferredCount ?
|
|
3292
|
+
<button
|
|
3293
|
+
type='button'
|
|
3294
|
+
className='readfirst-row readfirst-more-row options-readfirst-more'
|
|
3295
|
+
aria-expanded={moreOpen}
|
|
3296
|
+
onClick={() =>
|
|
3297
|
+
setExpandedMoreBoxes((prev) =>
|
|
3298
|
+
prev.includes(box.key) ?
|
|
3299
|
+
prev.filter((k) => k !== box.key)
|
|
3300
|
+
: [...prev, box.key]
|
|
3301
|
+
)
|
|
3267
3302
|
}
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3303
|
+
>
|
|
3304
|
+
<ReqoreP size='small' effect={{ opacity: 0.8 }}>
|
|
3305
|
+
{moreOpen ?
|
|
3306
|
+
'Show fewer'
|
|
3307
|
+
: `${deferredCount} more ${box.key === 'optional' ? 'optional ' : ''}field${deferredCount === 1 ? '' : 's'}`
|
|
3308
|
+
}
|
|
3309
|
+
</ReqoreP>
|
|
3310
|
+
<ReqoreIcon
|
|
3311
|
+
icon='ArrowDownSLine'
|
|
3312
|
+
size='small'
|
|
3313
|
+
className='readfirst-more-chevron'
|
|
3314
|
+
effect={{ opacity: 0.8 }}
|
|
3315
|
+
/>
|
|
3316
|
+
</button>
|
|
3317
|
+
: null}
|
|
3318
|
+
{/* A search forces the fold open for the same reason
|
|
3278
3319
|
the boxes open: an unmounted row cannot be found. */}
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3320
|
+
{deferredCount && moreOpen ?
|
|
3321
|
+
deferredGroups.map((groupName) =>
|
|
3322
|
+
renderBoxGroup(box.key, groupName, deferredRowSet)
|
|
3323
|
+
)
|
|
3324
|
+
: null}
|
|
3325
|
+
</StyledGroupBody>
|
|
3326
|
+
</StyledStatusBox>
|
|
3327
|
+
);
|
|
3328
|
+
})
|
|
3329
|
+
}
|
|
3288
3330
|
</StyledCompactPanel>
|
|
3289
3331
|
|
|
3290
3332
|
{/* Batched commit: the Save/Discard bar docks bottom-right, floating
|
|
@@ -354,6 +354,22 @@ export const StyledRowValue = styled.div<{ $color: string; $empty?: boolean }>`
|
|
|
354
354
|
gap: 4px;
|
|
355
355
|
margin-top: 4px;
|
|
356
356
|
}
|
|
357
|
+
/* Read-only companions of the value: what a chosen option means, on its own
|
|
358
|
+
line under it; and a long text drawn in full in the inset. */
|
|
359
|
+
.options-readfirst-choice-desc {
|
|
360
|
+
flex-basis: 100%;
|
|
361
|
+
width: 100%;
|
|
362
|
+
min-width: 0;
|
|
363
|
+
font-size: 12px;
|
|
364
|
+
line-height: 1.4;
|
|
365
|
+
opacity: 0.6;
|
|
366
|
+
}
|
|
367
|
+
.options-readfirst-text {
|
|
368
|
+
color: ${({ $color }) => $color};
|
|
369
|
+
white-space: pre-wrap;
|
|
370
|
+
overflow-wrap: anywhere;
|
|
371
|
+
line-height: 1.5;
|
|
372
|
+
}
|
|
357
373
|
`;
|
|
358
374
|
|
|
359
375
|
export const StyledRowActions = styled.div`
|
|
@@ -634,6 +650,22 @@ export const StyledGroupBody = styled.div<{
|
|
|
634
650
|
.readfirst-row-editing > div:nth-child(3) {
|
|
635
651
|
padding-top: 3px;
|
|
636
652
|
}
|
|
653
|
+
/* A read-only row is not a control. No pointer and no hover tint, because a
|
|
654
|
+
click does nothing; and its value wraps in full — the row is the only
|
|
655
|
+
rendering the value gets, so a line cut with an ellipsis would be the
|
|
656
|
+
reader's whole answer. */
|
|
657
|
+
.readfirst-row-read {
|
|
658
|
+
cursor: default;
|
|
659
|
+
}
|
|
660
|
+
.readfirst-row-read:hover {
|
|
661
|
+
background: transparent;
|
|
662
|
+
}
|
|
663
|
+
.readfirst-row-read .options-readfirst-valuetext {
|
|
664
|
+
white-space: pre-wrap;
|
|
665
|
+
overflow: visible;
|
|
666
|
+
text-overflow: clip;
|
|
667
|
+
overflow-wrap: anywhere;
|
|
668
|
+
}
|
|
637
669
|
.readfirst-row-editing:hover {
|
|
638
670
|
background: ${({ $hover }) => $hover};
|
|
639
671
|
}
|
|
@@ -264,7 +264,7 @@ export const AutoComponent: StoryObj<typeof meta> = {
|
|
|
264
264
|
docs: {
|
|
265
265
|
description: {
|
|
266
266
|
story:
|
|
267
|
-
'Renders TemplateField
|
|
267
|
+
'Renders TemplateField wrapping the AutoFormField dispatcher — the operator can pick a type from the auto picker or switch to a template value.',
|
|
268
268
|
},
|
|
269
269
|
},
|
|
270
270
|
},
|