mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Single legal values (#9935)
Adds an input form for single legal values (i.e. for number and semver operators). - Uses the `validator` for the constraint to check the list of legal values and provides a list of "invalid legal values" for values that don't pass validation. - Values in the "invalid legal values" set are "disabled" when rendered in the UI. Additionally, there's an extra bit of text that tells you that values that aren't valid are disabled. - Makes the legal values selector more generic and makes it adapt to multi- or single-value selection based on input props. The external interface is two separate components (to make it clearer that they are different things and because their props don't line up perfectly). Rendered: <img width="957" alt="image" src="https://github.com/user-attachments/assets/cd8d2f32-057d-4e31-8fd3-174676eeb65e" /> Still todo: testing deleted/invalid legal value detection. I'll do that as part of the big testathon in a followup.
This commit is contained in:
		
							parent
							
								
									520d708978
								
							
						
					
					
						commit
						6efbe2d545
					
				@ -16,7 +16,10 @@ import { ReactComponent as EqualsIcon } from 'assets/icons/constraint-equals.svg
 | 
				
			|||||||
import { ReactComponent as NotEqualsIcon } from 'assets/icons/constraint-not-equals.svg';
 | 
					import { ReactComponent as NotEqualsIcon } from 'assets/icons/constraint-not-equals.svg';
 | 
				
			||||||
import { AddSingleValueWidget } from './AddSingleValueWidget';
 | 
					import { AddSingleValueWidget } from './AddSingleValueWidget';
 | 
				
			||||||
import { ConstraintDateInput } from './ConstraintDateInput';
 | 
					import { ConstraintDateInput } from './ConstraintDateInput';
 | 
				
			||||||
import { LegalValuesSelector } from './LegalValuesSelector';
 | 
					import {
 | 
				
			||||||
 | 
					    LegalValuesSelector,
 | 
				
			||||||
 | 
					    SingleLegalValueSelector,
 | 
				
			||||||
 | 
					} from './LegalValuesSelector';
 | 
				
			||||||
import { useEditableConstraint } from './useEditableConstraint/useEditableConstraint';
 | 
					import { useEditableConstraint } from './useEditableConstraint/useEditableConstraint';
 | 
				
			||||||
import type { IConstraint } from 'interfaces/strategy';
 | 
					import type { IConstraint } from 'interfaces/strategy';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@ -227,9 +230,11 @@ export const EditableConstraint: FC<Props> = ({
 | 
				
			|||||||
        constraint: localConstraint,
 | 
					        constraint: localConstraint,
 | 
				
			||||||
        updateConstraint,
 | 
					        updateConstraint,
 | 
				
			||||||
        validator,
 | 
					        validator,
 | 
				
			||||||
        ...constraintMetadata
 | 
					        ...legalValueData
 | 
				
			||||||
    } = useEditableConstraint(constraint, onAutoSave);
 | 
					    } = useEditableConstraint(constraint, onAutoSave);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const isLegalValueConstraint = 'legalValues' in legalValueData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { context } = useUnleashContext();
 | 
					    const { context } = useUnleashContext();
 | 
				
			||||||
    const { contextName, operator } = localConstraint;
 | 
					    const { contextName, operator } = localConstraint;
 | 
				
			||||||
    const showCaseSensitiveButton = isStringOperator(operator);
 | 
					    const showCaseSensitiveButton = isStringOperator(operator);
 | 
				
			||||||
