diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index ac63174a57..1a3a91235b 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.8.1", + "version": "7.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.8.1", + "version": "7.9.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index 8add6202e4..42bc49fb00 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.8.1", + "version": "7.9.0", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index ffd7fe4ca0..024263a5ce 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -1,8 +1,12 @@ # @labkey/components Components, models, actions, and utility functions for LabKey applications and pages -### version 7.8.1 +### version 7.9.0 *Released*: 6 January 2026 +- GitHub Issue 503: Field editor URL option to set target window (i.e. _blank) + +### version 7.8.1 +*Released*: 5 January 2026 - GitHub 562: Combine warning messages for file preview unknown/system fields with the warning for duplicate columns - FilePreviewGrid to combine warningMsg with previewData.warningMsg in a single Alert diff --git a/packages/components/src/internal/components/domainproperties/DomainRowExpandedOptions.tsx b/packages/components/src/internal/components/domainproperties/DomainRowExpandedOptions.tsx index 748e233498..ef27ca621b 100644 --- a/packages/components/src/internal/components/domainproperties/DomainRowExpandedOptions.tsx +++ b/packages/components/src/internal/components/domainproperties/DomainRowExpandedOptions.tsx @@ -265,6 +265,7 @@ export class DomainRowExpandedOptions extends React.Component { field, index, onChange, + onMultiChange, showingModal, appPropertiesOnly, domainIndex, @@ -311,6 +312,7 @@ export class DomainRowExpandedOptions extends React.Component { domainIndex={domainIndex} field={field} onChange={onChange} + onMultiChange={onMultiChange} appPropertiesOnly={appPropertiesOnly} domainFormDisplayOptions={domainFormDisplayOptions} /> diff --git a/packages/components/src/internal/components/domainproperties/NameAndLinkingOptions.test.tsx b/packages/components/src/internal/components/domainproperties/NameAndLinkingOptions.test.tsx index beaafddf61..e17502eac4 100644 --- a/packages/components/src/internal/components/domainproperties/NameAndLinkingOptions.test.tsx +++ b/packages/components/src/internal/components/domainproperties/NameAndLinkingOptions.test.tsx @@ -5,10 +5,12 @@ import { createFormInputId } from './utils'; import { CALCULATED_CONCEPT_URI, DOMAIN_FIELD_DESCRIPTION, + DOMAIN_FIELD_FULLY_LOCKED, DOMAIN_FIELD_IMPORTALIASES, DOMAIN_FIELD_LABEL, DOMAIN_FIELD_ONTOLOGY_PRINCIPAL_CONCEPT, DOMAIN_FIELD_URL, + DOMAIN_FIELD_URL_TARGET, STORAGE_UNIQUE_ID_CONCEPT_URI, STRING_RANGE_URI, } from './constants'; @@ -29,6 +31,7 @@ const field = DomainField.create({ label: _label, importAliases: _importAliases, URL: _URL, + isTargetBlank: true, propertyURI: 'test', }); @@ -50,6 +53,15 @@ const calculatedField = DomainField.create({ conceptURI: CALCULATED_CONCEPT_URI, }); +const lockedField = DomainField.create({ + name: 'lockedField', + rangeURI: STRING_RANGE_URI, + propertyId: 3, + description: 'locked field desc', + label: 'Locked Field', + lockType: DOMAIN_FIELD_FULLY_LOCKED, +}); + const DEFAULT_PROPS = { index: 1, domainIndex: 1, @@ -73,21 +85,31 @@ describe('NameAndLinkingOptions', () => { let formField = document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_DESCRIPTION, 1, 1)); expect(formField.length).toEqual(1); expect(formField[0].textContent).toEqual(_description); + expect(formField[0].hasAttribute('disabled')).toEqual(false); // Label formField = document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_LABEL, 1, 1)); expect(formField.length).toEqual(1); expect(formField[0].getAttribute('value')).toEqual(_label); + expect(formField[0].hasAttribute('disabled')).toEqual(false); // Aliases formField = document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_IMPORTALIASES, 1, 1)); expect(formField.length).toEqual(1); expect(formField[0].getAttribute('value')).toEqual(_importAliases); + expect(formField[0].hasAttribute('disabled')).toEqual(false); // URL formField = document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_URL, 1, 1)); expect(formField.length).toEqual(1); expect(formField[0].getAttribute('value')).toEqual(_URL); + expect(formField[0].hasAttribute('disabled')).toEqual(false); + + // URL Target + formField = document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_URL_TARGET, 1, 1)); + expect(formField.length).toEqual(1); + expect(formField[0].hasAttribute('checked')).toEqual(true); + expect(formField[0].hasAttribute('disabled')).toEqual(false); expect(container).toMatchSnapshot(); }); @@ -119,6 +141,13 @@ describe('NameAndLinkingOptions', () => { test('calculated field', () => { render(); expect(document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_IMPORTALIASES, 1, 1))).toHaveLength(0); + + expect(document.querySelector('#' + createFormInputId(DOMAIN_FIELD_URL, 1, 1)).getAttribute('value')).toEqual( + '' + ); + expect( + document.querySelector('#' + createFormInputId(DOMAIN_FIELD_URL_TARGET, 1, 1)).hasAttribute('checked') + ).toEqual(false); }); test('hideImportAliases', () => { @@ -132,4 +161,23 @@ describe('NameAndLinkingOptions', () => { ); expect(document.querySelectorAll('#' + createFormInputId(DOMAIN_FIELD_IMPORTALIASES, 1, 1))).toHaveLength(0); }); + + test('locked field', () => { + render(); + expect( + document.querySelector('#' + createFormInputId(DOMAIN_FIELD_LABEL, 1, 1)).hasAttribute('disabled') + ).toEqual(true); + expect( + document.querySelector('#' + createFormInputId(DOMAIN_FIELD_DESCRIPTION, 1, 1)).hasAttribute('disabled') + ).toEqual(true); + expect( + document.querySelector('#' + createFormInputId(DOMAIN_FIELD_IMPORTALIASES, 1, 1)).hasAttribute('disabled') + ).toEqual(true); + expect( + document.querySelector('#' + createFormInputId(DOMAIN_FIELD_URL, 1, 1)).hasAttribute('disabled') + ).toEqual(true); + expect( + document.querySelector('#' + createFormInputId(DOMAIN_FIELD_URL_TARGET, 1, 1)).hasAttribute('disabled') + ).toEqual(true); + }); }); diff --git a/packages/components/src/internal/components/domainproperties/NameAndLinkingOptions.tsx b/packages/components/src/internal/components/domainproperties/NameAndLinkingOptions.tsx index 292762ecfb..4f2e195824 100644 --- a/packages/components/src/internal/components/domainproperties/NameAndLinkingOptions.tsx +++ b/packages/components/src/internal/components/domainproperties/NameAndLinkingOptions.tsx @@ -1,4 +1,5 @@ import React, { PureComponent, ReactNode } from 'react'; +import { List } from 'immutable'; import { HelpLink, URL_ENCODING_TOPIC } from '../../util/helpLinks'; @@ -9,15 +10,16 @@ import { ONTOLOGY_MODULE_NAME } from '../ontology/actions'; import { hasModule } from '../../app/utils'; import { isFieldFullyLocked } from './propertiesUtil'; -import { createFormInputId, createFormInputName } from './utils'; +import { createFormInputId, createFormInputName, isEmptyString } from './utils'; import { DOMAIN_FIELD_DESCRIPTION, DOMAIN_FIELD_IMPORTALIASES, DOMAIN_FIELD_LABEL, DOMAIN_FIELD_ONTOLOGY_PRINCIPAL_CONCEPT, DOMAIN_FIELD_URL, + DOMAIN_FIELD_URL_TARGET, } from './constants'; -import { DomainField, IDomainFormDisplayOptions } from './models'; +import { DomainField, IDomainFormDisplayOptions, IFieldChange } from './models'; import { SectionHeading } from './SectionHeading'; import { DomainFieldLabel } from './DomainFieldLabel'; @@ -28,6 +30,7 @@ interface NameAndLinkingProps { field: DomainField; index: number; onChange: (string, any) => void; + onMultiChange: (changes: List) => void; } export class NameAndLinkingOptions extends PureComponent { @@ -36,7 +39,30 @@ export class NameAndLinkingOptions extends PureComponent { }; onChange = (id: string, value: any): void => { - this.props?.onChange(id, value); + this.props.onChange(id, value); + }; + + handleURLChange = (evt: any): void => { + const { index, domainIndex } = this.props; + const val = evt.target.value; + const isEmpty = isEmptyString(val); + + // make sure to uncheck the "open in new tab" option if URL is cleared out + if (isEmpty) { + let changes = List(); + changes = changes.push({ id: evt.target.id, value: null }); + changes = changes.push({ + id: createFormInputId(DOMAIN_FIELD_URL_TARGET, domainIndex, index), + value: false, + }); + this.props.onMultiChange(changes); + } else { + this.onChange(evt.target.id, isEmpty ? null : val); + } + }; + + handleURLTargetChange = (evt: any): void => { + this.onChange(evt.target.id, evt.target.checked); }; getImportAliasHelpText = (): ReactNode => { @@ -70,7 +96,7 @@ export class NameAndLinkingOptions extends PureComponent {
- +
@@ -78,24 +104,24 @@ export class NameAndLinkingOptions extends PureComponent {
Description