) {
super.updated(changedProperties);
if (
changedProperties.has('value') ||
changedProperties.has('min') ||
changedProperties.has('max')
) {
this.updateProgress();
// Update USWDS callout if available
this.updateUSWDSCallout();
}
}
private updateUSWDSCallout() {
try {
if (typeof window !== 'undefined' && (window as any).USWDS?.range) {
const input = this.querySelector('.usa-range') as HTMLInputElement;
if (input) {
(window as any).USWDS.range.updateCallout(input);
(window as any).USWDS.range.updateVisualCallout(input);
}
}
} catch (error) {
// Silently fail if USWDS methods aren't available
}
}
private formatValue(value: number): string {
return `${value}${this.unit || ''}`;
}
private getAriaDescribedBy(): string | undefined {
const descriptions = [
this.hint ? `${this.inputId}-hint` : '',
this.error ? `${this.inputId}-error` : ''
].filter(Boolean).join(' ');
return descriptions || undefined;
}
override disconnectedCallback() {
super.disconnectedCallback();
// No USWDS cleanup needed since we manage the wrapper ourselves
}
private renderRequiredIndicator() {
if (!this.required) return '';
return html`*`;
}
private renderHint() {
if (!this.hint) return '';
return html`${this.hint}`;
}
private renderError() {
if (!this.error) return '';
return html`
Error: ${this.error}
`;
}
private renderValue() {
if (!this.showValue) return '';
return html`${this.formatValue(this.value)}`;
}
private renderMinMax() {
if (!this.showMinMax) return '';
return html`
${this.formatValue(this.min)}
${this.formatValue(this.max)}
`;
}
private calculatePercentage(value: number): number {
const range = this.max - this.min;
return range === 0 ? 0 : ((value - this.min) / range) * 100;
}
private dispatchRangeChange(newValue: number) {
this.dispatchEvent(new CustomEvent('range-change', {
detail: {
value: newValue,
formattedValue: this.formatValue(newValue).replace(this.unit, ''), // Remove unit for formatted value
percentage: this.calculatePercentage(newValue),
target: this
},
bubbles: true,
composed: true
}));
}
private handleInput(event: Event) {
const target = event.target as HTMLInputElement;
const newValue = parseInt(target.value, 10);
if (!isNaN(newValue) && newValue !== this.value) {
this.value = newValue;
this.dispatchRangeChange(newValue);
}
}
private handleChange(event: Event) {
const target = event.target as HTMLInputElement;
const newValue = parseInt(target.value, 10);
if (!isNaN(newValue)) {
this.value = newValue;
this.dispatchRangeChange(newValue);
}
}
private handleKeydown(event: KeyboardEvent) {
const target = event.target as HTMLInputElement;
let newValue = this.value;
const largeStep = Math.max(this.step * 10, (this.max - this.min) / 10);
switch (event.key) {
case 'ArrowLeft':
case 'ArrowDown':
newValue = Math.max(this.min, this.value - this.step);
break;
case 'ArrowRight':
case 'ArrowUp':
newValue = Math.min(this.max, this.value + this.step);
break;
case 'Home':
newValue = this.min;
break;
case 'End':
newValue = this.max;
break;
case 'PageUp':
newValue = Math.min(this.max, this.value + largeStep);
break;
case 'PageDown':
newValue = Math.max(this.min, this.value - largeStep);
break;
default:
return; // Don't prevent default for other keys
}
event.preventDefault();
target.value = newValue.toString();
this.value = newValue;
this.dispatchRangeChange(newValue);
}
override render() {
const hasError = Boolean(this.error);
const formGroupClasses = [
'usa-form-group',
hasError ? 'usa-form-group--error' : '',
this.required ? 'usa-form-group--required' : '',
]
.filter(Boolean)
.join(' ');
const labelClasses = [
'usa-label',
hasError ? 'usa-label--error' : ''
]
.filter(Boolean)
.join(' ');
const inputClasses = [
'usa-range',
'usa-range__input',
hasError ? 'usa-range--error' : ''
]
.filter(Boolean)
.join(' ');
return html`
`;
}
}