@qoretechnologies/reqraft 0.10.39 → 0.10.40-pr.100.g0cc0f4b
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 +23 -13
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +8 -2
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +14 -0
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.js +11 -1
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/dist/helpers/validations.d.ts +18 -0
- package/dist/helpers/validations.d.ts.map +1 -1
- package/dist/helpers/validations.js +57 -16
- 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/form/engine/CompactRow.tsx +37 -15
- package/src/components/form/engine/FormEngine.tsx +5 -0
- package/src/components/form/engine/readFirst.ts +15 -0
- package/src/components/form/fields/auto/AutoFormField.tsx +12 -0
- package/src/helpers/validations.ts +72 -29
- package/src/index.tsx +1 -0
|
@@ -23,7 +23,7 @@ import isEqual from 'lodash/isEqual';
|
|
|
23
23
|
import size from 'lodash/size';
|
|
24
24
|
import React, { memo } from 'react';
|
|
25
25
|
import { useContextSelector } from 'use-context-selector';
|
|
26
|
-
import { hasAllDependenciesFullfilled } from '../../../helpers/validations';
|
|
26
|
+
import { hasAllDependenciesFullfilled, parseDependency } from '../../../helpers/validations';
|
|
27
27
|
import {
|
|
28
28
|
findTemplate,
|
|
29
29
|
getTemplateTagStyle,
|
|
@@ -1622,35 +1622,45 @@ export const CompactRow = memo(
|
|
|
1622
1622
|
showFieldTypes ?
|
|
1623
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}]` : ''}>`
|
|
1624
1624
|
: null;
|
|
1625
|
-
//
|
|
1626
|
-
//
|
|
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).
|
|
1627
1633
|
const fieldDisabled =
|
|
1628
1634
|
!hidden &&
|
|
1629
1635
|
!readOnly &&
|
|
1630
1636
|
(!!schema?.disabled ||
|
|
1637
|
+
fieldReadonly ||
|
|
1631
1638
|
!hasAllDependenciesFullfilled(schema?.depends_on, availableOptions, options || {}));
|
|
1632
1639
|
const fieldDisabledReason =
|
|
1633
|
-
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';
|
|
1634
1643
|
// Dependency contract: top-level entries must ALL hold; a nested array
|
|
1635
|
-
// 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.
|
|
1636
1647
|
const dependencyEntries =
|
|
1637
|
-
fieldDisabled && !schema?.disabled ?
|
|
1648
|
+
fieldDisabled && !schema?.disabled && !fieldReadonly ?
|
|
1638
1649
|
((schema?.depends_on || []) as (string | string[])[])
|
|
1639
1650
|
: [];
|
|
1640
1651
|
const describeDependency = (dep: string) => {
|
|
1641
|
-
const
|
|
1642
|
-
const depName = eqIndex === -1 ? dep : dep.slice(0, eqIndex);
|
|
1643
|
-
const expected = eqIndex === -1 ? undefined : dep.slice(eqIndex + 1);
|
|
1652
|
+
const { name: depName, op, value: expected } = parseDependency(dep);
|
|
1644
1653
|
const depLabel = (options?.[depName]?.display_name as string) || depName;
|
|
1645
1654
|
const depValue = (availableOptions as TQorusForm)?.[depName]?.value;
|
|
1646
1655
|
return {
|
|
1647
1656
|
name: depName,
|
|
1648
1657
|
exists: !!options?.[depName],
|
|
1649
|
-
label:
|
|
1658
|
+
label: !op ? depLabel : `${depLabel} ${op === '=' ? '=' : '≠'} ${expected}`,
|
|
1650
1659
|
fulfilled:
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
:
|
|
1660
|
+
!op ? !isOptionValueEmpty(depValue)
|
|
1661
|
+
: depValue == null ? false
|
|
1662
|
+
: op === '=' ? String(depValue) === expected
|
|
1663
|
+
: String(depValue) !== expected,
|
|
1654
1664
|
};
|
|
1655
1665
|
};
|
|
1656
1666
|
const depHighlightNames = (flatten(dependencyEntries as never[]) as string[])
|
|
@@ -1869,8 +1879,20 @@ export const CompactRow = memo(
|
|
|
1869
1879
|
reads less like a summary than like a second, disagreeing answer.
|
|
1870
1880
|
An UNDESCRIBED value keeps its summary: the data tree beneath it
|
|
1871
1881
|
says nothing about what the value MEANS, so the summary is still
|
|
1872
|
-
the only line that does.
|
|
1873
|
-
|
|
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 : (
|
|
1874
1896
|
<span className='options-readfirst-valuetext'>
|
|
1875
1897
|
{hidden || empty ? '—' : renderReadFirstValue(optionField, schema, formatted)}
|
|
1876
1898
|
</span>
|
|
@@ -2340,6 +2340,11 @@ const FormEngineImpl = ({
|
|
|
2340
2340
|
allowed_values_creatable={options?.[optionName]?.allowed_values_creatable}
|
|
2341
2341
|
disabled={
|
|
2342
2342
|
options?.[optionName]?.disabled ||
|
|
2343
|
+
// a system-owned field states that as `readonly`; only the older
|
|
2344
|
+
// compat projection also sent `disabled`, so honouring only
|
|
2345
|
+
// `disabled` rendered an editable control for a value the user
|
|
2346
|
+
// cannot change (see CompactRow's `fieldReadonly`)
|
|
2347
|
+
(options?.[optionName] as { readonly?: boolean } | undefined)?.readonly ||
|
|
2343
2348
|
readOnly ||
|
|
2344
2349
|
!hasAllDependenciesFullfilled(
|
|
2345
2350
|
options?.[optionName]?.depends_on,
|
|
@@ -676,12 +676,27 @@ export const getHashEntries = (
|
|
|
676
676
|
// whose entries are `{ type: 'string', value: 'x' }`) — recognise those by
|
|
677
677
|
// their strict shape (a string `type`, a `value`, and no foreign keys) so
|
|
678
678
|
// the sub-row shows 'x' rather than counting the envelope's own keys.
|
|
679
|
+
// `'value' in raw` is FALSE for a materialised-but-unset field: `fixOptions`
|
|
680
|
+
// omits the key entirely when the value is undefined, leaving `{type: 'x'}`.
|
|
681
|
+
// Requiring `value` therefore classed that envelope as a raw hash VALUE and
|
|
682
|
+
// counted its own `type` key, so an untouched Runtime Defaults collapsed to
|
|
683
|
+
// "Timeout (Seconds) 1 field" three times over while the sub-form correctly
|
|
684
|
+
// said 0/3 set. With a sub-schema present, a `type`-only object is an empty
|
|
685
|
+
// envelope, never content.
|
|
686
|
+
const isEmptyEnvelope =
|
|
687
|
+
!!subSchema &&
|
|
688
|
+
!!raw &&
|
|
689
|
+
typeof raw === 'object' &&
|
|
690
|
+
!Array.isArray(raw) &&
|
|
691
|
+
!('value' in (raw as object)) &&
|
|
692
|
+
Object.keys(raw as object).every((k) => k === 'type' || k === 'is_expression');
|
|
679
693
|
const isFieldShape =
|
|
680
694
|
(!!subSchema &&
|
|
681
695
|
!!raw &&
|
|
682
696
|
typeof raw === 'object' &&
|
|
683
697
|
!Array.isArray(raw) &&
|
|
684
698
|
'value' in (raw as object)) ||
|
|
699
|
+
isEmptyEnvelope ||
|
|
685
700
|
isTypedEnvelope(raw);
|
|
686
701
|
const subOption: IQorusFormField =
|
|
687
702
|
isFieldShape ?
|
|
@@ -364,6 +364,18 @@ function AutoField<T = any>({
|
|
|
364
364
|
return (
|
|
365
365
|
<Override
|
|
366
366
|
{...rest}
|
|
367
|
+
// The field's SHAPE, which `rest` does not carry: these three are
|
|
368
|
+
// destructured into named props above, so a host editor received a
|
|
369
|
+
// field it could not describe. `arg_schema` is the sub-schema a host
|
|
370
|
+
// sub-form renders FROM — without it the IDE's test-cases drawer fell
|
|
371
|
+
// back to humanising each key, so `Path`, `Actual` and `Expected` were
|
|
372
|
+
// asked for with no label of their own and no description, while the
|
|
373
|
+
// text sat one fetch away. `element_type`/`ui_element_type` travel with
|
|
374
|
+
// it: a sub-schema alone cannot say whether the value is one hash or a
|
|
375
|
+
// list of them.
|
|
376
|
+
arg_schema={arg_schema}
|
|
377
|
+
element_type={element_type}
|
|
378
|
+
ui_element_type={ui_element_type}
|
|
367
379
|
name={name}
|
|
368
380
|
value={value}
|
|
369
381
|
onChange={(val: any, emittedType?: IQorusType, emittedIsFunction?: boolean) => {
|
|
@@ -1809,6 +1809,68 @@ export const getUnresolvedRequiredOptions = (
|
|
|
1809
1809
|
return unresolved;
|
|
1810
1810
|
};
|
|
1811
1811
|
|
|
1812
|
+
/**
|
|
1813
|
+
* Split a dependency entry into the sibling it names and how its value is compared.
|
|
1814
|
+
*
|
|
1815
|
+
* Three forms: `name` (the sibling must simply have a value), `name=value` (it must
|
|
1816
|
+
* have exactly that value) and `name!=value` (it must have a value, and not that
|
|
1817
|
+
* one). The negative form is what lets a field say it applies to every variant of a
|
|
1818
|
+
* record but one — spelling that as an `any-of` list of every other variant is both
|
|
1819
|
+
* unreadable in the "Depends on" lock and silently wrong the moment a variant is
|
|
1820
|
+
* added.
|
|
1821
|
+
*
|
|
1822
|
+
* `!=` deliberately requires the sibling to be answered: "not X" is a statement about
|
|
1823
|
+
* an answer, so an unanswered sibling does not satisfy it and the field stays locked.
|
|
1824
|
+
*/
|
|
1825
|
+
export const parseDependency = (
|
|
1826
|
+
dependency: string
|
|
1827
|
+
): { name: string; op?: '=' | '!='; value?: string } => {
|
|
1828
|
+
const neqIdx = dependency.indexOf('!=');
|
|
1829
|
+
if (neqIdx !== -1) {
|
|
1830
|
+
return {
|
|
1831
|
+
name: dependency.substring(0, neqIdx),
|
|
1832
|
+
op: '!=',
|
|
1833
|
+
value: dependency.substring(neqIdx + 2),
|
|
1834
|
+
};
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
const eqIdx = dependency.indexOf('=');
|
|
1838
|
+
if (eqIdx !== -1) {
|
|
1839
|
+
return {
|
|
1840
|
+
name: dependency.substring(0, eqIdx),
|
|
1841
|
+
op: '=',
|
|
1842
|
+
value: dependency.substring(eqIdx + 1),
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
return { name: dependency };
|
|
1847
|
+
};
|
|
1848
|
+
|
|
1849
|
+
/** Whether one dependency entry holds against the current form values. */
|
|
1850
|
+
const isDependencyFulfilled = (
|
|
1851
|
+
dependency: string,
|
|
1852
|
+
options: TQorusForm,
|
|
1853
|
+
optionsSchema?: IQorusFormSchema
|
|
1854
|
+
): boolean => {
|
|
1855
|
+
const { name, op, value } = parseDependency(dependency);
|
|
1856
|
+
|
|
1857
|
+
if (op) {
|
|
1858
|
+
const optValue = options?.[name]?.value;
|
|
1859
|
+
if (optValue == null) {
|
|
1860
|
+
return false;
|
|
1861
|
+
}
|
|
1862
|
+
return op === '=' ? String(optValue) === value : String(optValue) !== value;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
return options?.[name]
|
|
1866
|
+
? validateField(options[name].type, options[name].value, {
|
|
1867
|
+
...(optionsSchema?.[name] as unknown as IFieldValidationProps),
|
|
1868
|
+
options,
|
|
1869
|
+
optionSchema: optionsSchema,
|
|
1870
|
+
})
|
|
1871
|
+
: true;
|
|
1872
|
+
};
|
|
1873
|
+
|
|
1812
1874
|
export const hasAllDependenciesFullfilled = (
|
|
1813
1875
|
dependencies: string[] | string[][],
|
|
1814
1876
|
options: TQorusForm,
|
|
@@ -1819,38 +1881,19 @@ export const hasAllDependenciesFullfilled = (
|
|
|
1819
1881
|
}
|
|
1820
1882
|
|
|
1821
1883
|
return dependencies.every((dependency: string | string[]) => {
|
|
1884
|
+
// A nested list is an ANY. It used to look each entry up as a whole form-field
|
|
1885
|
+
// name, so a `name=value` entry inside one found no field and returned `true`
|
|
1886
|
+
// unconditionally — the any-of group was satisfied by anything at all, while
|
|
1887
|
+
// CompactRow's lock rendered those same entries as real comparisons. Both halves
|
|
1888
|
+
// now go through one parser, so the grammar cannot mean two things.
|
|
1822
1889
|
if (isArray(dependency)) {
|
|
1823
|
-
return (dependency as string[]).some((dep) =>
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
...(optionsSchema?.[dep] as unknown as IFieldValidationProps),
|
|
1827
|
-
options,
|
|
1828
|
-
optionSchema: optionsSchema,
|
|
1829
|
-
})
|
|
1830
|
-
: true;
|
|
1831
|
-
});
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
if (isString(dependency)) {
|
|
1835
|
-
const eqIdx = (dependency as string).indexOf('=');
|
|
1836
|
-
if (eqIdx !== -1) {
|
|
1837
|
-
const depName = (dependency as string).substring(0, eqIdx);
|
|
1838
|
-
const depValue = (dependency as string).substring(eqIdx + 1);
|
|
1839
|
-
const optValue = options?.[depName]?.value;
|
|
1840
|
-
return optValue != null && String(optValue) === depValue;
|
|
1841
|
-
}
|
|
1890
|
+
return (dependency as string[]).some((dep) =>
|
|
1891
|
+
isDependencyFulfilled(dep, options, optionsSchema)
|
|
1892
|
+
);
|
|
1842
1893
|
}
|
|
1843
1894
|
|
|
1844
|
-
return
|
|
1845
|
-
?
|
|
1846
|
-
options[dependency as string].type,
|
|
1847
|
-
options[dependency as string].value,
|
|
1848
|
-
{
|
|
1849
|
-
...(optionsSchema?.[dependency as string] as unknown as IFieldValidationProps),
|
|
1850
|
-
options,
|
|
1851
|
-
optionSchema: optionsSchema,
|
|
1852
|
-
}
|
|
1853
|
-
)
|
|
1895
|
+
return isString(dependency)
|
|
1896
|
+
? isDependencyFulfilled(dependency as string, options, optionsSchema)
|
|
1854
1897
|
: true;
|
|
1855
1898
|
});
|
|
1856
1899
|
};
|
package/src/index.tsx
CHANGED