@qoretechnologies/reqraft 0.10.61 → 0.10.62-pr.137.g86641aa
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/dpqlEditor/DpqlEditor.d.ts.map +1 -1
- package/dist/components/dpqlEditor/DpqlEditor.js +8 -3
- package/dist/components/dpqlEditor/DpqlEditor.js.map +1 -1
- package/dist/components/dpqlEditor/types.d.ts +5 -0
- package/dist/components/dpqlEditor/types.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +17 -8
- 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 +65 -15
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
- package/dist/components/form/engine/OptionFieldMessages.js +12 -1
- package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
- package/dist/components/form/engine/compactRowContext.d.ts +2 -0
- package/dist/components/form/engine/compactRowContext.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowContext.js.map +1 -1
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +5 -1
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/expressions/ExpressionField.d.ts +21 -1
- package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
- package/dist/components/form/expressions/ExpressionField.js +72 -14
- package/dist/components/form/expressions/ExpressionField.js.map +1 -1
- package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/index.js +16 -3
- package/dist/components/form/expressions/builder/index.js.map +1 -1
- package/dist/components/form/expressions/expressionOperands.d.ts +16 -0
- package/dist/components/form/expressions/expressionOperands.d.ts.map +1 -0
- package/dist/components/form/expressions/expressionOperands.js +97 -0
- package/dist/components/form/expressions/expressionOperands.js.map +1 -0
- package/dist/components/form/expressions/mockExpressions.d.ts.map +1 -1
- package/dist/components/form/expressions/mockExpressions.js +15 -0
- package/dist/components/form/expressions/mockExpressions.js.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts +2 -0
- package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.js +24 -5
- package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
- package/dist/components/form/expressions/textOfExpression.d.ts +18 -0
- package/dist/components/form/expressions/textOfExpression.d.ts.map +1 -0
- package/dist/components/form/expressions/textOfExpression.js +37 -0
- package/dist/components/form/expressions/textOfExpression.js.map +1 -0
- package/dist/components/form/expressions/useRenderExpression.d.ts.map +1 -1
- package/dist/components/form/expressions/useRenderExpression.js +7 -7
- package/dist/components/form/expressions/useRenderExpression.js.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts +8 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.js +10 -10
- package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
- package/dist/components/form/fields/template/TemplateField.d.ts +6 -0
- package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
- package/dist/components/form/fields/template/TemplateField.js +280 -55
- package/dist/components/form/fields/template/TemplateField.js.map +1 -1
- package/dist/components/form/fields/template/writtenAsText.d.ts +57 -0
- package/dist/components/form/fields/template/writtenAsText.d.ts.map +1 -0
- package/dist/components/form/fields/template/writtenAsText.js +203 -0
- package/dist/components/form/fields/template/writtenAsText.js.map +1 -0
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +7 -1
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/smartEditor/useLspSession.d.ts +6 -0
- package/dist/components/smartEditor/useLspSession.d.ts.map +1 -1
- package/dist/components/smartEditor/useLspSession.js +7 -6
- package/dist/components/smartEditor/useLspSession.js.map +1 -1
- package/dist/helpers/templates.d.ts +18 -0
- package/dist/helpers/templates.d.ts.map +1 -1
- package/dist/helpers/templates.js +30 -1
- package/dist/helpers/templates.js.map +1 -1
- package/dist/helpers/validations.d.ts.map +1 -1
- package/dist/helpers/validations.js +98 -18
- package/dist/helpers/validations.js.map +1 -1
- package/package.json +1 -1
- package/src/components/dpqlEditor/DpqlEditor.tsx +11 -18
- package/src/components/dpqlEditor/types.ts +5 -0
- package/src/components/form/engine/CompactRow.tsx +31 -10
- package/src/components/form/engine/FormEngine.stories.tsx +656 -18
- package/src/components/form/engine/FormEngine.tsx +64 -22
- package/src/components/form/engine/FormEngineRemote.stories.tsx +5 -0
- package/src/components/form/engine/OptionFieldMessages.tsx +12 -1
- package/src/components/form/engine/compactRowContext.ts +2 -0
- package/src/components/form/engine/readFirst.ts +7 -1
- package/src/components/form/expressions/ExpressionField.stories.tsx +57 -9
- package/src/components/form/expressions/ExpressionField.tsx +179 -60
- package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +8 -1
- package/src/components/form/expressions/builder/index.tsx +16 -3
- package/src/components/form/expressions/dpqlMockLanguage.ts +7 -2
- package/src/components/form/expressions/expressionOperands.ts +74 -0
- package/src/components/form/expressions/mockExpressions.ts +16 -4
- package/src/components/form/expressions/renderExpressionToText.ts +26 -9
- package/src/components/form/expressions/textOfExpression.ts +39 -0
- package/src/components/form/expressions/useRenderExpression.ts +9 -1
- package/src/components/form/fields/number/Number.stories.tsx +8 -7
- package/src/components/form/fields/rich-text/RichText.tsx +18 -10
- package/src/components/form/fields/template/TemplateField.stories.tsx +486 -18
- package/src/components/form/fields/template/TemplateField.tsx +506 -62
- package/src/components/form/fields/template/writtenAsText.ts +217 -0
- package/src/components/form/index.tsx +6 -0
- package/src/components/smartEditor/useLspSession.ts +15 -10
- package/src/helpers/templates.ts +35 -0
- package/src/helpers/validations.ts +83 -12
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
filterTemplatesByType as templatesFilterFunc,
|
|
36
36
|
getTemplateKey,
|
|
37
37
|
getTemplateValue,
|
|
38
|
-
|
|
38
|
+
findTemplate,
|
|
39
39
|
isCompleteTemplateToken,
|
|
40
40
|
describeTemplateReference,
|
|
41
41
|
isValueTemplate,
|
|
@@ -67,6 +67,20 @@ import { RichTextFormField } from '../rich-text/RichText';
|
|
|
67
67
|
import { richtextToString } from '../../../../helpers/common';
|
|
68
68
|
import { isSingleLineStringType } from '../../../../helpers/singleLineString';
|
|
69
69
|
import { isUntypedOptionType } from '../../../../helpers/optionUiTypes';
|
|
70
|
+
import {
|
|
71
|
+
expressionSeedOf,
|
|
72
|
+
isWrittenAsText,
|
|
73
|
+
isWrittenAsTextOnTheValueTab,
|
|
74
|
+
loneTemplateOf,
|
|
75
|
+
loneValueOf,
|
|
76
|
+
expressionTextOfValue,
|
|
77
|
+
sameValueType,
|
|
78
|
+
templateTypeName,
|
|
79
|
+
valueTextOf,
|
|
80
|
+
TValueTab,
|
|
81
|
+
templateTextValue,
|
|
82
|
+
untypedTextOf,
|
|
83
|
+
} from './writtenAsText';
|
|
70
84
|
import {
|
|
71
85
|
IRowMenuRegistration,
|
|
72
86
|
RowMenuContext,
|
|
@@ -183,6 +197,12 @@ export interface ITemplateFieldProps extends Partial<
|
|
|
183
197
|
* arguments, array items) never receive it and stay IDE-verbatim.
|
|
184
198
|
*/
|
|
185
199
|
allowTextExpressions?: boolean;
|
|
200
|
+
/**
|
|
201
|
+
* The value is entered on tabs (qorus#646, David): Value · Expression · Visual where the field takes
|
|
202
|
+
* templates and expressions written as text, Value · Template where it takes templates only. A form's
|
|
203
|
+
* options set it (FormEngine); an expression's operands and other hosts keep the field as it was.
|
|
204
|
+
*/
|
|
205
|
+
valueTabs?: boolean;
|
|
186
206
|
/**
|
|
187
207
|
* SEAM (reqraft): host-injected per-card actions for the expression editor
|
|
188
208
|
* this field renders in expression mode (the IDE's AI-assist button).
|
|
@@ -525,6 +545,7 @@ const TemplateFieldImpl = memo(
|
|
|
525
545
|
allowTemplates = true,
|
|
526
546
|
allowFunctions,
|
|
527
547
|
allowTextExpressions,
|
|
548
|
+
valueTabs,
|
|
528
549
|
extraActions,
|
|
529
550
|
componentOverrides,
|
|
530
551
|
allowCustomValues = true,
|
|
@@ -663,15 +684,55 @@ const TemplateFieldImpl = memo(
|
|
|
663
684
|
const emptyLandsOnTemplates =
|
|
664
685
|
typeIsAnyLike && (hasTemplatesOnOffer || (!!allowCustomValues && !hasOnlyAllowedValues));
|
|
665
686
|
const opensOnTemplates = isEmptyValue && emptyLandsOnTemplates;
|
|
687
|
+
/* An untyped field holding null is written in the same text field, as `null`: the editor an untyped
|
|
688
|
+
field otherwise falls to drew null as an empty box, the same as a field holding nothing, so the
|
|
689
|
+
author could not tell the two apart (qorus#646). */
|
|
690
|
+
const nullWrittenAsText =
|
|
691
|
+
value === null && typeIsAnyLike && !!allowCustomValues && !hasOnlyAllowedValues;
|
|
692
|
+
|
|
693
|
+
/* The tabs a value is entered on (qorus#646, David): Value · Expression · Visual where the field takes
|
|
694
|
+
templates and expressions written as text, Value · Template where it takes templates only, none where
|
|
695
|
+
it takes no templates. One value, three views: see `selectTab`. */
|
|
696
|
+
const valueTabIsText = isWrittenAsTextOnTheValueTab(type as string) && !hasOnlyAllowedValues;
|
|
697
|
+
const tabsKind: 'full' | 'template' | undefined =
|
|
698
|
+
!valueTabs || !allowTemplates || editorHandlesTemplates || rest.arg_schema ? undefined
|
|
699
|
+
: allowFunctions && allowTextExpressions && !hasOnlyAllowedValues ? 'full'
|
|
700
|
+
: 'template';
|
|
701
|
+
/** The value is an expression (its AST), as the host says. */
|
|
702
|
+
const valueIsExpression =
|
|
703
|
+
!!(isFunction || isDefaultFunction) && value !== null && typeof value === 'object';
|
|
704
|
+
/** Where a saved value opens: an expression on Expression; a template on Value where it is written as
|
|
705
|
+
* text, else on Expression (or Template, for a template-only field); anything else on Value. */
|
|
706
|
+
const [tab, setTab] = useState<TValueTab>(() => {
|
|
707
|
+
if (!tabsKind) return 'value';
|
|
708
|
+
if (valueIsExpression) return tabsKind === 'full' ? 'expression' : 'value';
|
|
709
|
+
if (typeof value === 'string' && isValueTemplate(value) && !valueTabIsText) {
|
|
710
|
+
return tabsKind === 'full' ? 'expression' : 'template';
|
|
711
|
+
}
|
|
712
|
+
return 'value';
|
|
713
|
+
});
|
|
714
|
+
const opensInExpressionText = tabsKind === 'full' && tab === 'expression' && !valueIsExpression;
|
|
715
|
+
const tabsKindRef = useRef(tabsKind);
|
|
716
|
+
tabsKindRef.current = tabsKind;
|
|
717
|
+
/** What the Text view is seeded with when the value is not an expression yet; null outside it. */
|
|
718
|
+
const [expressionSeed, setExpressionSeed] = useState<string | null>(
|
|
719
|
+
opensInExpressionText ? expressionTextOfValue(value) : null
|
|
720
|
+
);
|
|
666
721
|
|
|
667
722
|
const [isTemplateState, setIsTemplate] = useState<boolean>(
|
|
668
|
-
|
|
669
|
-
|
|
723
|
+
tabsKind ?
|
|
724
|
+
tab === 'template' || (tab === 'value' && valueTabIsText)
|
|
725
|
+
: (isDefaultTemplate ||
|
|
726
|
+
isValueTemplate(value) ||
|
|
727
|
+
!allowCustomValues ||
|
|
728
|
+
opensOnTemplates ||
|
|
729
|
+
nullWrittenAsText) &&
|
|
730
|
+
!!allowTemplates
|
|
670
731
|
);
|
|
671
732
|
|
|
672
733
|
const isTemplate = editorHandlesTemplates ? false : isTemplateState;
|
|
673
734
|
const [internalIsFunction, setInternalIsFunction] = useState<boolean>(
|
|
674
|
-
!!isDefaultFunction && !!allowFunctions
|
|
735
|
+
tabsKind ? tab === 'expression' || tab === 'visual' : !!isDefaultFunction && !!allowFunctions
|
|
675
736
|
);
|
|
676
737
|
const [templateValue, setTemplateValue] = useState<string | null>(value);
|
|
677
738
|
|
|
@@ -701,6 +762,12 @@ const TemplateFieldImpl = memo(
|
|
|
701
762
|
if (isTemplate && isValueTemplate(value)) {
|
|
702
763
|
setTemplateValue(value);
|
|
703
764
|
}
|
|
765
|
+
/* Emptied from outside the editor - the card's "Clear value", an undo, a host's reset - the text the
|
|
766
|
+
value is written in is emptied too. It kept its own copy, so a cleared value went on showing what it
|
|
767
|
+
had held (qorus#646: "Clear value" had no effect). The editor's own emptying sets this already. */
|
|
768
|
+
if (isEmptyValue && templateValue) {
|
|
769
|
+
setTemplateValue(null);
|
|
770
|
+
}
|
|
704
771
|
}, [JSON.stringify(value)]);
|
|
705
772
|
|
|
706
773
|
/* Clearing an untyped field returns it to the template selector, which is
|
|
@@ -766,8 +833,22 @@ const TemplateFieldImpl = memo(
|
|
|
766
833
|
}
|
|
767
834
|
}, [allowCustomValues]);
|
|
768
835
|
|
|
836
|
+
// null arriving later (written in the Text tab, say) is written in the text field too - see above
|
|
769
837
|
useEffect(() => {
|
|
770
|
-
if (!isTemplate &&
|
|
838
|
+
if (!isTemplate && nullWrittenAsText && allowTemplates) {
|
|
839
|
+
setTemplateValue(null);
|
|
840
|
+
setIsTemplate(true);
|
|
841
|
+
}
|
|
842
|
+
}, [nullWrittenAsText, allowTemplates]);
|
|
843
|
+
|
|
844
|
+
useEffect(() => {
|
|
845
|
+
// a template on the Expression tab stays there: it is the expression editor's to show
|
|
846
|
+
if (
|
|
847
|
+
!isTemplate &&
|
|
848
|
+
isValueTemplate(value) &&
|
|
849
|
+
allowTemplates &&
|
|
850
|
+
!(tabsKind && internalIsFunction)
|
|
851
|
+
) {
|
|
771
852
|
// In auto mode, leave user-typed dollar-strings alone ('$foo: hello'
|
|
772
853
|
// passes the loose check) — but a string that IS one well-formed token
|
|
773
854
|
// ($data:{…}, $config:item) must still flip into template mode, or an
|
|
@@ -799,6 +880,13 @@ const TemplateFieldImpl = memo(
|
|
|
799
880
|
// When template key or template value change run the onChange function
|
|
800
881
|
useUpdateEffect(() => {
|
|
801
882
|
if (templateValue) {
|
|
883
|
+
/* Read from the text where the field is written as text: a lone template takes its own
|
|
884
|
+
type in an untyped field, a scalar's literal is that literal, other text is text. */
|
|
885
|
+
if (templateSupportsCustomValues) {
|
|
886
|
+
const read = templateTextValue(templateValue, type as string, filteredTemplates);
|
|
887
|
+
onChange?.(name, read.value, read.type as TQorusType, effectiveIsFunction);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
802
890
|
onChange?.(name, templateValue, type as TQorusType, effectiveIsFunction);
|
|
803
891
|
}
|
|
804
892
|
}, [JSON.stringify(templateValue)]);
|
|
@@ -808,15 +896,6 @@ const TemplateFieldImpl = memo(
|
|
|
808
896
|
const showTemplateToggle =
|
|
809
897
|
allowCustomValues && allowTemplates && !rest.arg_schema && !editorHandlesTemplates;
|
|
810
898
|
|
|
811
|
-
// Only a BRACED context ref (`$data:{…}` — machine-written, nobody types
|
|
812
|
-
// one) renders as the picker chip (named via `resolveTemplateLabel`)
|
|
813
|
-
// rather than as its raw text in a string editor. Plain word-path tokens
|
|
814
|
-
// (`$local:id`) are typeable, so per the build #123 review they keep the
|
|
815
|
-
// input that offers templates while typing. Mixed text-and-token strings
|
|
816
|
-
// keep the string editor too; the chip-in-editor treatment arrives with
|
|
817
|
-
// the rich-text string mode.
|
|
818
|
-
const templateValueIsBracedToken = isBracedTemplateToken(templateValue);
|
|
819
|
-
|
|
820
899
|
/* An UNTYPED field can be typed into as well as picked from.
|
|
821
900
|
|
|
822
901
|
Template mode has a typable editor whenever the field's type can hold
|
|
@@ -832,11 +911,19 @@ const TemplateFieldImpl = memo(
|
|
|
832
911
|
specific to that field: every empty untyped field with templates on
|
|
833
912
|
offer got the same downgrade. The editor still offers the same templates
|
|
834
913
|
on focus, so nothing is lost by being able to type as well. */
|
|
914
|
+
/* And so can a scalar: a whole number, a number or a yes/no is written as text too
|
|
915
|
+
(qorus#646). A template chosen into it is a chip in that text, not a pick-only
|
|
916
|
+
control that holds the template and nothing else, so text can be written around it;
|
|
917
|
+
what the field then holds is read from the text (writtenAsText). A date keeps its
|
|
918
|
+
date control. */
|
|
835
919
|
const templateSupportsCustomValues =
|
|
836
|
-
allowCustomValues && (type
|
|
920
|
+
allowCustomValues && isWrittenAsText(type as string) && !hasOnlyAllowedValues;
|
|
837
921
|
const showTemplatesDropdown =
|
|
838
922
|
allowTemplates && (!allowCustomValues || (isTemplate && !templateSupportsCustomValues));
|
|
839
923
|
const hasOnlyExpressions = !allowCustomValues && !allowTemplates && allowFunctions;
|
|
924
|
+
/** A date field with templates on offer: they are offered beside its date control. */
|
|
925
|
+
const dateTakesTemplates =
|
|
926
|
+
type === 'date' && !!allowTemplates && hasTemplatesOnOffer && !hasOnlyAllowedValues;
|
|
840
927
|
// True when some input control renders besides the ⋮ menu. When nothing
|
|
841
928
|
// does (an empty `any` field: custom values are disallowed and the value's
|
|
842
929
|
// type is picked FROM the menu), the menu trigger is the field's only
|
|
@@ -860,8 +947,11 @@ const TemplateFieldImpl = memo(
|
|
|
860
947
|
/* Emptying the text leaves the field where an empty field lands. For
|
|
861
948
|
an untyped field that is THIS editor: switching to custom mode here
|
|
862
949
|
mounted a different editor in its place, so deleting the last
|
|
863
|
-
character lost the cursor.
|
|
864
|
-
|
|
950
|
+
character lost the cursor. A field written as text (a whole number, a
|
|
951
|
+
number, a yes/no) is the same: deleting the template in it left a
|
|
952
|
+
different, empty editor in its place, and what was typed next went
|
|
953
|
+
nowhere. */
|
|
954
|
+
if (!((emptyLandsOnTemplates || templateSupportsCustomValues) && allowTemplates)) {
|
|
865
955
|
setIsTemplate(false);
|
|
866
956
|
}
|
|
867
957
|
setTemplateValue(null);
|
|
@@ -870,7 +960,7 @@ const TemplateFieldImpl = memo(
|
|
|
870
960
|
setTemplateValue(val);
|
|
871
961
|
}
|
|
872
962
|
},
|
|
873
|
-
[name, onChange, emptyLandsOnTemplates, allowTemplates]
|
|
963
|
+
[name, onChange, emptyLandsOnTemplates, templateSupportsCustomValues, allowTemplates]
|
|
874
964
|
);
|
|
875
965
|
|
|
876
966
|
const handleTemplateTextChange = useCallback(
|
|
@@ -923,6 +1013,15 @@ const TemplateFieldImpl = memo(
|
|
|
923
1013
|
// `SaveValueButton` in the controls menu — the saved-values storage is
|
|
924
1014
|
// IDE-only, so the menu item is dropped (`allowSaving` is inert).
|
|
925
1015
|
|
|
1016
|
+
/* A template chosen beside a date control is the value at once, IN the template control: handed to
|
|
1017
|
+
the form while the field was still drawing its date control, the reference reached the date
|
|
1018
|
+
picker first, which threw on it ("Invalid ISO 8601 date time string"). */
|
|
1019
|
+
const handleSelectDateTemplate = useCallback((item: { value?: unknown }) => {
|
|
1020
|
+
if (typeof item?.value !== 'string') return;
|
|
1021
|
+
setIsTemplate(true);
|
|
1022
|
+
setTemplateValue(item.value);
|
|
1023
|
+
}, []);
|
|
1024
|
+
|
|
926
1025
|
const handleRemoveTemplateClick = useCallback(() => {
|
|
927
1026
|
/* This is the `×` ON the template control, and it means "I do not want a
|
|
928
1027
|
template here" — on a field whose menu offers no `Set Custom Value` it
|
|
@@ -990,6 +1089,46 @@ const TemplateFieldImpl = memo(
|
|
|
990
1089
|
}
|
|
991
1090
|
}, [name, onChange, value, templateValue]);
|
|
992
1091
|
|
|
1092
|
+
/* One value, three views (qorus#646, David). Value → Expression writes the literal or template as
|
|
1093
|
+
expression text, emitting nothing until it is edited. Expression ↔ Visual is the editor's own toggle.
|
|
1094
|
+
Expression → Value converts only a literal or a lone template; any other expression stays, and the
|
|
1095
|
+
Value tab says it cannot show it and offers to replace it. Nothing is lost silently. */
|
|
1096
|
+
const selectTab = useCallback(
|
|
1097
|
+
(next: TValueTab) => {
|
|
1098
|
+
if (next === tab) return;
|
|
1099
|
+
if (next === 'value' || next === 'template') {
|
|
1100
|
+
if (valueIsExpression) {
|
|
1101
|
+
const envelope = { is_expression: true, value };
|
|
1102
|
+
const template = loneTemplateOf(envelope);
|
|
1103
|
+
const literal = template === undefined ? loneValueOf(envelope) : undefined;
|
|
1104
|
+
if (template !== undefined) {
|
|
1105
|
+
setTemplateValue(template);
|
|
1106
|
+
onChange?.(name, template, type as TQorusType, false);
|
|
1107
|
+
} else if (literal !== undefined) {
|
|
1108
|
+
setTemplateValue(valueTextOf(literal.value, type as string) || null);
|
|
1109
|
+
onChange?.(name, literal.value, type as TQorusType, false);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
setInternalIsFunction(false);
|
|
1113
|
+
setExpressionFromText(null);
|
|
1114
|
+
setIsTemplate(next === 'template' || valueTabIsText);
|
|
1115
|
+
} else {
|
|
1116
|
+
if (!valueIsExpression) setExpressionSeed(expressionTextOfValue(value));
|
|
1117
|
+
setInternalIsFunction(true);
|
|
1118
|
+
}
|
|
1119
|
+
setTab(next);
|
|
1120
|
+
},
|
|
1121
|
+
[tab, valueIsExpression, value, name, onChange, type, valueTabIsText]
|
|
1122
|
+
);
|
|
1123
|
+
|
|
1124
|
+
/** Replace what a tab cannot show with an empty value of its own kind. */
|
|
1125
|
+
const replaceWithAValue = useCallback(() => {
|
|
1126
|
+
setInternalIsFunction(false);
|
|
1127
|
+
setTemplateValue(null);
|
|
1128
|
+
setExpressionSeed(null);
|
|
1129
|
+
onChange?.(name, undefined, type as TQorusType, false);
|
|
1130
|
+
}, [name, onChange, type]);
|
|
1131
|
+
|
|
993
1132
|
const handleTemplateToggleClick = useCallback(() => {
|
|
994
1133
|
setInternalIsFunction(false);
|
|
995
1134
|
onChange(name, undefined, undefined, false);
|
|
@@ -1001,12 +1140,30 @@ const TemplateFieldImpl = memo(
|
|
|
1001
1140
|
(expressionValue: IExpression | undefined, remove: boolean) => {
|
|
1002
1141
|
if (remove) {
|
|
1003
1142
|
setInternalIsFunction(false);
|
|
1143
|
+
setExpressionSeed(null);
|
|
1004
1144
|
// The expression is gone, so there is no longer a switch to undo.
|
|
1005
1145
|
setExpressionFromText(null);
|
|
1006
1146
|
}
|
|
1147
|
+
/* On the Expression tab, a lone template is the template: stored bare, as every consumer
|
|
1148
|
+
evaluates it (see `loneTemplateOf`) - the server evaluates its expression form, template(...),
|
|
1149
|
+
to the template's parts, not to its value. The Text view stays, seeded with it. */
|
|
1150
|
+
const onTabs = !!tabsKindRef.current;
|
|
1151
|
+
const lone = remove || !onTabs ? undefined : loneTemplateOf(expressionValue);
|
|
1152
|
+
if (lone !== undefined) {
|
|
1153
|
+
setExpressionSeed(lone);
|
|
1154
|
+
onChange(name, lone, type as TQorusType, false);
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
// and a lone value (12, true) is that value, the field's own shape (see `loneValueOf`)
|
|
1158
|
+
const literal = remove || !onTabs ? undefined : loneValueOf(expressionValue);
|
|
1159
|
+
if (literal !== undefined) {
|
|
1160
|
+
setExpressionSeed(expressionSeedOf(literal.value));
|
|
1161
|
+
onChange(name, literal.value, type as TQorusType, false);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1007
1164
|
onChange(name, expressionValue?.value, expressionDataType as TQorusType, !remove);
|
|
1008
1165
|
},
|
|
1009
|
-
[name, onChange, expressionDataType, value]
|
|
1166
|
+
[name, onChange, expressionDataType, value, type]
|
|
1010
1167
|
);
|
|
1011
1168
|
|
|
1012
1169
|
// ─── Text typed into a plain field that is really a DPQL expression ───
|
|
@@ -1046,6 +1203,8 @@ const TemplateFieldImpl = memo(
|
|
|
1046
1203
|
setInternalIsFunction(true);
|
|
1047
1204
|
setIsTemplate(false);
|
|
1048
1205
|
setTemplateValue(null);
|
|
1206
|
+
// the Value tab never holds an expression: detected, it moves to the Expression tab, with an undo
|
|
1207
|
+
if (tabsKindRef.current === 'full') setTab('expression');
|
|
1049
1208
|
// `dpql/parse` answers with the field-ready envelope
|
|
1050
1209
|
// (`{is_expression, value}`); this field stores the inner AST and
|
|
1051
1210
|
// signals the flag through `onChange`'s fourth argument, exactly as
|
|
@@ -1062,6 +1221,10 @@ const TemplateFieldImpl = memo(
|
|
|
1062
1221
|
|
|
1063
1222
|
declinedTexts.current.add(expressionFromText);
|
|
1064
1223
|
setInternalIsFunction(false);
|
|
1224
|
+
if (tabsKind) {
|
|
1225
|
+
setTab('value');
|
|
1226
|
+
setIsTemplate(valueTabIsText);
|
|
1227
|
+
}
|
|
1065
1228
|
setExpressionFromText(null);
|
|
1066
1229
|
onChange?.(name, expressionFromText, type as TQorusType, false);
|
|
1067
1230
|
}, [expressionFromText, name, onChange, type]);
|
|
@@ -1097,7 +1260,8 @@ const TemplateFieldImpl = memo(
|
|
|
1097
1260
|
!!allowFunctions &&
|
|
1098
1261
|
!!allowTextExpressions &&
|
|
1099
1262
|
!effectiveIsFunction &&
|
|
1100
|
-
|
|
1263
|
+
// the template editor is text too: an expression written around a template is detected there
|
|
1264
|
+
(!isTemplate || templateSupportsCustomValues) &&
|
|
1101
1265
|
!hasOnlyAllowedValues &&
|
|
1102
1266
|
!rest.readonly &&
|
|
1103
1267
|
!rest.readOnly &&
|
|
@@ -1149,7 +1313,8 @@ const TemplateFieldImpl = memo(
|
|
|
1149
1313
|
|
|
1150
1314
|
const outcome = classifyTypedText({ text, type, field: validationField, parsed });
|
|
1151
1315
|
|
|
1152
|
-
|
|
1316
|
+
// on tabs, an expression detected switches (with an undo) rather than being offered
|
|
1317
|
+
if (outcome === 'switch' || (outcome === 'offer' && tabsKindRef.current === 'full')) {
|
|
1153
1318
|
enterExpressionRef.current(text, parsed.expression);
|
|
1154
1319
|
} else if (outcome === 'offer') {
|
|
1155
1320
|
setDpqlOffer({ text, expression: parsed.expression });
|
|
@@ -1188,36 +1353,41 @@ const TemplateFieldImpl = memo(
|
|
|
1188
1353
|
|
|
1189
1354
|
const publishedItems = useMemo(
|
|
1190
1355
|
() => [
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1356
|
+
// on tabs, the menu keeps value actions only: the tabs are the mode switch
|
|
1357
|
+
...(tabsKind ?
|
|
1358
|
+
[]
|
|
1359
|
+
: [
|
|
1360
|
+
...(canOfferExpression && !functions.loading ?
|
|
1361
|
+
[
|
|
1362
|
+
{
|
|
1363
|
+
label: 'Use Expression',
|
|
1364
|
+
icon: 'Functions' as const,
|
|
1365
|
+
tooltip: 'Run a function on this value',
|
|
1366
|
+
onClick: handleSelectFunctionChange,
|
|
1367
|
+
},
|
|
1368
|
+
]
|
|
1369
|
+
: []),
|
|
1370
|
+
...(canOfferTemplate ?
|
|
1371
|
+
[
|
|
1372
|
+
{
|
|
1373
|
+
label: 'Use Template',
|
|
1374
|
+
icon: 'MoneyDollarCircleLine' as const,
|
|
1375
|
+
tooltip: 'Use a template',
|
|
1376
|
+
onClick: handleTemplateToggleClick,
|
|
1377
|
+
},
|
|
1378
|
+
]
|
|
1379
|
+
: []),
|
|
1380
|
+
]),
|
|
1381
|
+
...(canOfferCustomValue && !tabsKind ?
|
|
1382
|
+
[
|
|
1383
|
+
{
|
|
1384
|
+
label: 'Use Custom Value',
|
|
1385
|
+
icon: 'EditLine' as const,
|
|
1386
|
+
tooltip: 'Write the value here instead of choosing a template',
|
|
1387
|
+
onClick: handleUseCustomValueClick,
|
|
1388
|
+
},
|
|
1389
|
+
]
|
|
1390
|
+
: []),
|
|
1221
1391
|
/* The "set a value of this type" choices an untyped field offers.
|
|
1222
1392
|
Dividers are dropped: they grouped items in a menu this field drew
|
|
1223
1393
|
itself, and in the row's shared menu they would divide other
|
|
@@ -1259,14 +1429,23 @@ const TemplateFieldImpl = memo(
|
|
|
1259
1429
|
useRowMenuPublisher(rowMenu, publishedKey, publishedItems as never);
|
|
1260
1430
|
|
|
1261
1431
|
const renderControls = useCallback(() => {
|
|
1432
|
+
// on tabs, the tabs are the mode switch: the menu keeps value actions only
|
|
1262
1433
|
const showFunctionsDropdown =
|
|
1263
|
-
|
|
1264
|
-
|
|
1434
|
+
!tabsKind &&
|
|
1435
|
+
allowFunctions &&
|
|
1436
|
+
!hasOnlyAllowedValues &&
|
|
1437
|
+
!rest.readonly &&
|
|
1438
|
+
!internalIsFunction;
|
|
1439
|
+
const showTemplatesButton = !tabsKind && showTemplateToggle && !isTemplate;
|
|
1265
1440
|
// The way back out of template mode, where the editor draws no `×`.
|
|
1266
|
-
const showCustomValueButton =
|
|
1441
|
+
const showCustomValueButton =
|
|
1442
|
+
!tabsKind && showTemplateToggle && isTemplate && templateSupportsCustomValues;
|
|
1267
1443
|
// The "Set value" label promises a way to set one — reorder rows alone don't.
|
|
1268
1444
|
const hasValueRows =
|
|
1269
|
-
showFunctionsDropdown ||
|
|
1445
|
+
showFunctionsDropdown ||
|
|
1446
|
+
showTemplatesButton ||
|
|
1447
|
+
showCustomValueButton ||
|
|
1448
|
+
size(menuItems) > 0;
|
|
1270
1449
|
|
|
1271
1450
|
/* A lone group opens itself. Two of the menu's groups are collapsed
|
|
1272
1451
|
sections, so a menu holding nothing but one of them asked for a click
|
|
@@ -1394,7 +1573,7 @@ const TemplateFieldImpl = memo(
|
|
|
1394
1573
|
|
|
1395
1574
|
: null}
|
|
1396
1575
|
|
|
1397
|
-
{showTemplatesButton ?
|
|
1576
|
+
{showTemplatesButton && !(showFunctionsDropdown && allowTextExpressions) ?
|
|
1398
1577
|
<ReqoreButton
|
|
1399
1578
|
transparent
|
|
1400
1579
|
icon='MoneyDollarCircleLine'
|
|
@@ -1457,6 +1636,7 @@ const TemplateFieldImpl = memo(
|
|
|
1457
1636
|
handleSelectFunctionChange,
|
|
1458
1637
|
handleTemplateToggleClick,
|
|
1459
1638
|
handleUseCustomValueClick,
|
|
1639
|
+
allowTextExpressions,
|
|
1460
1640
|
hasOnlyAllowedValues,
|
|
1461
1641
|
isTemplate,
|
|
1462
1642
|
rest.readonly,
|
|
@@ -1475,6 +1655,7 @@ const TemplateFieldImpl = memo(
|
|
|
1475
1655
|
hasInputAffordance,
|
|
1476
1656
|
// the row decides whether this field draws a menu at all
|
|
1477
1657
|
rowMenu,
|
|
1658
|
+
tabsKind,
|
|
1478
1659
|
]);
|
|
1479
1660
|
|
|
1480
1661
|
// When the type is a list, and it has an element type - that element type is different
|
|
@@ -1503,6 +1684,228 @@ const TemplateFieldImpl = memo(
|
|
|
1503
1684
|
type,
|
|
1504
1685
|
]);
|
|
1505
1686
|
|
|
1687
|
+
// ─── Value · Expression · Visual (or Value · Template) ───────────────────────────────────────────
|
|
1688
|
+
if (tabsKind && !rest.disabled) {
|
|
1689
|
+
const tabButton = (
|
|
1690
|
+
key: TValueTab,
|
|
1691
|
+
label: string,
|
|
1692
|
+
icon: IReqoreIconName,
|
|
1693
|
+
tooltip?: string
|
|
1694
|
+
) => (
|
|
1695
|
+
<ReqoreButton
|
|
1696
|
+
key={key}
|
|
1697
|
+
compact
|
|
1698
|
+
size={rest.size}
|
|
1699
|
+
icon={icon}
|
|
1700
|
+
active={tab === key}
|
|
1701
|
+
aria-pressed={tab === key}
|
|
1702
|
+
data-tab={label}
|
|
1703
|
+
className={`value-tab value-tab-${key}`}
|
|
1704
|
+
tooltip={tooltip}
|
|
1705
|
+
disabled={rest.readOnly || rest.readonly}
|
|
1706
|
+
onClick={() => selectTab(key)}
|
|
1707
|
+
>
|
|
1708
|
+
{label}
|
|
1709
|
+
</ReqoreButton>
|
|
1710
|
+
);
|
|
1711
|
+
/* A lone template of another type than the field's is converted when the value is used: said as a
|
|
1712
|
+
warning, not a block - whether it converts depends on the data (qorus#646, David). */
|
|
1713
|
+
const loneTemplate =
|
|
1714
|
+
typeof value === 'string' && isCompleteTemplateToken(value) ? value : undefined;
|
|
1715
|
+
const templateBadge =
|
|
1716
|
+
loneTemplate && !typeIsAnyLike ?
|
|
1717
|
+
findTemplate(templates ?? {}, loneTemplate)?.badge
|
|
1718
|
+
: undefined;
|
|
1719
|
+
const conversionNote =
|
|
1720
|
+
(
|
|
1721
|
+
typeof templateBadge === 'string' &&
|
|
1722
|
+
templateBadge &&
|
|
1723
|
+
!sameValueType(templateBadge, type as string)
|
|
1724
|
+
) ?
|
|
1725
|
+
`${templateTypeName(templateBadge)} is converted to ${templateTypeName(type as string).toLowerCase()} when the value is used; a value that does not convert fails`
|
|
1726
|
+
: undefined;
|
|
1727
|
+
const cannotShow = (message: string) => (
|
|
1728
|
+
<ReqoreMessage
|
|
1729
|
+
size='small'
|
|
1730
|
+
flat
|
|
1731
|
+
opaque={false}
|
|
1732
|
+
intent='muted'
|
|
1733
|
+
className='value-tab-cannot-show'
|
|
1734
|
+
>
|
|
1735
|
+
{message}{' '}
|
|
1736
|
+
<ReqoreButton
|
|
1737
|
+
compact
|
|
1738
|
+
size={rest.size}
|
|
1739
|
+
className='value-tab-replace'
|
|
1740
|
+
onClick={replaceWithAValue}
|
|
1741
|
+
disabled={rest.readOnly || rest.readonly}
|
|
1742
|
+
>
|
|
1743
|
+
Replace it with a value
|
|
1744
|
+
</ReqoreButton>
|
|
1745
|
+
</ReqoreMessage>
|
|
1746
|
+
);
|
|
1747
|
+
const valueBody = () => {
|
|
1748
|
+
if (valueIsExpression) {
|
|
1749
|
+
return cannotShow("This expression can't be shown as a value.");
|
|
1750
|
+
}
|
|
1751
|
+
if (valueTabIsText) {
|
|
1752
|
+
return (
|
|
1753
|
+
<ReqoreControlGroup vertical fluid gapSize='small'>
|
|
1754
|
+
<RichTextFormField
|
|
1755
|
+
className='template-selector value-tab-text'
|
|
1756
|
+
valueFormat='text'
|
|
1757
|
+
singleLine={isSingleLineStringType('string')}
|
|
1758
|
+
value={
|
|
1759
|
+
typeof templateValue === 'string' ? templateValue : (
|
|
1760
|
+
valueTextOf(value, type as string)
|
|
1761
|
+
)
|
|
1762
|
+
}
|
|
1763
|
+
templates={filteredTemplates}
|
|
1764
|
+
// a chip of a field not of this type is named as the catalogue names it
|
|
1765
|
+
namingTemplates={templates}
|
|
1766
|
+
allowTemplates
|
|
1767
|
+
onChange={handleTemplateTextChange}
|
|
1768
|
+
{...rest}
|
|
1769
|
+
fixed={false}
|
|
1770
|
+
fluid
|
|
1771
|
+
panelProps={{
|
|
1772
|
+
...(rest as { panelProps?: object }).panelProps,
|
|
1773
|
+
style: {
|
|
1774
|
+
...(rest as { panelProps?: { style?: object } }).panelProps?.style,
|
|
1775
|
+
width: '150px',
|
|
1776
|
+
maxWidth: '100%',
|
|
1777
|
+
minWidth: 'fit-content',
|
|
1778
|
+
},
|
|
1779
|
+
}}
|
|
1780
|
+
aria-label={fieldAriaLabel}
|
|
1781
|
+
/>
|
|
1782
|
+
{conversionNote ?
|
|
1783
|
+
<ReqoreMessage
|
|
1784
|
+
size='small'
|
|
1785
|
+
flat
|
|
1786
|
+
opaque={false}
|
|
1787
|
+
intent='warning'
|
|
1788
|
+
className='value-tab-conversion'
|
|
1789
|
+
>
|
|
1790
|
+
{conversionNote}
|
|
1791
|
+
</ReqoreMessage>
|
|
1792
|
+
: null}
|
|
1793
|
+
</ReqoreControlGroup>
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
if (loneTemplate !== undefined || (typeof value === 'string' && isValueTemplate(value))) {
|
|
1797
|
+
return cannotShow(
|
|
1798
|
+
`A template is shown on the ${tabsKind === 'full' ? 'Expression' : 'Template'} tab, not here.`
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
1801
|
+
return (
|
|
1802
|
+
<Component
|
|
1803
|
+
value={value}
|
|
1804
|
+
allowTemplates={false}
|
|
1805
|
+
onChange={onChange}
|
|
1806
|
+
name={name}
|
|
1807
|
+
level={level}
|
|
1808
|
+
{...rest}
|
|
1809
|
+
componentOverrides={componentOverrides}
|
|
1810
|
+
{...componentTypeProp}
|
|
1811
|
+
aria-label={fieldAriaLabel}
|
|
1812
|
+
className={`${className} template-selector`}
|
|
1813
|
+
/>
|
|
1814
|
+
);
|
|
1815
|
+
};
|
|
1816
|
+
const expressionBody = () => (
|
|
1817
|
+
<ReqoreControlGroup vertical fluid gapSize='small'>
|
|
1818
|
+
<ReqoreErrorBoundary>
|
|
1819
|
+
<ExpressionField
|
|
1820
|
+
/* A value that is not an expression yet is shown as the server reads it written alone:
|
|
1821
|
+
value(12) - so the Visual tab starts from the value, not from an operand with no operation. */
|
|
1822
|
+
value={{
|
|
1823
|
+
is_expression: true,
|
|
1824
|
+
value:
|
|
1825
|
+
value !== null && typeof value === 'object' ? value
|
|
1826
|
+
: !isEmptyValue && value !== null ?
|
|
1827
|
+
{ exp: 'value', args: [{ type: type as TQorusType, value }] }
|
|
1828
|
+
: undefined,
|
|
1829
|
+
}}
|
|
1830
|
+
initialText={expressionSeed ?? undefined}
|
|
1831
|
+
heldAsValue={!isEmptyValue && value !== null && typeof value !== 'object'}
|
|
1832
|
+
componentOverrides={componentOverrides}
|
|
1833
|
+
localTemplates={templates}
|
|
1834
|
+
type={type as string}
|
|
1835
|
+
returnType={(returnType || expressionDataType) as any}
|
|
1836
|
+
onChange={handleExpressionChange}
|
|
1837
|
+
readOnly={rest.readOnly || rest.disabled}
|
|
1838
|
+
expressions={rest.expressions}
|
|
1839
|
+
expressionsUrl={rest.expressions_url}
|
|
1840
|
+
fields={rest.dpql_fields}
|
|
1841
|
+
serverHandled={rest.server_expression_handling}
|
|
1842
|
+
extraActions={extraActions}
|
|
1843
|
+
size={rest.size}
|
|
1844
|
+
defaultMode={tab === 'visual' ? 'visual' : 'text'}
|
|
1845
|
+
requestedMode={tab === 'visual' ? 'visual' : 'text'}
|
|
1846
|
+
hideModeToggle
|
|
1847
|
+
// no language server: the Visual tab is where the expression is built
|
|
1848
|
+
onTextUnavailable={() => setTab('visual')}
|
|
1849
|
+
reorder={reorder}
|
|
1850
|
+
/>
|
|
1851
|
+
</ReqoreErrorBoundary>
|
|
1852
|
+
{expressionFromText !== null ?
|
|
1853
|
+
<ReqoreButton
|
|
1854
|
+
fixed
|
|
1855
|
+
compact
|
|
1856
|
+
minimal
|
|
1857
|
+
icon='ArrowGoBackLine'
|
|
1858
|
+
size={rest.size}
|
|
1859
|
+
className='dpql-detected-undo'
|
|
1860
|
+
tooltip={`Keep "${expressionFromText}" as a value instead`}
|
|
1861
|
+
onClick={undoExpressionFromText}
|
|
1862
|
+
>
|
|
1863
|
+
Undo
|
|
1864
|
+
</ReqoreButton>
|
|
1865
|
+
: null}
|
|
1866
|
+
</ReqoreControlGroup>
|
|
1867
|
+
);
|
|
1868
|
+
const templateBody = () => (
|
|
1869
|
+
<TemplateDropdownSelector
|
|
1870
|
+
allowCustomValues={false}
|
|
1871
|
+
templates={templates}
|
|
1872
|
+
value={typeof value === 'string' && isValueTemplate(value) ? value : null}
|
|
1873
|
+
items={filteredTemplates?.items}
|
|
1874
|
+
onItemSelect={handleSelectTemplateFromList}
|
|
1875
|
+
onRemoveClick={replaceWithAValue}
|
|
1876
|
+
size={rest.size}
|
|
1877
|
+
label={label}
|
|
1878
|
+
hasOnlyAllowedValues={hasOnlyAllowedValues}
|
|
1879
|
+
/>
|
|
1880
|
+
);
|
|
1881
|
+
return (
|
|
1882
|
+
<ReqoreControlGroup vertical fluid gapSize='small' className='value-tabs-field'>
|
|
1883
|
+
<ReqoreControlGroup fluid size={rest.size} verticalAlign='center'>
|
|
1884
|
+
<ReqoreControlGroup stack size={rest.size} className='value-tabs' fixed>
|
|
1885
|
+
{tabButton('value', 'Value', 'EditLine')}
|
|
1886
|
+
{tabsKind === 'full' ?
|
|
1887
|
+
[
|
|
1888
|
+
tabButton(
|
|
1889
|
+
'expression',
|
|
1890
|
+
'Expression',
|
|
1891
|
+
'CodeLine',
|
|
1892
|
+
'Write an expression - it also takes templates'
|
|
1893
|
+
),
|
|
1894
|
+
tabButton('visual', 'Visual', 'NodeTree', 'Build the expression visually'),
|
|
1895
|
+
]
|
|
1896
|
+
: tabButton('template', 'Template', 'MoneyDollarCircleLine', 'Choose a template')}
|
|
1897
|
+
</ReqoreControlGroup>
|
|
1898
|
+
{renderControls()}
|
|
1899
|
+
</ReqoreControlGroup>
|
|
1900
|
+
{tab === 'value' ?
|
|
1901
|
+
valueBody()
|
|
1902
|
+
: tab === 'template' ?
|
|
1903
|
+
templateBody()
|
|
1904
|
+
: expressionBody()}
|
|
1905
|
+
</ReqoreControlGroup>
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1506
1909
|
if (effectiveIsFunction && !hasOnlyAllowedValues) {
|
|
1507
1910
|
// SEAM (reqraft): `allowTextExpressions` swaps the IDE's bare builder
|
|
1508
1911
|
// for the ExpressionField shell (Visual = the same builder, Text = the
|
|
@@ -1634,21 +2037,65 @@ const TemplateFieldImpl = memo(
|
|
|
1634
2037
|
/>
|
|
1635
2038
|
: null}
|
|
1636
2039
|
|
|
2040
|
+
{/* A date keeps its date control - it is not written as text - and takes a template beside it:
|
|
2041
|
+
chosen, the template is the value (the template control, whose × returns to the date). */}
|
|
2042
|
+
{/* A date keeps its date control - it is not written as text - and takes a template beside it:
|
|
2043
|
+
chosen, the template is the value (the template control, whose × returns to the date). */}
|
|
2044
|
+
{!isTemplate && allowCustomValues && dateTakesTemplates ?
|
|
2045
|
+
<ReqoreDropdown
|
|
2046
|
+
className='date-template-picker'
|
|
2047
|
+
icon='MoneyDollarCircleLine'
|
|
2048
|
+
fixed
|
|
2049
|
+
compact
|
|
2050
|
+
size={rest.size}
|
|
2051
|
+
tooltip='Use a template'
|
|
2052
|
+
aria-label='Use a template'
|
|
2053
|
+
filterable
|
|
2054
|
+
items={filteredTemplates?.items}
|
|
2055
|
+
onItemSelect={handleSelectDateTemplate}
|
|
2056
|
+
/>
|
|
2057
|
+
: null}
|
|
2058
|
+
|
|
1637
2059
|
{/* Template mode's editor for a value that can also be typed: each
|
|
1638
2060
|
reference in it is a chip named as the catalogue names it, while the
|
|
1639
2061
|
field still stores the plain string. A textarea spelled a chosen
|
|
1640
2062
|
template `$local:name` wherever it was edited — in the Visual
|
|
1641
2063
|
builder's operands, in every string field that takes templates. */}
|
|
1642
|
-
{isTemplate && templateSupportsCustomValues
|
|
2064
|
+
{isTemplate && templateSupportsCustomValues ?
|
|
1643
2065
|
<RichTextFormField
|
|
1644
2066
|
className='template-selector'
|
|
1645
2067
|
valueFormat='text'
|
|
1646
2068
|
singleLine={isSingleLineStringType('string')}
|
|
1647
|
-
|
|
2069
|
+
/* null shows as null, not as an empty field: the two are different values, and an
|
|
2070
|
+
untyped field holding null looked exactly like one holding nothing (qorus#646) */
|
|
2071
|
+
value={
|
|
2072
|
+
typeof templateValue === 'string' ? templateValue : (
|
|
2073
|
+
(untypedTextOf(value, type as string) ?? '')
|
|
2074
|
+
)
|
|
2075
|
+
}
|
|
1648
2076
|
templates={filteredTemplates}
|
|
2077
|
+
// a chip of a field not of this type (text around it, say) is named as the catalogue names it
|
|
2078
|
+
namingTemplates={templates}
|
|
1649
2079
|
allowTemplates
|
|
1650
2080
|
onChange={handleTemplateTextChange}
|
|
1651
2081
|
{...rest}
|
|
2082
|
+
/* The text the value is written in takes the room its row gives it and gives it back: a
|
|
2083
|
+
caller's \`fixed\` sized a picker chip, and on a text field it kept the field at its full
|
|
2084
|
+
width, pushing the ⋮ past an operand's row in a narrow column. */
|
|
2085
|
+
fixed={false}
|
|
2086
|
+
fluid
|
|
2087
|
+
/* 150px wide, as a free text field is, wider for what it holds, and narrower only where its
|
|
2088
|
+
row has less room - an operand in a narrow column - as a number's input is: a fixed
|
|
2089
|
+
150px minimum held it at full width there. */
|
|
2090
|
+
panelProps={{
|
|
2091
|
+
...(rest as { panelProps?: object }).panelProps,
|
|
2092
|
+
style: {
|
|
2093
|
+
...(rest as { panelProps?: { style?: object } }).panelProps?.style,
|
|
2094
|
+
width: '150px',
|
|
2095
|
+
maxWidth: '100%',
|
|
2096
|
+
minWidth: 'fit-content',
|
|
2097
|
+
},
|
|
2098
|
+
}}
|
|
1652
2099
|
aria-label={fieldAriaLabel}
|
|
1653
2100
|
/>
|
|
1654
2101
|
: null}
|
|
@@ -1676,10 +2123,7 @@ const TemplateFieldImpl = memo(
|
|
|
1676
2123
|
</ReqoreControlGroup>
|
|
1677
2124
|
: null}
|
|
1678
2125
|
|
|
1679
|
-
{
|
|
1680
|
-
showTemplatesDropdown ||
|
|
1681
|
-
(isTemplate && templateSupportsCustomValues && templateValueIsBracedToken)
|
|
1682
|
-
) ?
|
|
2126
|
+
{showTemplatesDropdown ?
|
|
1683
2127
|
<TemplateDropdownSelector
|
|
1684
2128
|
allowCustomValues={allowCustomValues}
|
|
1685
2129
|
templates={templates}
|