Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e789a75
Export Collapsible
labkey-susanh Feb 19, 2026
5c9ad41
@labkey/components v7.20.1-deriveActions.0
labkey-susanh Feb 19, 2026
e4239a1
Add styling for disabled expansion icon
labkey-susanh Feb 19, 2026
5de91f2
@labkey/components v7.20.1-deriveActions.1
labkey-susanh Feb 20, 2026
3068fad
disabled hover color update
labkey-susanh Feb 20, 2026
6a2671c
merge from develop
labkey-susanh Feb 23, 2026
ef5f0e6
Back out change to export Collapsible. Not needed at the moment.
labkey-susanh Feb 24, 2026
bd5d75f
@labkey/components v7.20.2-deriveActions.2
labkey-susanh Feb 24, 2026
85f3f10
merge from develop
labkey-susanh Feb 25, 2026
ec806a2
merge from develop
labkey-susanh Mar 2, 2026
2b8eefe
package update after merge from develop
labkey-susanh Mar 2, 2026
88c000e
remove unused parameter doc
labkey-susanh Mar 2, 2026
d38e05a
lint
labkey-susanh Mar 2, 2026
45ff50a
Update `isAllSamplesSchema` to account for move of `JobInputSamples` …
labkey-susanh Mar 3, 2026
2cdf8de
@labkey/components v7.21.1-deriveActions.3
labkey-susanh Mar 3, 2026
2c240b3
@labkey/api v1.48.1-deriveActions.1
labkey-susanh Mar 4, 2026
da028d0
@labkey/components v7.21.1-deriveActions.4
labkey-susanh Mar 4, 2026
f0f15f5
Revert use of new @labkey/api version
labkey-susanh Mar 4, 2026
c436194
Merge remote-tracking branch 'origin/develop' into fb_deriveActions
labkey-susanh Mar 5, 2026
107823e
@labkey/components v7.22.1-deriveActions.5
labkey-susanh Mar 5, 2026
ea9cac7
Add placement prop for DisableableButton
labkey-susanh Mar 5, 2026
73f4612
@labkey/components v7.22.1-deriveActions.6
labkey-susanh Mar 6, 2026
4a5412d
Remove unused class
labkey-susanh Mar 6, 2026
374d8fb
@labkey/components v7.22.2-deriveActions.7
labkey-susanh Mar 10, 2026
fade065
Merge from develop and update to @labkey/components v7.23.1-deriveAct…
labkey-susanh Mar 10, 2026
a6355c0
Merge from develop and @labkey/components v7.23.2-deriveActions.7
labkey-susanh Mar 12, 2026
e0b2b26
Little bit of linting
labkey-susanh Mar 12, 2026
50e17ca
add `fitlerArrayToString` method in QueryModel utils
labkey-susanh Mar 12, 2026
5134b58
@labkey/components v7.23.2-deriveActions.8
labkey-susanh Mar 12, 2026
6663b73
add `pronoun` utility method for the it/they or it/them text choices
labkey-susanh Mar 12, 2026
0b8c927
@labkey/components v7.23.2-deriveActions.9
labkey-susanh Mar 13, 2026
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.1",
"version": "7.23.2-deriveActions.9",
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
"sideEffects": false,
"files": [
Expand Down
7 changes: 7 additions & 0 deletions packages/components/releaseNotes/components.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# @labkey/components
Components, models, actions, and utility functions for LabKey applications and pages

### version TBD
*Released*: TBD
- Update `isAllSamplesSchema` to account for move of `JobInputSamples` to `workflow` schema
- Add placement prop for `DisableableButton`
- add `fitlerArrayToString` method in QueryModel utils
- add `pronoun` utility method for the it/they or it/them text choices

### version 7.23.1
*Released*: 11 March 2026
- Merge from release26.3-SNAPSHOT to develop
Expand Down
4 changes: 4 additions & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
makeCommaSeparatedString,
parseCsvString,
parseScientificInt,
pronoun,
quoteValueWithDelimiters,
setIsTestEnv,
uncapitalizeFirstChar,
Expand Down Expand Up @@ -541,6 +542,7 @@ import {
createOrderedSnapshotSelectionKey,
createSnapshotSelectionKey,
createSnapshotSelectionKeyStr,
filterArrayToString,
runDetailsColumnsForQueryModel,
} from './public/QueryModel/utils';
import { CONFIRM_MESSAGE, useRouteLeave } from './internal/util/RouteLeave';
Expand Down Expand Up @@ -1299,6 +1301,7 @@ export {
FileInput,
FileTree,
FilterAction,
filterArrayToString,
FilterCriteriaRenderer,
FilterStatus,
FIND_BY_IDS_QUERY_PARAM,
Expand Down Expand Up @@ -1592,6 +1595,7 @@ export {
ProductMenuModel,
ProductNavigationMenu,
Progress,
pronoun,
pushParameters,
QUERY_UPDATE_AUDIT_QUERY,
QueryColumn,
Expand Down
4 changes: 2 additions & 2 deletions packages/components/src/internal/app/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,6 @@ export const FREEZER_MANAGER_APP_PROPERTIES: AppProperties = {
export const APPLICATION_PROPERTIES = {
[FREEZER_MANAGER_PRODUCT_ID]: FREEZER_MANAGER_APP_PROPERTIES,
[SAMPLE_MANAGER_PRODUCT_ID]: SAMPLE_MANAGER_APP_PROPERTIES,
[LIMS_PRODUCT_ID] : LIMS_APP_PROPERTIES,
[LIMS_PRODUCT_ID]: LIMS_APP_PROPERTIES,
[BIOLOGICS_PRODUCT_ID]: BIOLOGICS_APP_PROPERTIES
}
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { memo, FC, useMemo, PropsWithChildren } from 'react';
import React, { FC, memo, PropsWithChildren, useMemo } from 'react';

import { createPortal } from 'react-dom';

Expand All @@ -10,23 +10,24 @@ interface Props extends PropsWithChildren {
className?: string;
disabledMsg?: string;
onClick?: () => void;
placement?: 'bottom' | 'left' | 'right' | 'top';
title?: string;
}

export const DisableableButton: FC<Props> = memo(props => {
const { bsStyle = 'default', children, className = '', disabledMsg, onClick, title } = props;
const { bsStyle = 'default', children, className = '', disabledMsg, onClick, placement = 'bottom', title } = props;
const { onMouseEnter, onMouseLeave, portalEl, show, targetRef } = useOverlayTriggerState<HTMLButtonElement>(
'disabled-button-overlay',
disabledMsg !== undefined,
false
);
const popover = useMemo(
() => (
<Popover id="disabled-button-popover" title={title} placement="bottom" targetRef={targetRef}>
<Popover id="disabled-button-popover" placement={placement} targetRef={targetRef} title={title}>
{disabledMsg}
</Popover>
),
[disabledMsg, targetRef, title]
[disabledMsg, placement, targetRef, title]
);

// Note: we use onPointerEnter/Leave so events propagate when the button is disabled
Expand All @@ -37,8 +38,8 @@ export const DisableableButton: FC<Props> = memo(props => {
onClick={onClick}
onPointerEnter={onMouseEnter}
onPointerLeave={onMouseLeave}
type="button"
ref={targetRef}
type="button"
>
{children}
{show && createPortal(popover, portalEl)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { LoadingSpinner } from '../base/LoadingSpinner';
import { Alert } from '../base/Alert';
import { Container } from '../base/models/Container';
import { useNotificationsContext } from '../notifications/NotificationsContext';
import { capitalizeFirstChar, makeCommaSeparatedString } from '../../util/utils';
import { capitalizeFirstChar, makeCommaSeparatedString, pronoun } from '../../util/utils';
import { HelpLink, MOVE_SAMPLES_TOPIC } from '../../util/helpLinks';
import { isLoading, LoadingState } from '../../../public/LoadingState';
import { AppURL } from '../../url/AppURL';
Expand Down Expand Up @@ -241,12 +241,12 @@ export const getMoveConfirmationProperties = (
text = `${text} ${noun} will be moved.`;
} else {
const cannotMoveNoun = numCannotMove === 1 ? nounSingular : nounPlural;
const pronoun = numCannotMove === 1 ? 'it' : 'they';
const _pronoun = pronoun(numCannotMove, 'they');
const verb = numCannotMove === 1 ? 'has' : 'have';
const parts = [];
if (numNotPermitted > 0) parts.push('you lack the proper permissions');
if (numNotAllowed > 0) parts.push(`${pronoun} ${verb} a status or related data that prevents moving`);
if (numMissing > 0) parts.push(`${pronoun} may have been deleted`);
if (numNotAllowed > 0) parts.push(`${_pronoun} ${verb} a status or related data that prevents moving`);
if (numMissing > 0) parts.push(`${_pronoun} may have been deleted`);
const error = makeCommaSeparatedString(parts, ', or ', '.');

if (numCanMove === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,6 @@ export async function getFolderConfigurableEntityTypeOptions(
* @param targetQueryName the name of the listing schema query that represents the initial target for creation.
* @param allowParents are parents of this entity type allowed or not
* @param isItemSamples use the selectionKey from inventory.items table to query sample parents
* @param combineParentTypes
*/
export function getEntityTypeData(
model: EntityIdCreationModel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getCommonDataValues,
getUpdatedData,
makeCommaSeparatedString,
pronoun,
} from '../../util/utils';

import { ComponentsAPIWrapper } from '../../APIWrapper';
Expand Down Expand Up @@ -66,8 +67,7 @@ export const SelectionWarning: FC<SelectionWarningProps> = props => {
}

if (missingCount > 0) {
const pronoun = missingCount > 1 ? 'they' : 'it';
messages.push(`Cannot edit ${missingCount} of the selected ${nounPlural}, ${pronoun} may have been deleted.`);
messages.push(`Cannot edit ${missingCount} of the selected ${nounPlural}, ${pronoun(missingCount, 'they')} may have been deleted.`);
}

if (messages.length === 0) return null;
Expand Down Expand Up @@ -102,12 +102,11 @@ export function errorMessage(
if (missingCount + notPermittedCount !== selectedCount) return undefined;

const noun = selectedCount > 1 ? nounPlural : nounSingular;
const pronoun = selectedCount > 1 ? 'they' : 'it';
const parts = [];

if (notPermittedCount > 0) parts.push(`you do not have the required permissions`);

if (missingCount > 0) parts.push(`${pronoun} may have been deleted`);
if (missingCount > 0) parts.push(`${pronoun(selectedCount, 'they')} may have been deleted`);

return `Cannot edit selected ${noun}, ${makeCommaSeparatedString(parts, ', or ', '.')}`;
}
Expand Down
10 changes: 6 additions & 4 deletions packages/components/src/internal/components/samples/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,13 @@ export function isAllSamplesSchema(schemaQuery: SchemaQuery): boolean {
return true;

if (lcSchemaName === SCHEMAS.SAMPLE_MANAGEMENT.SCHEMA) {
return (
lcQueryName === SCHEMAS.SAMPLE_MANAGEMENT.SOURCE_SAMPLES.queryName.toLowerCase() ||
lcQueryName === SCHEMAS.WORKFLOW.JOB_INPUT_SAMPLES.queryName.toLowerCase()
);
return lcQueryName === SCHEMAS.SAMPLE_MANAGEMENT.SOURCE_SAMPLES.queryName.toLowerCase();
}
if (
lcSchemaName === SCHEMAS.WORKFLOW.SCHEMA &&
lcQueryName === SCHEMAS.WORKFLOW.JOB_INPUT_SAMPLES.queryName.toLowerCase()
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI in my open PR I have added an arg to SchemaQuery.isEqual to allow you to easily compare two SchemaQuery objects without comparing their view names, which I think would be useful in this method.

isEqual(sq: SchemaQuery, includeViewName = true): boolean {

return true;

return false;
}
Expand Down
29 changes: 20 additions & 9 deletions packages/components/src/internal/util/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
makeCommaSeparatedString,
parseCsvString,
parseScientificInt,
pronoun,
quoteValueWithDelimiters,
styleStringToObj,
toLowerSafe,
Expand All @@ -58,24 +59,22 @@ import {
withTransformedKeys,
} from './utils';

const emptyList = List<string>();

describe('toLowerSafe', () => {
test('strings', () => {
expect(toLowerSafe(List<string>(['TEST ', ' Test', 'TeSt', 'test']))).toEqual(
List<string>(['test ', ' test', 'test', 'test'])
expect(toLowerSafe(['TEST ', ' Test', 'TeSt', 'test'])).toEqual(
['test ', ' test', 'test', 'test']
);
});

test('numbers', () => {
expect(toLowerSafe(List<string>([1, 2, 3]))).toEqual(emptyList);
expect(toLowerSafe(List<string>([1.0]))).toEqual(emptyList);
expect(toLowerSafe(List<string>([1.0, 2]))).toEqual(emptyList);
expect(toLowerSafe([1, 2, 3])).toEqual([]);
expect(toLowerSafe([1.0])).toEqual([]);
expect(toLowerSafe([1.0, 2])).toEqual([]);
});

test('strings and numbers', () => {
expect(toLowerSafe(List<string>([1, 2, 'TEST ', ' Test', 3.0, 4.4, 'TeSt', 'test']))).toEqual(
List<string>(['test ', ' test', 'test', 'test'])
expect(toLowerSafe([1, 2, 'TEST ', ' Test', 3.0, 4.4, 'TeSt', 'test'])).toEqual(
['test ', ' test', 'test', 'test']
);
});
});
Expand All @@ -96,6 +95,18 @@ describe('camelCaseToTitleCase', () => {
});
});

describe('pronoun', () => {
test('singular', () => {
expect(pronoun(1)).toBe('it');
expect(pronoun(1, 'some')).toBe('it');
});
test('plural', () => {
expect(pronoun(2)).toBe('them');
expect(pronoun(2, 'some')).toBe('some');
expect(pronoun(undefined)).toBe('them');
});
});

describe('capitalizeFirstChar', () => {
test('capitalizeFirstChar', () => {
const testStrings = {
Expand Down
6 changes: 5 additions & 1 deletion packages/components/src/internal/util/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export function withTransformedKeys(obj: Record<string, any>, keyTransformFn: (v
}

/**
* Returns a copy of List<string> and ensures that in copy all values are lower case strings.
* Returns a copy of string[] and ensures that in copy all values are lower case strings.
* @param a
*/
export function toLowerSafe(a: string[]): string[] {
Expand All @@ -107,6 +107,10 @@ export function camelCaseToTitleCase(text: string): string {
return saferText.charAt(0).toUpperCase() + saferText.slice(1);
}

export function pronoun(count, plural = 'them'): string {
return count === 1 ? 'it' : plural;
}

export function not(predicate: (...args: any[]) => boolean): (...args: any[]) => boolean {
return function () {
return !predicate.apply(this, arguments);
Expand Down
7 changes: 7 additions & 0 deletions packages/components/src/public/QueryModel/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ export function filterArraysEqual(a: Filter.IFilter[], b: Filter.IFilter[]): boo
return aStr === bStr;
}

export function filterArrayToString(filterArray: Filter.IFilter[]): string {
if (!filterArray) {
return '';
}
return filterArray.map(filterToString).sort().join(';');
}

export function sortsEqual(a: QuerySort, b: QuerySort): boolean {
return a.toRequestString() === b.toRequestString();
}
Expand Down
Loading