/**
* @file ds-fieldset.js
* @summary A custom Web Component that wraps a native `<fieldset>` element.
* @description
* The `ds-fieldset` component provides a styled and functional fieldset element
* for grouping related form controls together. It creates a visual and semantic
* grouping that improves form organization and accessibility.
*
* - The content inside `<ds-fieldset>...</ds-fieldset>` is rendered via the default slot as the fieldset content.
* - Should contain a `<ds-legend>` or native `<legend>` as the first child for accessibility.
* - Supports ARIA attributes: `aria-label`, `aria-describedby` for accessibility.
* - Uses `part="fieldset"` for styling via Shadow DOM.
* - Warns in the console if no accessible name (legend or `aria-label`) is provided.
* - No custom events are fired.
*
* @element ds-fieldset
*
* @slot - Renders form controls and other content within the fieldset.
*
* @attr {string} aria-label - Accessible label for the fieldset element (overrides legend).
* @attr {string} aria-describedby - Reference to element(s) describing the fieldset.
*
* @note Uses `part="fieldset"` for styling via Shadow DOM.
* @note Warns if no accessible name (legend or `aria-label`) is provided.
* @note Should contain a `<ds-legend>` or `<legend>` as the first child for correct semantics and accessibility.
*
* @example
* <!-- Basic fieldset -->
* <ds-fieldset>
* <ds-legend>Personal Information</ds-legend>
* <ds-label for="first-name">First Name</ds-label>
* <ds-text-input id="first-name" name="firstName"></ds-text-input>
* <ds-label for="last-name">Last Name</ds-label>
* <ds-text-input id="last-name" name="lastName"></ds-text-input>
* </ds-fieldset>
*
* @example
* <!-- Fieldset with radio buttons -->
* <ds-fieldset>
* <ds-legend>Gender</ds-legend>
* <ds-radio name="gender" value="male" id="male">Male</ds-radio>
* <ds-radio name="gender" value="female" id="female">Female</ds-radio>
* <ds-radio name="gender" value="other" id="other">Other</ds-radio>
* </ds-fieldset>
*
* @example
* <!-- Fieldset with checkboxes -->
* <ds-fieldset>
* <ds-legend>Interests</ds-legend>
* <ds-checkbox name="interests" value="sports" id="sports">Sports</ds-checkbox>
* <ds-checkbox name="interests" value="music" id="music">Music</ds-checkbox>
* <ds-checkbox name="interests" value="reading" id="reading">Reading</ds-checkbox>
* </ds-fieldset>
*/
import BaseComponent from './base-component.js';
class DsFieldset extends BaseComponent {
constructor() {
// ARIA config for ds-fieldset
const ariaConfig = {
staticAriaAttributes: {},
dynamicAriaAttributes: [
'aria-label',
'aria-describedby'
],
requiredAriaAttributes: [],
referenceAttributes: ['aria-describedby'],
};
const template = document.createElement('template');
template.innerHTML = `
<style>
@import url('/src/styles/styles.css');
:host {
display: block;
}
.wrapper {
width: 100%;
}
</style>
<div class="wrapper">
<fieldset part="fieldset">
<slot></slot>
</fieldset>
</div>
`;
super({
template: template.innerHTML,
targetSelector: 'fieldset',
ariaConfig,
events: [],
observedAttributes: []
});
this.fieldset = this.shadowRoot.querySelector('fieldset');
}
static get observedAttributes() {
return ['aria-label', 'aria-describedby'];
}
attributeChangedCallback(name, oldValue, newValue) {
super.attributeChangedCallback(name, oldValue, newValue);
if (oldValue === newValue) return;
}
// ARIA property accessors
get ariaLabel() {
const value = this.fieldset.getAttribute('aria-label');
return value === null ? null : value;
}
set ariaLabel(val) {
if (val === null || val === undefined) {
this.fieldset.removeAttribute('aria-label');
} else {
this.fieldset.setAttribute('aria-label', val);
}
}
get ariaDescribedBy() {
const value = this.fieldset.getAttribute('aria-describedby');
return value === null ? null : value;
}
set ariaDescribedBy(val) {
if (val === null || val === undefined) {
this.fieldset.removeAttribute('aria-describedby');
} else {
this.fieldset.setAttribute('aria-describedby', val);
}
}
// Override validateARIA for fieldset-specific checks
validateARIA() {
const errors = super.validateARIA ? super.validateARIA() : [];
// Accessible name check: must have a legend or aria-label
const legend = this.fieldset.querySelector('legend,ds-legend');
const ariaLabel = this.fieldset.getAttribute('aria-label');
if (!legend && !ariaLabel) {
errors.push('Fieldset has no accessible name (legend or aria-label required)');
}
return errors;
}
}
// Register the custom element
if (!customElements.get('ds-fieldset')) {
customElements.define('ds-fieldset', DsFieldset);
}
// Export for use in other modules
export default DsFieldset;