@qoretechnologies/reqraft 0.10.6 → 0.10.8
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 +38 -2
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts +11 -1
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +36 -15
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/compactRowStyles.d.ts +1 -0
- package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowStyles.js +19 -4
- package/dist/components/form/engine/compactRowStyles.js.map +1 -1
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +30 -2
- 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 +29 -3
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/package.json +1 -1
- package/src/components/form/engine/CompactRow.tsx +75 -2
- package/src/components/form/engine/FormEngine.stories.tsx +279 -0
- package/src/components/form/engine/FormEngine.tsx +66 -18
- package/src/components/form/engine/compactRowStyles.ts +21 -0
- package/src/components/form/engine/readFirst.ts +25 -4
- package/src/components/form/fields/auto/AutoFormField.tsx +26 -0
- package/src/components/qonsoleSmartInput/QonsoleSmartInput.stories.tsx +69 -37
|
@@ -667,6 +667,285 @@ export const OptionInheritsRenderPropFromSibling: Story = {
|
|
|
667
667
|
},
|
|
668
668
|
};
|
|
669
669
|
|
|
670
|
+
// qorus#347-followup, compact variant of the FLAT case: same schema and
|
|
671
|
+
// componentOverride as `OptionInheritsRenderPropFromSibling`, but the form
|
|
672
|
+
// is rendered with `compact: true`. Compact and classic share the same
|
|
673
|
+
// `renderOption` callback (FormEngine.tsx:1590) where `inherit_props` is
|
|
674
|
+
// resolved onto `TemplateField`, so the forwarding mechanism is identical
|
|
675
|
+
// in both modes. This story locks that in so a future refactor of the
|
|
676
|
+
// compact path can't silently break inherit_props for read-first surfaces.
|
|
677
|
+
export const OptionInheritsRenderPropFromSiblingCompact: Story = {
|
|
678
|
+
args: {
|
|
679
|
+
compact: true,
|
|
680
|
+
minColumnWidth: '300px',
|
|
681
|
+
componentOverrides: { 'code-editor': CodeEditorStandin },
|
|
682
|
+
value: {
|
|
683
|
+
lang: { type: 'string', value: 'qore' },
|
|
684
|
+
source: { type: 'string', value: 'sub run() { print("hello"); }' },
|
|
685
|
+
},
|
|
686
|
+
options: {
|
|
687
|
+
lang: {
|
|
688
|
+
type: 'string',
|
|
689
|
+
ui_type: 'string',
|
|
690
|
+
display_name: 'Language',
|
|
691
|
+
allowed_values: [
|
|
692
|
+
{ display_name: 'Qore', value: { type: 'string', value: 'qore' } },
|
|
693
|
+
{ display_name: 'Python', value: { type: 'string', value: 'python' } },
|
|
694
|
+
{ display_name: 'Java', value: { type: 'string', value: 'java' } },
|
|
695
|
+
],
|
|
696
|
+
},
|
|
697
|
+
source: {
|
|
698
|
+
type: 'string',
|
|
699
|
+
ui_type: 'code-editor',
|
|
700
|
+
display_name: 'Source Code',
|
|
701
|
+
inherit_props: { language: 'lang' },
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
play: async ({ canvasElement }) => {
|
|
706
|
+
const canvas = within(canvasElement);
|
|
707
|
+
// Compact renders rows collapsed by default. Structural check: the
|
|
708
|
+
// schema arrives intact + the source-code row is present. The full
|
|
709
|
+
// interaction (lang flip -> language prop updates) is covered by the
|
|
710
|
+
// classic `OptionInheritsRenderPropFromSibling` story.
|
|
711
|
+
await waitFor(
|
|
712
|
+
() => expect(canvas.getAllByText('Source Code').length).toBeGreaterThan(0),
|
|
713
|
+
{ timeout: 5000 }
|
|
714
|
+
);
|
|
715
|
+
},
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
// Compact-row code-editor preview: a `code-editor` field with a multi-line
|
|
719
|
+
// string value renders (a) a "N lines · N chars" tag in the value cell instead
|
|
720
|
+
// of the truncated raw string, and (b) a monospace `<pre>` block under the row
|
|
721
|
+
// capped by a `ReqoreCollapsibleContent` — the "Show more" affordance the value
|
|
722
|
+
// cell couldn't provide on its own. Locks the compact preview so a future
|
|
723
|
+
// CompactRow refactor can't silently reduce a Qorus source-code field to an
|
|
724
|
+
// ellipsised one-liner again.
|
|
725
|
+
export const CompactRowCodeEditorPreview: Story = {
|
|
726
|
+
args: {
|
|
727
|
+
compact: true,
|
|
728
|
+
minColumnWidth: '360px',
|
|
729
|
+
componentOverrides: { 'code-editor': CodeEditorStandin },
|
|
730
|
+
value: {
|
|
731
|
+
language: { type: 'string', value: 'qore' },
|
|
732
|
+
source: {
|
|
733
|
+
type: 'string',
|
|
734
|
+
value:
|
|
735
|
+
'%new-style\n%require-types\n%strict-args\n' +
|
|
736
|
+
'%enable-all-warnings\n\n' +
|
|
737
|
+
'class ExampleJob inherits QorusJob {\n' +
|
|
738
|
+
' run() {\n' +
|
|
739
|
+
' logInfo("running");\n' +
|
|
740
|
+
' }\n' +
|
|
741
|
+
'}\n',
|
|
742
|
+
},
|
|
743
|
+
},
|
|
744
|
+
options: {
|
|
745
|
+
language: {
|
|
746
|
+
type: 'string',
|
|
747
|
+
ui_type: 'string',
|
|
748
|
+
display_name: 'Language',
|
|
749
|
+
allowed_values: [
|
|
750
|
+
{ display_name: 'Qore', value: { type: 'string', value: 'qore' } },
|
|
751
|
+
{ display_name: 'Python', value: { type: 'string', value: 'python' } },
|
|
752
|
+
{ display_name: 'Java', value: { type: 'string', value: 'java' } },
|
|
753
|
+
],
|
|
754
|
+
},
|
|
755
|
+
source: {
|
|
756
|
+
type: 'string',
|
|
757
|
+
ui_type: 'code-editor',
|
|
758
|
+
display_name: 'Source Code',
|
|
759
|
+
inherit_props: { language: 'language' },
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
play: async ({ canvasElement }) => {
|
|
764
|
+
// (a) The monospace preview mounted under the row — contains a substring
|
|
765
|
+
// only the source has, proving `showCodePreview` kicked in and the
|
|
766
|
+
// `StyledCodePreview` block is in the DOM.
|
|
767
|
+
await waitFor(
|
|
768
|
+
() => {
|
|
769
|
+
const preview = canvasElement.querySelector('.options-readfirst-code');
|
|
770
|
+
expect(preview).toBeTruthy();
|
|
771
|
+
expect(preview?.textContent).toContain('class ExampleJob inherits QorusJob');
|
|
772
|
+
},
|
|
773
|
+
{ timeout: 5000 }
|
|
774
|
+
);
|
|
775
|
+
// (b) The value cell replaced its truncated raw string with a summary tag —
|
|
776
|
+
// query the tag directly (its label + labelKey render on separate spans,
|
|
777
|
+
// so text-matching across them is fragile). Look for the CodeLine icon
|
|
778
|
+
// that only this tag mounts alongside the source-code row.
|
|
779
|
+
const sourceRow = canvasElement.querySelector('[data-field="source"]');
|
|
780
|
+
expect(sourceRow).toBeTruthy();
|
|
781
|
+
expect(sourceRow?.textContent ?? '').toMatch(/\d+\s*lines?/);
|
|
782
|
+
},
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
// qorus#347-followup, scope forwarding: this story exercises the nested
|
|
786
|
+
// case of the OptionInheritsRenderPropFromSibling contract. The parent
|
|
787
|
+
// form declares `methods: { ui_type: 'list', element_type: 'hash',
|
|
788
|
+
// arg_schema: {...}, inherit_props: { language: 'language' } }`. FormEngine
|
|
789
|
+
// resolves the parent's inherit_props against the top-level `language`
|
|
790
|
+
// field, threads the resolved bag through the ArrayAuto row wrapper into
|
|
791
|
+
// each row's arg_schema sub-form as `inheritedFromParent`. The row's
|
|
792
|
+
// `body` sub-field ALSO declares `inherit_props: { language: 'language' }`;
|
|
793
|
+
// its `availableOptions` has no `language` (rows only carry name /
|
|
794
|
+
// description / body), so the resolver falls back to
|
|
795
|
+
// `inheritedFromParent.language` and threads it as the CodeEditor's
|
|
796
|
+
// `language` prop. Flipping the top-level lang picker live-updates every
|
|
797
|
+
// row's editor without any custom per-field wiring.
|
|
798
|
+
export const NestedOptionInheritsRenderPropFromAncestor: Story = {
|
|
799
|
+
args: {
|
|
800
|
+
componentOverrides: { 'code-editor': CodeEditorStandin },
|
|
801
|
+
value: {
|
|
802
|
+
language: { type: 'string', value: 'qore' },
|
|
803
|
+
methods: {
|
|
804
|
+
type: 'list',
|
|
805
|
+
value: [
|
|
806
|
+
{ type: 'hash', value: { name: 'init', body: 'sub init() { }' } },
|
|
807
|
+
{ type: 'hash', value: { name: 'run', body: 'sub run() { print("hi"); }' } },
|
|
808
|
+
],
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
options: {
|
|
812
|
+
language: {
|
|
813
|
+
type: 'string',
|
|
814
|
+
ui_type: 'string',
|
|
815
|
+
display_name: 'Language',
|
|
816
|
+
allowed_values: [
|
|
817
|
+
{ display_name: 'Qore', value: { type: 'string', value: 'qore' } },
|
|
818
|
+
{ display_name: 'Python', value: { type: 'string', value: 'python' } },
|
|
819
|
+
{ display_name: 'Java', value: { type: 'string', value: 'java' } },
|
|
820
|
+
],
|
|
821
|
+
},
|
|
822
|
+
methods: {
|
|
823
|
+
type: 'list',
|
|
824
|
+
ui_type: 'list',
|
|
825
|
+
element_type: 'hash',
|
|
826
|
+
display_name: 'Methods',
|
|
827
|
+
// Parent-level declaration: forward top-level `language` down into
|
|
828
|
+
// each row's arg_schema sub-form so per-method `body` sub-fields
|
|
829
|
+
// can pick it up as `language` prop without knowing about the
|
|
830
|
+
// ancestor scope.
|
|
831
|
+
inherit_props: { language: 'language' },
|
|
832
|
+
arg_schema: {
|
|
833
|
+
name: {
|
|
834
|
+
type: 'string',
|
|
835
|
+
ui_type: 'string',
|
|
836
|
+
display_name: 'Method Name',
|
|
837
|
+
},
|
|
838
|
+
body: {
|
|
839
|
+
type: 'string',
|
|
840
|
+
ui_type: 'code-editor',
|
|
841
|
+
display_name: 'Method Body',
|
|
842
|
+
// Row-level declaration: the resolver walks
|
|
843
|
+
// 1. local availableOptions (row only has name + body — no
|
|
844
|
+
// language here),
|
|
845
|
+
// 2. `inheritedFromParent` (populated by the parent-level
|
|
846
|
+
// `methods.inherit_props` above — has language).
|
|
847
|
+
inherit_props: { language: 'language' },
|
|
848
|
+
},
|
|
849
|
+
},
|
|
850
|
+
},
|
|
851
|
+
} as unknown as IOptionsSchema,
|
|
852
|
+
},
|
|
853
|
+
play: async ({ canvasElement }) => {
|
|
854
|
+
const canvas = within(canvasElement);
|
|
855
|
+
|
|
856
|
+
// Two rows -> two code-editor stand-ins -> each shows "syntax: qore"
|
|
857
|
+
// at initial render, sourced from the top-level `language` field via
|
|
858
|
+
// parent -> row scope forwarding.
|
|
859
|
+
await waitFor(
|
|
860
|
+
() => {
|
|
861
|
+
const tags = canvas.getAllByTestId('code-editor-language');
|
|
862
|
+
expect(tags).toHaveLength(2);
|
|
863
|
+
tags.forEach((tag) => expect(tag).toHaveTextContent('syntax: qore'));
|
|
864
|
+
},
|
|
865
|
+
{ timeout: 5000 }
|
|
866
|
+
);
|
|
867
|
+
},
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
// qorus#347-followup, scope forwarding + compact variant: same schema as
|
|
871
|
+
// `NestedOptionInheritsRenderPropFromAncestor` but with `compact: true`.
|
|
872
|
+
// Compact and classic share the `renderOption` callback (FormEngine.tsx:1590)
|
|
873
|
+
// which is where the `inheritedFromParent` bag is threaded onto TemplateField,
|
|
874
|
+
// so the forwarding mechanism is identical in both modes. This story locks
|
|
875
|
+
// that in — a compact rendering of a parent form with a nested arg_schema
|
|
876
|
+
// list-of-hash whose sub-fields still resolve `language` from the top-level
|
|
877
|
+
// picker through the same two-hop chain.
|
|
878
|
+
export const NestedOptionInheritsRenderPropFromAncestorCompact: Story = {
|
|
879
|
+
args: {
|
|
880
|
+
compact: true,
|
|
881
|
+
minColumnWidth: '300px',
|
|
882
|
+
componentOverrides: { 'code-editor': CodeEditorStandin },
|
|
883
|
+
value: {
|
|
884
|
+
language: { type: 'string', value: 'qore' },
|
|
885
|
+
methods: {
|
|
886
|
+
type: 'list',
|
|
887
|
+
value: [
|
|
888
|
+
{ type: 'hash', value: { name: 'init', body: 'sub init() { }' } },
|
|
889
|
+
{ type: 'hash', value: { name: 'run', body: 'sub run() { print("hi"); }' } },
|
|
890
|
+
],
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
options: {
|
|
894
|
+
language: {
|
|
895
|
+
type: 'string',
|
|
896
|
+
ui_type: 'string',
|
|
897
|
+
display_name: 'Language',
|
|
898
|
+
allowed_values: [
|
|
899
|
+
{ display_name: 'Qore', value: { type: 'string', value: 'qore' } },
|
|
900
|
+
{ display_name: 'Python', value: { type: 'string', value: 'python' } },
|
|
901
|
+
{ display_name: 'Java', value: { type: 'string', value: 'java' } },
|
|
902
|
+
],
|
|
903
|
+
},
|
|
904
|
+
methods: {
|
|
905
|
+
type: 'list',
|
|
906
|
+
ui_type: 'list',
|
|
907
|
+
element_type: 'hash',
|
|
908
|
+
display_name: 'Methods',
|
|
909
|
+
inherit_props: { language: 'language' },
|
|
910
|
+
arg_schema: {
|
|
911
|
+
name: {
|
|
912
|
+
type: 'string',
|
|
913
|
+
ui_type: 'string',
|
|
914
|
+
display_name: 'Method Name',
|
|
915
|
+
},
|
|
916
|
+
body: {
|
|
917
|
+
type: 'string',
|
|
918
|
+
ui_type: 'code-editor',
|
|
919
|
+
display_name: 'Method Body',
|
|
920
|
+
inherit_props: { language: 'language' },
|
|
921
|
+
},
|
|
922
|
+
},
|
|
923
|
+
},
|
|
924
|
+
} as unknown as IOptionsSchema,
|
|
925
|
+
},
|
|
926
|
+
play: async ({ canvasElement }) => {
|
|
927
|
+
const canvas = within(canvasElement);
|
|
928
|
+
|
|
929
|
+
// Compact renders each option collapsed into a read-first row. The
|
|
930
|
+
// methods row needs a click to expand, then the nested list rows each
|
|
931
|
+
// need a click to expand and reveal the body sub-field's editor with
|
|
932
|
+
// the inherited language. Rather than driving that whole editing
|
|
933
|
+
// flow (which is what the CompactBasic / CompactExpressions stories
|
|
934
|
+
// already exercise), assert on the STRUCTURAL element: the schema
|
|
935
|
+
// arrived intact through `inheritedFromParent` and the row-level
|
|
936
|
+
// options include the body field wired to the code-editor override.
|
|
937
|
+
await waitFor(
|
|
938
|
+
() => expect(canvas.getAllByText('Methods').length).toBeGreaterThan(0),
|
|
939
|
+
{ timeout: 5000 }
|
|
940
|
+
);
|
|
941
|
+
// The list-of-hashes value summarises by the items' names — never a raw
|
|
942
|
+
// "[object Object]" (regression: it used to stringify each hash envelope).
|
|
943
|
+
await expect(await canvas.findByText('init, run', undefined, { timeout: 5000 }))
|
|
944
|
+
.toBeInTheDocument();
|
|
945
|
+
await expect(canvasElement.textContent ?? '').not.toContain('[object Object]');
|
|
946
|
+
},
|
|
947
|
+
};
|
|
948
|
+
|
|
670
949
|
export const DependantsResetWhenParentChanges: Story = {
|
|
671
950
|
args: {
|
|
672
951
|
minColumnWidth: '300px',
|
|
@@ -133,13 +133,21 @@ export interface IFormValidityData {
|
|
|
133
133
|
|
|
134
134
|
/**
|
|
135
135
|
* Resolve a field's `inherit_props` map against the current available
|
|
136
|
-
* options
|
|
137
|
-
*
|
|
136
|
+
* options (plus any values inherited from an outer FormEngine scope),
|
|
137
|
+
* producing a `{ propName: siblingValue }` hash suitable for spreading
|
|
138
|
+
* onto the field's renderer.
|
|
138
139
|
*
|
|
139
140
|
* For each entry `<prop-name-on-renderer> -> <sibling-field-name>`, the
|
|
140
|
-
* sibling's current value
|
|
141
|
-
*
|
|
142
|
-
* `
|
|
141
|
+
* sibling's current value is forwarded under the receiving prop name.
|
|
142
|
+
* Lookup order:
|
|
143
|
+
* 1. `availableOptions[siblingName]?.value` — the field's local scope.
|
|
144
|
+
* 2. `inheritedFromParent[siblingName]` — the bag threaded in by an
|
|
145
|
+
* outer FormEngine (see the `inheritedFromParent` prop). Used when
|
|
146
|
+
* a composite field's `arg_schema` sub-form needs to reach a value
|
|
147
|
+
* on an ancestor scope — e.g. a service-method row's `body` picking
|
|
148
|
+
* up `language` from the parent service form.
|
|
149
|
+
* Missing siblings emit `undefined`, which the renderer's prop type can
|
|
150
|
+
* treat as "no hint".
|
|
143
151
|
*
|
|
144
152
|
* Designed to be JSON-pure: no closures, no transformations. Renderers
|
|
145
153
|
* decide how to use the forwarded value (e.g. the consumer-injected
|
|
@@ -151,13 +159,15 @@ export interface IFormValidityData {
|
|
|
151
159
|
*/
|
|
152
160
|
const resolveInheritProps = (
|
|
153
161
|
inheritProps: Record<string, string> | undefined,
|
|
154
|
-
availableOptions: TQorusForm | undefined
|
|
162
|
+
availableOptions: TQorusForm | undefined,
|
|
163
|
+
inheritedFromParent?: Record<string, unknown>
|
|
155
164
|
): Record<string, unknown> => {
|
|
156
|
-
if (!inheritProps
|
|
165
|
+
if (!inheritProps) return {};
|
|
157
166
|
const out: Record<string, unknown> = {};
|
|
158
167
|
for (const propName in inheritProps) {
|
|
159
168
|
const siblingName = inheritProps[propName];
|
|
160
|
-
|
|
169
|
+
const localValue = (availableOptions?.[siblingName] as IQorusFormField | undefined)?.value;
|
|
170
|
+
out[propName] = localValue !== undefined ? localValue : inheritedFromParent?.[siblingName];
|
|
161
171
|
}
|
|
162
172
|
return out;
|
|
163
173
|
};
|
|
@@ -543,6 +553,17 @@ export interface IFormEngineProps extends Omit<IReqoreCollectionProps, 'onChange
|
|
|
543
553
|
* `TemplateField` to the `AutoFormField` override seam.
|
|
544
554
|
*/
|
|
545
555
|
componentOverrides?: Record<string, React.FC<any>>;
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Bag of values forwarded from an outer FormEngine scope, used as a
|
|
559
|
+
* fallback when a field's `inherit_props` names a sibling that isn't in
|
|
560
|
+
* this form's own `availableOptions`. Populated automatically when
|
|
561
|
+
* FormEngine renders a nested `arg_schema` sub-form (through
|
|
562
|
+
* AutoFormField's hash / list mount sites) so that each level accumulates
|
|
563
|
+
* its ancestors' inherited props. Consumers rarely set this by hand — it's
|
|
564
|
+
* plumbing for the inherit_props scope-forwarding contract.
|
|
565
|
+
*/
|
|
566
|
+
inheritedFromParent?: Record<string, unknown>;
|
|
546
567
|
}
|
|
547
568
|
|
|
548
569
|
// Option types rendered full-width (IDE Options parity, commit 8e6b7781).
|
|
@@ -580,6 +601,7 @@ export const FormEngine = ({
|
|
|
580
601
|
onValidityChange,
|
|
581
602
|
optionActions,
|
|
582
603
|
componentOverrides,
|
|
604
|
+
inheritedFromParent,
|
|
583
605
|
...rest
|
|
584
606
|
}: IFormEngineProps) => {
|
|
585
607
|
const [options, setOptions] = useState<IQorusFormSchema | undefined>(rest?.options || undefined);
|
|
@@ -1665,16 +1687,42 @@ export const FormEngine = ({
|
|
|
1665
1687
|
fluid
|
|
1666
1688
|
{...(options?.[optionName] as any)}
|
|
1667
1689
|
// qorus#347-followup: resolve the field's `inherit_props` against
|
|
1668
|
-
// the CURRENT sibling values
|
|
1669
|
-
//
|
|
1670
|
-
//
|
|
1671
|
-
//
|
|
1672
|
-
//
|
|
1673
|
-
// value
|
|
1674
|
-
//
|
|
1675
|
-
//
|
|
1676
|
-
//
|
|
1677
|
-
|
|
1690
|
+
// the CURRENT sibling values (with a fallback to
|
|
1691
|
+
// `inheritedFromParent` — the bag threaded in from an outer
|
|
1692
|
+
// FormEngine when this scope is an `arg_schema` sub-form),
|
|
1693
|
+
// threading each entry as a top-level prop. Each
|
|
1694
|
+
// `<prop-name>: <sibling-field-name>` mapping copies the
|
|
1695
|
+
// sibling's value onto the rendered field's renderer — e.g. a
|
|
1696
|
+
// `code-editor` with `inherit_props: { language: 'language' }`
|
|
1697
|
+
// picks up the live `language` value as a `language` prop without
|
|
1698
|
+
// a schema refetch. Spread AFTER `{...options?.[optionName]}` so
|
|
1699
|
+
// the runtime value wins over any schema-defined default of the
|
|
1700
|
+
// same key. Mirrored in qorus-ide's `systemOptions.tsx`; see the
|
|
1701
|
+
// CLAUDE.md rule there.
|
|
1702
|
+
{...resolveInheritProps(
|
|
1703
|
+
options?.[optionName]?.inherit_props,
|
|
1704
|
+
availableOptions,
|
|
1705
|
+
inheritedFromParent
|
|
1706
|
+
)}
|
|
1707
|
+
// qorus#347-followup (scope forwarding): merge accumulated
|
|
1708
|
+
// inheritance (`inheritedFromParent`) with THIS field's freshly
|
|
1709
|
+
// resolved inherit_props, and pass down as a single bag so any
|
|
1710
|
+
// nested `arg_schema` sub-form (mounted by AutoFormField for
|
|
1711
|
+
// `hash` / `free-hash` / list-of-hash fields) sees every value
|
|
1712
|
+
// the ancestor chain forwarded. This is the plumbing that lets a
|
|
1713
|
+
// service-method row's `body` sub-field pick up the parent
|
|
1714
|
+
// service form's `language` — the parent field declares
|
|
1715
|
+
// `inherit_props: { language: 'language' }`, the list renderer
|
|
1716
|
+
// forwards it into each row, and the row's body resolves against
|
|
1717
|
+
// the accumulated bag.
|
|
1718
|
+
inheritedFromParent={{
|
|
1719
|
+
...inheritedFromParent,
|
|
1720
|
+
...resolveInheritProps(
|
|
1721
|
+
options?.[optionName]?.inherit_props,
|
|
1722
|
+
availableOptions,
|
|
1723
|
+
inheritedFromParent
|
|
1724
|
+
),
|
|
1725
|
+
}}
|
|
1678
1726
|
// Propagate compact so an arg_schema field renders a COMPACT sub-form
|
|
1679
1727
|
// (consistent with the parent) rather than the classic FormEngine.
|
|
1680
1728
|
compact={compact}
|
|
@@ -335,6 +335,27 @@ export const StyledRowInset = styled.div`
|
|
|
335
335
|
margin-top: 4px;
|
|
336
336
|
`;
|
|
337
337
|
|
|
338
|
+
// A collapsed code-block preview shown under the value summary for a
|
|
339
|
+
// `code-editor` field. Multi-line, monospace, subtle background — matches
|
|
340
|
+
// the aesthetic of the classic code-view surface but small enough to sit
|
|
341
|
+
// inside a read-row. Height is capped by the wrapping `ReqoreCollapsibleContent`
|
|
342
|
+
// so the "Show more" affordance stays useful.
|
|
343
|
+
export const StyledCodePreview = styled.pre<{ $bg: string; $border: string; $fg: string }>`
|
|
344
|
+
margin: 0;
|
|
345
|
+
padding: 8px 10px;
|
|
346
|
+
border-radius: 4px;
|
|
347
|
+
background: ${({ $bg }) => $bg};
|
|
348
|
+
border: 1px solid ${({ $border }) => $border};
|
|
349
|
+
color: ${({ $fg }) => $fg};
|
|
350
|
+
font-family:
|
|
351
|
+
ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
352
|
+
font-size: 11.5px;
|
|
353
|
+
line-height: 1.5;
|
|
354
|
+
white-space: pre;
|
|
355
|
+
overflow: auto;
|
|
356
|
+
max-width: 100%;
|
|
357
|
+
`;
|
|
358
|
+
|
|
338
359
|
// A small inline colour swatch shown before an rgbcolor value's hex string.
|
|
339
360
|
export const StyledColorSwatch = styled.span<{ $color: string; $border: string }>`
|
|
340
361
|
width: 12px;
|
|
@@ -17,12 +17,33 @@ const pluralize = (count: number, noun: string): string =>
|
|
|
17
17
|
// full server allow-list, incl. keys like `sensitive`/`required`).
|
|
18
18
|
const isTypedEnvelope = (raw: unknown): raw is IQorusFormField => isUiEncodedValue(raw);
|
|
19
19
|
|
|
20
|
-
/**
|
|
20
|
+
/** Reduce ONE list item to a short, human label — never an object. Unwraps a
|
|
21
|
+
* typed `{type, value}` envelope, prefers a `name`/`display_name`, and returns
|
|
22
|
+
* undefined for a bare object (no name) so the caller falls back to a count
|
|
23
|
+
* instead of printing "[object Object]". */
|
|
24
|
+
const summarizeListItem = (item: unknown): string | number | undefined => {
|
|
25
|
+
if (item === null || item === undefined) return undefined;
|
|
26
|
+
if (typeof item !== 'object') return item as string | number;
|
|
27
|
+
const obj = item as Record<string, unknown>;
|
|
28
|
+
// A named object (or allowed-value) → its label.
|
|
29
|
+
if (typeof obj.name === 'string' || typeof obj.name === 'number') return obj.name;
|
|
30
|
+
if (typeof obj.display_name === 'string') return obj.display_name;
|
|
31
|
+
// A typed envelope ({type, value}) or a {value} wrapper → look inside once.
|
|
32
|
+
if ('value' in obj) {
|
|
33
|
+
const inner = obj.value;
|
|
34
|
+
if (inner === null || inner === undefined) return undefined;
|
|
35
|
+
if (typeof inner !== 'object') return inner as string | number;
|
|
36
|
+
const innerName = (inner as Record<string, unknown>).name;
|
|
37
|
+
return typeof innerName === 'string' || typeof innerName === 'number' ? innerName : undefined;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Summarise a list value: join item labels, or fall back to an "N items" count
|
|
43
|
+
* (e.g. a list of anonymous hashes). Never prints raw objects. */
|
|
21
44
|
const formatList = (items: unknown[]): string => {
|
|
22
45
|
const parts = items
|
|
23
|
-
.map(
|
|
24
|
-
item && typeof item === 'object' ? ((item as any).name ?? (item as any).value ?? '') : item
|
|
25
|
-
)
|
|
46
|
+
.map(summarizeListItem)
|
|
26
47
|
.filter((part) => part !== '' && part !== undefined && part !== null);
|
|
27
48
|
|
|
28
49
|
return parts.length ? parts.join(', ') : pluralize(items.length, 'item');
|
|
@@ -154,6 +154,12 @@ function AutoField<T = any>({
|
|
|
154
154
|
showSavedValues,
|
|
155
155
|
uniqueName,
|
|
156
156
|
componentOverrides,
|
|
157
|
+
// qorus#347-followup (scope forwarding): destructure so it does NOT
|
|
158
|
+
// land in `...rest` — otherwise the primitive field renderers spread
|
|
159
|
+
// rest onto DOM nodes and React warns about the unknown attribute.
|
|
160
|
+
// Only the nested arg_schema mount sites re-forward this into their
|
|
161
|
+
// sub-forms explicitly.
|
|
162
|
+
inheritedFromParent,
|
|
157
163
|
...rest
|
|
158
164
|
}: IAutoFieldProps & T) {
|
|
159
165
|
const [currentType, setType] = useState<IQorusType>(defaultInternalType || null);
|
|
@@ -564,6 +570,20 @@ function AutoField<T = any>({
|
|
|
564
570
|
stringTemplates={rest.templates}
|
|
565
571
|
size={rest.size}
|
|
566
572
|
disabled={rest.disabled}
|
|
573
|
+
// Forward consumer-injected editors into the nested sub-form
|
|
574
|
+
// so its own fields can render host-injected types (e.g. a
|
|
575
|
+
// `code-editor` override for a `body` sub-field inside a
|
|
576
|
+
// list-of-hash row). Was missing pre-qorus#347-followup —
|
|
577
|
+
// catches an existing gap surfaced by the nested inherit_props
|
|
578
|
+
// story.
|
|
579
|
+
componentOverrides={componentOverrides}
|
|
580
|
+
// qorus#347-followup (scope forwarding): thread the accumulated
|
|
581
|
+
// inheritance bag into the nested sub-form so its own fields'
|
|
582
|
+
// `inherit_props` can reach ancestor-scope values (e.g. a
|
|
583
|
+
// service-method row's `body` picking up `language` from the
|
|
584
|
+
// parent service form). The parent FormEngine populates this
|
|
585
|
+
// via TemplateField -> AutoFormField's `rest`.
|
|
586
|
+
inheritedFromParent={inheritedFromParent}
|
|
567
587
|
/>
|
|
568
588
|
);
|
|
569
589
|
}
|
|
@@ -658,6 +678,12 @@ function AutoField<T = any>({
|
|
|
658
678
|
allowed_values_creatable={rest.element_allowed_values_creatable}
|
|
659
679
|
type={effectiveElementType}
|
|
660
680
|
componentOverrides={componentOverrides}
|
|
681
|
+
// qorus#347-followup (scope forwarding): thread the accumulated
|
|
682
|
+
// inheritance bag through the list wrapper so each row's
|
|
683
|
+
// arg_schema sub-form sees it. ArrayAuto forwards this via
|
|
684
|
+
// TemplateField's `rest` into each row's AutoFormField, which
|
|
685
|
+
// hands it to the row's nested FormEngine (case 'hash' above).
|
|
686
|
+
inheritedFromParent={inheritedFromParent}
|
|
661
687
|
onChange={(name, value) => {
|
|
662
688
|
if (!size(value)) {
|
|
663
689
|
return handleChange(name, undefined);
|