Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/components/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@labkey/components",
"version": "7.23.2",
"version": "7.23.3",
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
"sideEffects": false,
"files": [
Expand Down
6 changes: 6 additions & 0 deletions packages/components/releaseNotes/components.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# @labkey/components
Components, models, actions, and utility functions for LabKey applications and pages

### version 7.23.3
*Released*: 19 March 2026
- GitHub Issue 942: Add error for duplicate values for MVTC fields
- GitHub Issue 961: Clicking the "Allow multiple selections" label doesn't toggle the checkbox
- GitHub Issue 932: No help text for disabled Multi-Value checkbox in designer

### version 7.23.2
*Released*: 18 March 2026
- Merge from release26.3-SNAPSHOT to develop
Expand Down
4 changes: 4 additions & 0 deletions packages/components/src/internal/OverlayTrigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ interface Props extends PropsWithChildren {
className?: string;
delay?: number;
id?: string;
noShow?: boolean;
overlay: ReactElement<OverlayComponent>; // See note in doc string below
style?: CSSProperties;
triggerType?: TriggerType;
Expand Down Expand Up @@ -155,6 +156,7 @@ export const OverlayTrigger: FC<Props> = ({
overlay,
triggerType = 'hover',
style,
noShow,
}) => {
const id_ = useMemo(() => id ?? generateId(), [id]);
const { onMouseEnter, onMouseLeave, onClick, portalEl, show, targetRef } = useOverlayTriggerState(
Expand All @@ -167,6 +169,8 @@ export const OverlayTrigger: FC<Props> = ({
const className_ = classNames('overlay-trigger', className);
const clonedContent = cloneElement(overlay, { targetRef });

if (noShow) return children;

return (
<div
className={className_}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,6 @@ describe('TextChoiceOptions', () => {
const multiCheckbox = document.querySelector('input.domain-text-choice-multi') as HTMLInputElement;
expect(multiCheckbox).toBeInTheDocument();
expect(multiCheckbox).toBeDisabled();
const labelSpan = screen.getByText('Allow multiple selections');
expect(labelSpan.getAttribute('title')).toBe('Multiple values are currently used by at least one data row.');
});

test('multi-choice checkbox not present', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { getTextChoiceInUseValues, TextChoiceInUseValues } from './actions';
import { createFormInputId } from './utils';
import { isFieldFullyLocked } from './propertiesUtil';
import { MULTI_CHOICE_TYPE, TEXT_CHOICE_TYPE } from './PropDescType';
import { Popover } from '../../Popover';
import { OverlayTrigger } from '../../OverlayTrigger';

const MIN_VALUES_FOR_SEARCH_COUNT = 2;
const HELP_TIP_BODY = <p>The set of values to be used as drop-down options to restrict data entry into this field.</p>;
Expand Down Expand Up @@ -100,6 +102,7 @@ export const TextChoiceOptionsImpl: FC<ImplProps> = memo(props => {
const [showAddValuesModal, setShowAddValuesModal] = useState<boolean>();
const [search, setSearch] = useState<string>('');
const fieldTypeId = createFormInputId(DOMAIN_FIELD_TYPE, domainIndex, index);
const mvPopOverId = useMemo(() => createFormInputId('mv-in-use-popover', domainIndex, index), [domainIndex, index]);
const isMultiChoiceField = field.dataType.name === MULTI_CHOICE_TYPE.name;

// keep a map from the updated values for the in-use field values to their original values
Expand Down Expand Up @@ -280,25 +283,26 @@ export const TextChoiceOptionsImpl: FC<ImplProps> = memo(props => {
title={`Add Values (max ${maxValueCount})`}
/>
{allowMultiChoice && (
<>
<input
checked={field.dataType.name === 'multiChoice'}
className="domain-text-choice-multi"
disabled={isFieldFullyLocked(field.lockType) || hasMultiValueInUse}
id={createFormInputId(DOMAIN_FIELD_TEXTCHOICE_MULTI, domainIndex, index)}
onChange={onAllowMultiChange}
type="checkbox"
/>
<span
title={
hasMultiValueInUse
? 'Multiple values are currently used by at least one data row.'
: ''
}
>
Allow multiple selections
</span>
</>
<OverlayTrigger
noShow={!hasMultiValueInUse}
overlay={
<Popover id={mvPopOverId} placement="top">
Multiple values are currently used by at least one data row.
</Popover>
}
>
<label className="label-weight-normal" id={mvPopOverId}>
<input
checked={field.dataType.name === 'multiChoice'}
className="domain-text-choice-multi"
disabled={isFieldFullyLocked(field.lockType) || hasMultiValueInUse}
id={createFormInputId(DOMAIN_FIELD_TEXTCHOICE_MULTI, domainIndex, index)}
onChange={onAllowMultiChange}
type="checkbox"
/>
<span>Allow multiple selections</span>
</label>
</OverlayTrigger>
)}
</div>
<div className="col-xs-6 col-lg-4">
Expand Down
17 changes: 16 additions & 1 deletion packages/components/src/internal/components/editable/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1530,13 +1530,22 @@ async function insertPastedData(
const unmatched: string[] = [];
const values = [];

const foundValues = new Set<string>();
// GitHub Issue 942: Add error for duplicate values
const dupValues = new Set<string>();
parsedValues.forEach(v => {
const vt = v.trim();
if (!vt) return;

const vd = col.validValues?.find(d => d === vt);
values.push({ display: vt, raw: vt });

if (foundValues.has(vt)) {
dupValues.add(vt);
} else {
foundValues.add(vt);
}

const vd = col.validValues?.find(d => d === vt);
if (vd) return;

unmatched.push(vt);
Expand All @@ -1548,6 +1557,12 @@ async function insertPastedData(
.map(u => '"' + u + '"')
.join(', ');
msg = { message: lookupValidationErrorMessage(valueStr, true) };
} else if (dupValues.size > 0) {
const valueStr = Array.from(dupValues)
.slice(0, 4)
.map(u => '"' + u + '"')
.join(', ');
msg = { message: `Duplicate values not allowed: ${valueStr}.` };
}
cv = List(values);
} else {
Expand Down
Loading