@ -327,25 +332,37 @@ export const EditableConstraint: FC<Props> = ({
 | 
				
			|||||||
                        values={
 | 
					                        values={
 | 
				
			||||||
                            isMultiValueConstraint(localConstraint)
 | 
					                            isMultiValueConstraint(localConstraint)
 | 
				
			||||||
                                ? Array.from(localConstraint.values)
 | 
					                                ? Array.from(localConstraint.values)
 | 
				
			||||||
 | 
					                                : 'legalValues' in legalValueData &&
 | 
				
			||||||
 | 
					                                    localConstraint.value
 | 
				
			||||||
 | 
					                                  ? [localConstraint.value]
 | 
				
			||||||
                                  : undefined
 | 
					                                  : undefined
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        removeValue={(value) =>
 | 
					                        removeValue={(value) => {
 | 
				
			||||||
 | 
					                            if (isMultiValueConstraint(localConstraint)) {
 | 
				
			||||||
                                updateConstraint({
 | 
					                                updateConstraint({
 | 
				
			||||||
                                    type: 'remove value from list',
 | 
					                                    type: 'remove value from list',
 | 
				
			||||||
                                    payload: value,
 | 
					                                    payload: value,
 | 
				
			||||||
                            })
 | 
					                                });
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                updateConstraint({
 | 
				
			||||||
 | 
					                                    type: 'set value',
 | 
				
			||||||
 | 
					                                    payload: '',
 | 
				
			||||||
 | 
					                                });
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
                        getExternalFocusTarget={() =>
 | 
					                        getExternalFocusTarget={() =>
 | 
				
			||||||
                            addValuesButtonRef.current ??
 | 
					                            addValuesButtonRef.current ??
 | 
				
			||||||
                            deleteButtonRef.current
 | 
					                            deleteButtonRef.current
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
 | 
					                        {isLegalValueConstraint ? null : (
 | 
				
			||||||
                            <TopRowInput
 | 
					                            <TopRowInput
 | 
				
			||||||
                                localConstraint={localConstraint}
 | 
					                                localConstraint={localConstraint}
 | 
				
			||||||
                                updateConstraint={updateConstraint}
 | 
					                                updateConstraint={updateConstraint}
 | 
				
			||||||
                                validator={validator}
 | 
					                                validator={validator}
 | 
				
			||||||
                                addValuesButtonRef={addValuesButtonRef}
 | 
					                                addValuesButtonRef={addValuesButtonRef}
 | 
				
			||||||
                            />
 | 
					                            />
 | 
				
			||||||
 | 
					                        )}
 | 
				
			||||||
                    </ValueList>
 | 
					                    </ValueList>
 | 
				
			||||||
                </ConstraintDetails>
 | 
					                </ConstraintDetails>
 | 
				
			||||||
                <ButtonPlaceholder />
 | 
					                <ButtonPlaceholder />
 | 
				
			||||||
@ -361,9 +378,9 @@ export const EditableConstraint: FC<Props> = ({
 | 
				
			|||||||
                    </StyledIconButton>
 | 
					                    </StyledIconButton>
 | 
				
			||||||
                </HtmlTooltip>
 | 
					                </HtmlTooltip>
 | 
				
			||||||
            </TopRow>
 | 
					            </TopRow>
 | 
				
			||||||
            {'legalValues' in constraintMetadata &&
 | 
					            {'legalValues' in legalValueData ? (
 | 
				
			||||||
            isMultiValueConstraint(localConstraint) ? (
 | 
					 | 
				
			||||||
                <LegalValuesContainer>
 | 
					                <LegalValuesContainer>
 | 
				
			||||||
 | 
					                    {isMultiValueConstraint(localConstraint) ? (
 | 
				
			||||||
                        <LegalValuesSelector
 | 
					                        <LegalValuesSelector
 | 
				
			||||||
                            values={localConstraint.values}
 | 
					                            values={localConstraint.values}
 | 
				
			||||||
                            clearAll={() =>
 | 
					                            clearAll={() =>
 | 
				
			||||||
@ -383,11 +400,25 @@ export const EditableConstraint: FC<Props> = ({
 | 
				
			|||||||
                                    payload: value,
 | 
					                                    payload: value,
 | 
				
			||||||
                                })
 | 
					                                })
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        deletedLegalValues={
 | 
					                            {...legalValueData}
 | 
				
			||||||
                            constraintMetadata.deletedLegalValues
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        legalValues={constraintMetadata.legalValues}
 | 
					 | 
				
			||||||
                        />
 | 
					                        />
 | 
				
			||||||
 | 
					                    ) : (
 | 
				
			||||||
 | 
					                        <SingleLegalValueSelector
 | 
				
			||||||
 | 
					                            value={localConstraint.value}
 | 
				
			||||||
 | 
					                            clear={() =>
 | 
				
			||||||
 | 
					                                updateConstraint({
 | 
				
			||||||
 | 
					                                    type: 'clear values',
 | 
				
			||||||
 | 
					                                })
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            addValue={(newValues) =>
 | 
				
			||||||
 | 
					                                updateConstraint({
 | 
				
			||||||
 | 
					                                    type: 'set value',
 | 
				
			||||||
 | 
					                                    payload: newValues,
 | 
				
			||||||
 | 
					                                })
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            {...legalValueData}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
                </LegalValuesContainer>
 | 
					                </LegalValuesContainer>
 | 
				
			||||||
            ) : null}
 | 
					            ) : null}
 | 
				
			||||||
        </Container>
 | 
					        </Container>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { useState } from 'react';
 | 
					import { type FC, useId, useState } from 'react';
 | 
				
			||||||
import { Alert, Button, Checkbox, styled } from '@mui/material';
 | 
					import { Alert, Button, Checkbox, Radio, styled } from '@mui/material';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    filterLegalValues,
 | 
					    filterLegalValues,
 | 
				
			||||||
    LegalValueLabel,
 | 
					    LegalValueLabel,
 | 
				
			||||||
@ -7,15 +7,6 @@ import {
 | 
				
			|||||||
import { ConstraintValueSearch } from './ConstraintValueSearch';
 | 
					import { ConstraintValueSearch } from './ConstraintValueSearch';
 | 
				
			||||||
import type { ILegalValue } from 'interfaces/context';
 | 
					import type { ILegalValue } from 'interfaces/context';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type LegalValuesSelectorProps = {
 | 
					 | 
				
			||||||
    values: Set<string>;
 | 
					 | 
				
			||||||
    addValues: (values: string[]) => void;
 | 
					 | 
				
			||||||
    removeValue: (value: string) => void;
 | 
					 | 
				
			||||||
    clearAll: () => void;
 | 
					 | 
				
			||||||
    deletedLegalValues?: Set<string>;
 | 
					 | 
				
			||||||
    legalValues: ILegalValue[];
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const StyledValuesContainer = styled('div')(({ theme }) => ({
 | 
					const StyledValuesContainer = styled('div')(({ theme }) => ({
 | 
				
			||||||
    display: 'grid',
 | 
					    display: 'grid',
 | 
				
			||||||
    gridTemplateColumns: 'repeat(auto-fit, minmax(120px, 1fr))',
 | 
					    gridTemplateColumns: 'repeat(auto-fit, minmax(120px, 1fr))',
 | 
				
			||||||
@ -37,36 +28,39 @@ const LegalValuesSelectorWidget = styled('article')(({ theme }) => ({
 | 
				
			|||||||
    gap: theme.spacing(2),
 | 
					    gap: theme.spacing(2),
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const LegalValuesSelector = ({
 | 
					type BaseProps = {
 | 
				
			||||||
 | 
					    legalValues: ILegalValue[];
 | 
				
			||||||
 | 
					    onChange: (value: string) => void;
 | 
				
			||||||
 | 
					    deletedLegalValues?: Set<string>;
 | 
				
			||||||
 | 
					    invalidLegalValues?: Set<string>;
 | 
				
			||||||
 | 
					    isInputSelected: (value: string) => boolean;
 | 
				
			||||||
 | 
					    multiSelect?: {
 | 
				
			||||||
 | 
					        selectAll: () => void;
 | 
				
			||||||
 | 
					        clearAll: () => void;
 | 
				
			||||||
 | 
					        values: Set<string>;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BaseLegalValueSelector: FC<BaseProps> = ({
 | 
				
			||||||
    legalValues,
 | 
					    legalValues,
 | 
				
			||||||
    values,
 | 
					    onChange,
 | 
				
			||||||
    addValues,
 | 
					 | 
				
			||||||
    removeValue,
 | 
					 | 
				
			||||||
    clearAll,
 | 
					 | 
				
			||||||
    deletedLegalValues,
 | 
					    deletedLegalValues,
 | 
				
			||||||
}: LegalValuesSelectorProps) => {
 | 
					    invalidLegalValues,
 | 
				
			||||||
 | 
					    isInputSelected: isInputChecked,
 | 
				
			||||||
 | 
					    multiSelect,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
    const [filter, setFilter] = useState('');
 | 
					    const [filter, setFilter] = useState('');
 | 
				
			||||||
 | 
					    const labelId = useId();
 | 
				
			||||||
 | 
					    const descriptionId = useId();
 | 
				
			||||||
 | 
					    const groupNameId = useId();
 | 
				
			||||||
 | 
					    const alertId = useId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const filteredValues = filterLegalValues(legalValues, filter);
 | 
					    const filteredValues = filterLegalValues(legalValues, filter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onChange = (legalValue: string) => {
 | 
					    const isAllSelected =
 | 
				
			||||||
        if (values.has(legalValue)) {
 | 
					        multiSelect &&
 | 
				
			||||||
            removeValue(legalValue);
 | 
					        legalValues.length ===
 | 
				
			||||||
        } else {
 | 
					            multiSelect.values.size + (deletedLegalValues?.size ?? 0);
 | 
				
			||||||
            addValues([legalValue]);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const isAllSelected = legalValues.every((value) => values.has(value.value));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const onSelectAll = () => {
 | 
					 | 
				
			||||||
        if (isAllSelected) {
 | 
					 | 
				
			||||||
            clearAll();
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            addValues(legalValues.map(({ value }) => value));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleSearchKeyDown = (event: React.KeyboardEvent) => {
 | 
					    const handleSearchKeyDown = (event: React.KeyboardEvent) => {
 | 
				
			||||||
        if (event.key === 'Enter') {
 | 
					        if (event.key === 'Enter') {
 | 
				
			||||||
@ -77,11 +71,12 @@ export const LegalValuesSelector = ({
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    const Control = multiSelect ? Checkbox : Radio;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <LegalValuesSelectorWidget>
 | 
					        <LegalValuesSelectorWidget>
 | 
				
			||||||
            {deletedLegalValues?.size ? (
 | 
					            {deletedLegalValues?.size ? (
 | 
				
			||||||
                <Alert severity='warning'>
 | 
					                <Alert id={alertId} severity='warning'>
 | 
				
			||||||
                    This constraint is using legal values that have been deleted
 | 
					                    This constraint is using legal values that have been deleted
 | 
				
			||||||
                    as valid options. If you save changes on this constraint and
 | 
					                    as valid options. If you save changes on this constraint and
 | 
				
			||||||
                    then save the strategy the following values will be removed:
 | 
					                    then save the strategy the following values will be removed:
 | 
				
			||||||
@ -92,36 +87,58 @@ export const LegalValuesSelector = ({
 | 
				
			|||||||
                    </ul>
 | 
					                    </ul>
 | 
				
			||||||
                </Alert>
 | 
					                </Alert>
 | 
				
			||||||
            ) : null}
 | 
					            ) : null}
 | 
				
			||||||
            <p>Select values from a predefined set</p>
 | 
					            <p>
 | 
				
			||||||
 | 
					                <span id={labelId}>Select values from a predefined set</span>
 | 
				
			||||||
 | 
					                {invalidLegalValues?.size ? (
 | 
				
			||||||
 | 
					                    <span id={descriptionId}>
 | 
				
			||||||
 | 
					                        Values that are not valid for your chosen operator have
 | 
				
			||||||
 | 
					                        been disabled.
 | 
				
			||||||
 | 
					                    </span>
 | 
				
			||||||
 | 
					                ) : null}
 | 
				
			||||||
 | 
					            </p>
 | 
				
			||||||
            <Row>
 | 
					            <Row>
 | 
				
			||||||
                <ConstraintValueSearch
 | 
					                <ConstraintValueSearch
 | 
				
			||||||
                    onKeyDown={handleSearchKeyDown}
 | 
					                    onKeyDown={handleSearchKeyDown}
 | 
				
			||||||
                    filter={filter}
 | 
					                    filter={filter}
 | 
				
			||||||
                    setFilter={setFilter}
 | 
					                    setFilter={setFilter}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
 | 
					                {multiSelect ? (
 | 
				
			||||||
                    <Button
 | 
					                    <Button
 | 
				
			||||||
                        sx={{
 | 
					                        sx={{
 | 
				
			||||||
                            whiteSpace: 'nowrap',
 | 
					                            whiteSpace: 'nowrap',
 | 
				
			||||||
                        }}
 | 
					                        }}
 | 
				
			||||||
                        variant={'text'}
 | 
					                        variant={'text'}
 | 
				
			||||||
                    onClick={onSelectAll}
 | 
					                        onClick={() => {
 | 
				
			||||||
 | 
					                            if (isAllSelected) {
 | 
				
			||||||
 | 
					                                multiSelect.clearAll();
 | 
				
			||||||
 | 
					                                return;
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                multiSelect.selectAll();
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                        {isAllSelected ? 'Unselect all' : 'Select all'}
 | 
					                        {isAllSelected ? 'Unselect all' : 'Select all'}
 | 
				
			||||||
                    </Button>
 | 
					                    </Button>
 | 
				
			||||||
 | 
					                ) : null}
 | 
				
			||||||
            </Row>
 | 
					            </Row>
 | 
				
			||||||
            <StyledValuesContainer>
 | 
					            <StyledValuesContainer
 | 
				
			||||||
 | 
					                aria-labelledby={labelId}
 | 
				
			||||||
 | 
					                aria-describedby={descriptionId}
 | 
				
			||||||
 | 
					                aria-details={alertId}
 | 
				
			||||||
 | 
					                role={multiSelect ? undefined : 'radiogroup'}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
                {filteredValues.map((match) => (
 | 
					                {filteredValues.map((match) => (
 | 
				
			||||||
                    <LegalValueLabel
 | 
					                    <LegalValueLabel
 | 
				
			||||||
                        key={match.value}
 | 
					                        key={match.value}
 | 
				
			||||||
                        legal={match}
 | 
					                        legal={match}
 | 
				
			||||||
                        filter={filter}
 | 
					                        filter={filter}
 | 
				
			||||||
                        control={
 | 
					                        control={
 | 
				
			||||||
                            <Checkbox
 | 
					                            <Control
 | 
				
			||||||
                                checked={Boolean(values.has(match.value))}
 | 
					 | 
				
			||||||
                                onChange={() => onChange(match.value)}
 | 
					 | 
				
			||||||
                                name='legal-value'
 | 
					 | 
				
			||||||
                                color='primary'
 | 
					                                color='primary'
 | 
				
			||||||
                                disabled={deletedLegalValues?.has(match.value)}
 | 
					                                name={`legal-value-${groupNameId}`}
 | 
				
			||||||
 | 
					                                checked={isInputChecked(match.value)}
 | 
				
			||||||
 | 
					                                onChange={() => onChange(match.value)}
 | 
				
			||||||
 | 
					                                disabled={invalidLegalValues?.has(match.value)}
 | 
				
			||||||
                            />
 | 
					                            />
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
@ -130,3 +147,78 @@ export const LegalValuesSelector = ({
 | 
				
			|||||||
        </LegalValuesSelectorWidget>
 | 
					        </LegalValuesSelectorWidget>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LegalValuesSelectorProps = {
 | 
				
			||||||
 | 
					    values: Set<string>;
 | 
				
			||||||
 | 
					    addValues: (values: string[]) => void;
 | 
				
			||||||
 | 
					    removeValue: (value: string) => void;
 | 
				
			||||||
 | 
					    clearAll: () => void;
 | 
				
			||||||
 | 
					    deletedLegalValues?: Set<string>;
 | 
				
			||||||
 | 
					    invalidLegalValues?: Set<string>;
 | 
				
			||||||
 | 
					    legalValues: ILegalValue[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const LegalValuesSelector = ({
 | 
				
			||||||
 | 
					    legalValues,
 | 
				
			||||||
 | 
					    values,
 | 
				
			||||||
 | 
					    addValues,
 | 
				
			||||||
 | 
					    removeValue,
 | 
				
			||||||
 | 
					    clearAll,
 | 
				
			||||||
 | 
					    ...baseProps
 | 
				
			||||||
 | 
					}: LegalValuesSelectorProps) => {
 | 
				
			||||||
 | 
					    const onChange = (legalValue: string) => {
 | 
				
			||||||
 | 
					        if (values.has(legalValue)) {
 | 
				
			||||||
 | 
					            removeValue(legalValue);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            addValues([legalValue]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <BaseLegalValueSelector
 | 
				
			||||||
 | 
					            legalValues={legalValues}
 | 
				
			||||||
 | 
					            isInputSelected={(inputValue) => values.has(inputValue)}
 | 
				
			||||||
 | 
					            onChange={onChange}
 | 
				
			||||||
 | 
					            multiSelect={{
 | 
				
			||||||
 | 
					                clearAll,
 | 
				
			||||||
 | 
					                selectAll: () => {
 | 
				
			||||||
 | 
					                    addValues(legalValues.map(({ value }) => value));
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                values,
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					            {...baseProps}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SingleLegalValueSelectorProps = {
 | 
				
			||||||
 | 
					    value: string;
 | 
				
			||||||
 | 
					    addValue: (value: string) => void;
 | 
				
			||||||
 | 
					    clear: () => void;
 | 
				
			||||||
 | 
					    deletedLegalValues?: Set<string>;
 | 
				
			||||||
 | 
					    legalValues: ILegalValue[];
 | 
				
			||||||
 | 
					    invalidLegalValues?: Set<string>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SingleLegalValueSelector = ({
 | 
				
			||||||
 | 
					    value,
 | 
				
			||||||
 | 
					    addValue,
 | 
				
			||||||
 | 
					    clear,
 | 
				
			||||||
 | 
					    ...baseProps
 | 
				
			||||||
 | 
					}: SingleLegalValueSelectorProps) => {
 | 
				
			||||||
 | 
					    const onChange = (newValue: string) => {
 | 
				
			||||||
 | 
					        if (value === newValue) {
 | 
				
			||||||
 | 
					            clear();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            addValue(newValue);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <BaseLegalValueSelector
 | 
				
			||||||
 | 
					            onChange={onChange}
 | 
				
			||||||
 | 
					            isInputSelected={(inputValue) => inputValue === value}
 | 
				
			||||||
 | 
					            {...baseProps}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -52,6 +52,7 @@ type MultiValueConstraintState = {
 | 
				
			|||||||
type LegalValueData = {
 | 
					type LegalValueData = {
 | 
				
			||||||
    legalValues: ILegalValue[];
 | 
					    legalValues: ILegalValue[];
 | 
				
			||||||
    deletedLegalValues?: Set<string>;
 | 
					    deletedLegalValues?: Set<string>;
 | 
				
			||||||
 | 
					    invalidLegalValues?: Set<string>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type LegalValueConstraintState = {
 | 
					type LegalValueConstraintState = {
 | 
				
			||||||
@ -80,11 +81,14 @@ export const useEditableConstraint = (
 | 
				
			|||||||
        resolveContextDefinition(context, localConstraint.contextName),
 | 
					        resolveContextDefinition(context, localConstraint.contextName),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const validator = constraintValidator(localConstraint);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const deletedLegalValues = useMemo(() => {
 | 
					    const deletedLegalValues = useMemo(() => {
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
            contextDefinition.legalValues?.length &&
 | 
					            contextDefinition.legalValues?.length &&
 | 
				
			||||||
            constraint.values?.length
 | 
					            constraint.values?.length
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
 | 
					            // todo: extract and test
 | 
				
			||||||
            const currentLegalValues = new Set(
 | 
					            const currentLegalValues = new Set(
 | 
				
			||||||
                contextDefinition.legalValues.map(({ value }) => value),
 | 
					                contextDefinition.legalValues.map(({ value }) => value),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
@ -101,6 +105,24 @@ export const useEditableConstraint = (
 | 
				
			|||||||
        JSON.stringify(constraint.values),
 | 
					        JSON.stringify(constraint.values),
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const invalidLegalValues = useMemo(() => {
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            contextDefinition.legalValues?.length &&
 | 
				
			||||||
 | 
					            isSingleValueConstraint(localConstraint)
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            // todo: extract and test
 | 
				
			||||||
 | 
					            return new Set(
 | 
				
			||||||
 | 
					                contextDefinition.legalValues
 | 
				
			||||||
 | 
					                    .filter(({ value }) => !validator(value)[0])
 | 
				
			||||||
 | 
					                    .map(({ value }) => value),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return undefined;
 | 
				
			||||||
 | 
					    }, [
 | 
				
			||||||
 | 
					        JSON.stringify(contextDefinition.legalValues),
 | 
				
			||||||
 | 
					        JSON.stringify(localConstraint.operator),
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const updateConstraint = (action: ConstraintUpdateAction) => {
 | 
					    const updateConstraint = (action: ConstraintUpdateAction) => {
 | 
				
			||||||
        const nextState = constraintReducer(
 | 
					        const nextState = constraintReducer(
 | 
				
			||||||
            localConstraint,
 | 
					            localConstraint,
 | 
				
			||||||
@ -120,26 +142,37 @@ export const useEditableConstraint = (
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (contextDefinition.legalValues?.length) {
 | 
				
			||||||
        if (isSingleValueConstraint(localConstraint)) {
 | 
					        if (isSingleValueConstraint(localConstraint)) {
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                updateConstraint,
 | 
					                updateConstraint,
 | 
				
			||||||
                constraint: localConstraint,
 | 
					                constraint: localConstraint,
 | 
				
			||||||
            validator: constraintValidator(localConstraint),
 | 
					                validator,
 | 
				
			||||||
 | 
					                legalValues: contextDefinition.legalValues,
 | 
				
			||||||
 | 
					                invalidLegalValues,
 | 
				
			||||||
 | 
					                deletedLegalValues,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    if (contextDefinition.legalValues?.length) {
 | 
					 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            updateConstraint,
 | 
					            updateConstraint,
 | 
				
			||||||
            constraint: localConstraint,
 | 
					            constraint: localConstraint,
 | 
				
			||||||
            validator: constraintValidator(localConstraint),
 | 
					            validator,
 | 
				
			||||||
            legalValues: contextDefinition.legalValues,
 | 
					            legalValues: contextDefinition.legalValues,
 | 
				
			||||||
 | 
					            invalidLegalValues,
 | 
				
			||||||
            deletedLegalValues,
 | 
					            deletedLegalValues,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    if (isSingleValueConstraint(localConstraint)) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            updateConstraint,
 | 
				
			||||||
 | 
					            constraint: localConstraint,
 | 
				
			||||||
 | 
					            validator,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        updateConstraint,
 | 
					        updateConstraint,
 | 
				
			||||||
        constraint: localConstraint,
 | 
					        constraint: localConstraint,
 | 
				
			||||||
        validator: constraintValidator(localConstraint),
 | 
					        validator,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user