Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
290ebbb
Accessibility improvements: add labels for input elements
labkey-susanh Apr 23, 2026
042341a
Many more inputs without labels now have them
labkey-susanh Apr 24, 2026
ef56023
Merge from develop and update package version
labkey-susanh Apr 24, 2026
fba23cd
Update tests
labkey-susanh Apr 24, 2026
445f3f9
Merge remote-tracking branch 'origin/develop' into fb_inputLabels
labkey-susanh Apr 27, 2026
335258d
@labkey/components v7.33.1-inputLabels.0
labkey-susanh Apr 27, 2026
801f8d0
Remove Y
labkey-susanh Apr 27, 2026
c8967a3
@labkey/components v7.33.1-inputLabels.1
labkey-susanh Apr 27, 2026
64a844f
AmountUnitInput label to use different col-X values based on if "enab…
cnathe Apr 27, 2026
392bf35
jest updates to match
cnathe Apr 27, 2026
4a9437a
7.33.1-inputLabels.2
cnathe Apr 27, 2026
4b69abc
7.33.1-inputLabels.2
cnathe Apr 27, 2026
706b5eb
Merge branch 'develop' into fb_inputLabels
cnathe Apr 28, 2026
8b847cf
7.33.1-inputLabels.3
cnathe Apr 28, 2026
d9ddfa8
merge from develop and update package version
labkey-susanh Apr 29, 2026
71f7c54
Make ids and labels less generic for elements that can be repeated on…
labkey-susanh Apr 29, 2026
0359317
@labkey/components v7.33.3-inputLabels.4
labkey-susanh Apr 29, 2026
0738f3c
Use more specific id
labkey-susanh Apr 29, 2026
a26607d
id -> inputId
labkey-susanh Apr 29, 2026
d131e06
aria-describedby -> aria-labelledby
labkey-susanh Apr 29, 2026
7b60691
Add missing domainIndex from id
labkey-susanh Apr 29, 2026
106b0e7
Add missing domainIndex from id
labkey-susanh Apr 29, 2026
e60bd8b
Change stringToHtmlId to not use regex
labkey-susanh Apr 29, 2026
447abc8
Better to have no label when name is not defined
labkey-susanh Apr 29, 2026
2a8a40d
@labkey/components v7.33.3-inputLabels.5
labkey-susanh Apr 29, 2026
379fc64
More updates for better ids
labkey-susanh Apr 29, 2026
e7e1248
@labkey/components v7.33.3-inputLabels.6
labkey-susanh Apr 29, 2026
d06867c
Use distinct ids
labkey-susanh Apr 29, 2026
56c0945
Use stringToHtmlId
labkey-susanh Apr 29, 2026
1e434ce
Don't generate ids with each re-render
labkey-susanh Apr 29, 2026
e2907a9
add inputId and distinguish from name
labkey-susanh Apr 29, 2026
0d60501
@labkey/components v7.33.3-inputLabels.7
labkey-susanh Apr 30, 2026
23dc1d6
Release date and version
labkey-susanh May 1, 2026
a266206
@labkey/components v7.33.3
labkey-susanh May 1, 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.33.2",
"version": "7.33.3",
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
"sideEffects": false,
"files": [
Expand Down
4 changes: 4 additions & 0 deletions packages/components/releaseNotes/components.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# @labkey/components
Components, models, actions, and utility functions for LabKey applications and pages

### version 7.33.3
*Released*: 1 May 2026
- Accessibility improvements: add labels or aria-labels to input elements

### version 7.33.2
*Released*: 29 April 2026
- GitHub Issue #598: Wrap text for long file names in various app locations
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import {
quoteValueWithDelimiters,
setIsTestEnv,
splitMultiValueForImport,
stringToHtmlId,
uncapitalizeFirstChar,
valueIsEmpty,
withTransformedKeys,
Expand Down Expand Up @@ -1336,6 +1337,7 @@ export {
genCellKey,
GENERAL_ASSAY_PROVIDER_NAME,
generateId,
stringToHtmlId,
generateNameWithTimestamp,
getActionErrorMessage,
getActionValuesForFilterProps,
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/internal/DropdownSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const DropdownSection: FC<MenuSectionProps> = ({ items, showDivider = fal
{expanded && items.length > SHOW_FILTER_CUTOFF && (
<MenuItem>
<input
aria-label="Filter selection options"
onChange={onFilterChange}
onClick={onFilterClick}
placeholder="Filter..."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ export const ThreadEditor: FC<ThreadEditorProps> = props => {
{view === EditorView.edit && (
<div className={classNames('form-group', { 'has-error': hasError })}>
<textarea
aria-label="Comment"
autoFocus
className="thread-editor__input form-control"
name="body"
Expand Down Expand Up @@ -516,6 +517,7 @@ export const ThreadEditor: FC<ThreadEditorProps> = props => {

<label className="thread-editor__attachment-input btn btn-default">
<span className="fa fa-paperclip" />
<span className="sr-only">Attach files</span>
<input multiple onChange={onFileInputChange} type="file" />
</label>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const FieldLabelDisplay: FC<FieldLabelDisplayProps> = memo(props => {
if (editing) {
return (
<input
aria-label="Field label"
autoFocus
className="form-control"
defaultValue={title}
Expand Down Expand Up @@ -551,8 +552,13 @@ export const ColumnSelectionModal: FC<ColumnSelectionModalProps> = memo(props =>
</div>
{allowShowAll && (
<div className="field-modal__footer" key="toggleAll">
<input checked={showAllColumns} onChange={onToggleShowAll} type="checkbox" />
&nbsp;Show all system and user-defined fields
<input
aria-labelledby="show-all-label"
checked={showAllColumns}
onChange={onToggleShowAll}
type="checkbox"
/>
&nbsp;<span id="show-all-label">Show all system and user-defined fields</span>
</div>
)}
</div>
Expand Down
9 changes: 8 additions & 1 deletion packages/components/src/internal/components/DateInput.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import React, { FC, memo, useCallback, useMemo, useRef } from 'react';
import React, { FC, memo, useCallback, useMemo, useRef, useState } from 'react';
import DatePicker, { DatePickerProps } from 'react-datepicker';

import { getDateFNSDateFormat, parseDateFNSTimeFormat } from '../util/Date';

import { Container } from './base/models/Container';
import { generateId } from '../util/utils';

export interface DateInputProps {
container?: Container;
}

export const DateInput: FC<DateInputProps & DatePickerProps> = memo(props => {
const { container, dateFormat, onSelect, timeFormat, ...pickerProps } = props;
const [id] = useState(() => generateId('date-input-'))

const input = useRef<DatePicker>(undefined);
const formats = useMemo(() => {
const dateFormat_ = dateFormat ?? getDateFNSDateFormat(container);
Expand All @@ -36,6 +39,7 @@ export const DateInput: FC<DateInputProps & DatePickerProps> = memo(props => {
return (
<span className="input-group date-input">
<DatePicker
ariaLabelledBy={id}
autoComplete="off"
className="form-control"
wrapperClassName="form-control"
Expand All @@ -46,6 +50,9 @@ export const DateInput: FC<DateInputProps & DatePickerProps> = memo(props => {
ref={input}
onSelect={onSelect_}
/>
<span className="sr-only" id={id}>
{pickerProps.placeholderText}
</span>
<span className="input-group-addon" onClick={onIconClick}>
<i className="fa fa-calendar" />
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ export const EditInlineField: FC<Props> = memo(props => {
{state.editing && isTextArea && (
<span className="input-group">
<textarea
aria-label={label ?? name}
autoFocus
className="form-control"
cols={100}
Expand All @@ -283,6 +284,7 @@ export const EditInlineField: FC<Props> = memo(props => {
{state.editing && !column && isText && (
<span className="input-group input-sizer">
<input
aria-label={label ?? name}
autoFocus
className="form-control"
defaultValue={_value}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import classNames from 'classnames';

import { FormsyInput } from '../forms/input/FormsyReactComponents';
import { LabelHelpTip } from '../base/LabelHelpTip';
import { stringToHtmlId } from '../../util/utils';

interface Props {
active: string;
Expand Down Expand Up @@ -51,7 +52,12 @@ export const ToggleButtons: FC<Props> = memo(props => {
return (
<>
{inputFieldName && (
<FormsyInput name={inputFieldName} type="hidden" value={active === first ? 'true' : 'false'} />
<FormsyInput
id={stringToHtmlId(inputFieldName)}
name={inputFieldName}
type="hidden"
value={active === first ? 'true' : 'false'}
/>
)}
<div
className={classNames('toggle', 'btn-group', {
Expand Down Expand Up @@ -106,7 +112,12 @@ export const ToggleIcon: FC<Props> = memo(props => {
return (
<>
{inputFieldName && (
<FormsyInput name={inputFieldName} type="hidden" value={active === first ? 'true' : 'false'} />
<FormsyInput
id={stringToHtmlId(inputFieldName)}
name={inputFieldName}
type="hidden"
value={active === first ? 'true' : 'false'}
/>
)}
<div
className={classNames('toggle', 'toggle-group-icon', 'btn-group', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ describe('ChartBuilderModal', () => {
expect(document.querySelectorAll('input')).toHaveLength(21);
expect(document.querySelector('input[value=automatic]').hasAttribute('checked')).toBe(true);
expect(document.querySelector('input[value=manual]').hasAttribute('checked')).toBe(false);
expect(document.querySelector('input[name=aggregate-method]').getAttribute('value')).toBe('SUM');
expect(document.querySelector('input[name=aggregateMethod]').getAttribute('value')).toBe('SUM');
expect(document.querySelectorAll('input[name=error-bar-method]')).toHaveLength(3);
expect(document.querySelector('input[value=SD]').hasAttribute('checked')).toBe(false);
expect(document.querySelector('input[value=SEM]').hasAttribute('checked')).toBe(false);
Expand Down Expand Up @@ -388,7 +388,7 @@ describe('ChartBuilderModal', () => {
expect(document.querySelectorAll('input')).toHaveLength(21);
expect(document.querySelector('input[value=automatic]').hasAttribute('checked')).toBe(true);
expect(document.querySelector('input[value=manual]').hasAttribute('checked')).toBe(false);
expect(document.querySelector('input[name=aggregate-method]').getAttribute('value')).toBe('MEAN');
expect(document.querySelector('input[name=aggregateMethod]').getAttribute('value')).toBe('MEAN');
expect(document.querySelectorAll('input[name=error-bar-method]')).toHaveLength(3);
expect(document.querySelector('input[value=SD]').hasAttribute('checked')).toBe(false);
expect(document.querySelector('input[value=SEM]').hasAttribute('checked')).toBe(true);
Expand Down Expand Up @@ -424,7 +424,7 @@ describe('ChartBuilderModal', () => {
expect(document.querySelectorAll('input')).toHaveLength(19);
expect(document.querySelector('input[name=x]').getAttribute('value')).toBe('field1');
expect(document.querySelector('input[name=y]').getAttribute('value')).toBe('field2');
expect(document.querySelectorAll('input[name=aggregate-method]')).toHaveLength(0);
expect(document.querySelectorAll('input[name=aggregateMethod]')).toHaveLength(0);
expect(document.querySelector('input[name=trendlineType]').getAttribute('value')).toBe('option1');
expect(document.querySelectorAll('input[name=trendlineAsymptoteMin]')).toHaveLength(0);
expect(document.querySelectorAll('input[name=trendlineAsymptoteMax]')).toHaveLength(0);
Expand Down Expand Up @@ -462,7 +462,7 @@ describe('ChartBuilderModal', () => {
expect(document.querySelectorAll('input')).toHaveLength(19);
expect(document.querySelector('input[name=x]').getAttribute('value')).toBe('field1');
expect(document.querySelector('input[name=y]').getAttribute('value')).toBe('field2');
expect(document.querySelectorAll('input[name=aggregate-method]')).toHaveLength(0);
expect(document.querySelectorAll('input[name=aggregateMethod]')).toHaveLength(0);
expect(document.querySelectorAll('input[name=trendlineType]')).toHaveLength(1);
expect(document.querySelectorAll('.field-option-icon')).toHaveLength(2); // gear icon for x and y axis

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { QueryModel } from '../../../public/QueryModel/QueryModel';
import { ColorIcon } from '../base/ColorIcon';
import { LABKEY_VIS } from '../../constants';
import { RemoveEntityButton } from '../buttons/RemoveEntityButton';
import { stringToHtmlId } from '../../util/utils';

enum COLOR_OPTIONS {
BOX_FILL_COLOR = 'boxFillColor',
Expand Down Expand Up @@ -270,11 +271,12 @@ export const ChartColorInputs: FC<ChartColorInputsProps> = memo(({ chartConfig,
{showColorPaletteScale && (
<div className="form-group row">
<div className="col-xs-12">
<label>Color Palette</label>
<label htmlFor="color-palette">Color Palette</label>
<SelectInput
clearable={false}
containerClass="row"
inputClass="col-xs-12"
inputId="color-palette"
name={COLOR_OPTIONS.COLOR_PALETTE_SCALE}
onChange={onColorPaletteChange}
options={COLOR_PALETTE_OPTIONS}
Expand Down Expand Up @@ -414,15 +416,18 @@ const SeriesLineStyleInput: FC<SeriesLineStyleInputProps> = memo(({ chartConfig,
return null;
}

const suffix = stringToHtmlId(chartConfig.measures.series?.name) ?? 'unknown';
return (
<>
<div className="form-group row">
<div className="col-xs-12">
<label>Line Color and Style</label>
<label htmlFor={'line-color-and-style-' + suffix}>Line Color and Style</label>
<SelectInput
containerClass="row"
inputClass="col-xs-12"
inputId={'line-color-and-style-' + suffix}
menuPlacement="top"
name={'lineColorAndStyle-' + suffix}
onChange={onSeriesSelectChange}
optionRenderer={seriesValueRenderer}
options={distinctSeriesOptions}
Expand All @@ -447,12 +452,16 @@ const SeriesLineStyleInput: FC<SeriesLineStyleInputProps> = memo(({ chartConfig,
</div>
{!chartConfig.geomOptions?.hideDataPoints && (
<div className="chart-color-input">
<label className="label-weight-normal">Shape</label>
<label className="label-weight-normal" htmlFor={'shape-' + suffix}>
Shape
</label>
<SelectInput
clearable={false}
containerClass="inline-block"
inputClass=""
inputId={'shape-' + suffix}
menuPlacement="top"
name="shape"
onChange={onSeriesShapeChange}
optionRenderer={shapeOptionRenderer}
options={SHAPE_OPTIONS}
Expand All @@ -466,12 +475,14 @@ const SeriesLineStyleInput: FC<SeriesLineStyleInputProps> = memo(({ chartConfig,
</div>
)}
<div className="chart-color-input">
<label className="label-weight-normal">Line Type</label>
<label className="label-weight-normal" htmlFor={'line-type-' + suffix}>Line Type</label>
<SelectInput
clearable={false}
containerClass="inline-block"
inputClass=""
inputId={'line-type-' + suffix}
menuPlacement="top"
name="lineType"
onChange={onSeriesLineTypeChange}
optionRenderer={lineTypeOptionRenderer}
options={LINE_TYPE_OPTIONS}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,14 @@ export const ChartFieldAggregateOptions: FC<OwnProps> = memo(props => {
return (
<>
<div>
<label>
<label htmlFor="aggregate-method">
Aggregate Method <LabelOverlay placement="right">{BAR_CHART_AGGREGATE_METHOD_TIP}</LabelOverlay>
</label>
<SelectInput
clearable={false}
inputClass="col-xs-12"
name="aggregate-method"
inputId="aggregate-method"
name="aggregateMethod"
onChange={onAggregateChange}
options={aggregateOptions}
placeholder="Select aggregate method"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ChartConfig, ChartConfigSetter, ChartFieldInfo, ChartLabels, ChartTypeI
import { getBarChartAxisLabel, getSelectOptions, hasTrendline } from './utils';

import { ChartFieldAdditionalOptions } from './ChartFieldAdditionalOptions';
import { stringToHtmlId } from '../../util/utils';

const DEFAULT_SCALE_VALUES = { type: 'automatic', trans: 'linear' };

Expand Down Expand Up @@ -96,16 +97,18 @@ export const ChartFieldOption: FC<OwnProps> = memo(props => {
[selectedType, setChartConfig]
);

const id = stringToHtmlId(field.name);
return (
<div>
<label>
<label htmlFor={id}>
{field.label}
{field.required && ' *'}
</label>
<div className="form-group row">
<SelectInput
containerClass=""
inputClass={showAdditionalOptions ? 'col-xs-11' : 'col-xs-12'}
inputId={id}
labelKey="caption"
name={field.name}
onChange={onSelectChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export const ChartFieldRangeScaleOptions: FC<Props> = memo(props => {
{scale.type === 'manual' && (
<div className="chart-builder-scale-range-inputs">
<input
aria-label="Scale minimum"
className="chart-builder-field-footer-input"
name="scaleMin"
onBlur={onScaleRangeBlur}
Expand All @@ -112,6 +113,7 @@ export const ChartFieldRangeScaleOptions: FC<Props> = memo(props => {
/>
<span className="chart-builder-field-footer-input">-</span>
<input
aria-label="Scale maximum"
className="chart-builder-field-footer-input"
name="scaleMax"
onBlur={onScaleRangeBlur}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ export const ChartLabelInput: FC<Props> = memo(({ label, name, onChange, value }
return (
<div className="form-group row">
<div className="col-xs-12">
<label>{label}</label>
<label htmlFor={inputName}>{label}</label>
<input
className="form-control"
id={inputName}
name={inputName}
onBlur={onBlur}
onChange={onChange_}
Expand Down
Loading