v0.0.14
builders

Accordion builder

Builder class for creating accessible Accordion components. Handles keyboard navigation, ARIA attributes, and panel toggling.

Usage

<script>
    import { AccordionBuilder } from 'svxui';

    // Controlled value state
    let value: string[] = $state([]);
    const options = ['Accordion1', 'Accordion2', 'Accordion3'];

    // Initialize accordion
    const accordion = new AccordionBuilder({
        get value() {
            return value;
        },
        set value(newValue) {
            value = newValue;
        }
    });
</script>

<!-- Accordion container -->
<div class="accordion-root" {...accordion.rootAttrs}>
    <!-- Accordion items -->
    {#each options as opt, i (i)}
        {@const item = accordion.getItem(opt)}

        <div class="accordion" {...item.itemAttrs}>
            <!-- Heading -->
            <div class="heading" {...item.headingAttrs}>
                Title {value}

                <!-- Trigger -->
                <button {...item.triggerAttrs}>
                    {item.expanded ? 'close' : 'open'}
                </button>
            </div>

            <!-- Content -->
            {#if item.expanded}
                <div class="content" {...item.contentAttrs}>
                    Content {value}
                </div>
            {/if}
        </div>
    {/each}
</div>

<style>
    .accordion-root {
        display: flex;
        flex-direction: column;
        gap: 5px;

        .accordion {
            display: flex;
            flex-direction: column;
            border: 1px solid light-dark(black, white);

            .heading {
                padding: 1rem;
                display: flex;
                justify-content: space-between;
            }

            .content {
                padding: 1rem;
            }
        }
    }
</style>

Type Definition

AccordionBuilder

export declare class AccordionBuilder<Value, Multiple extends boolean> {
    /**
     * @param options - Accordion configuration options.
     *
     * **Lifecycle note:** `AccordionBuilder` internally uses `SelectionState` which holds a reactive
     * `$effect.root()`. Call `destroy()` when the builder is instantiated outside a Svelte component
     * to avoid memory leaks.
     */
    constructor(options: AccordionBuilderOptions<Value, Multiple>);

    /**
     * Accordion instance id
     */
    get id(): string;

    /**
     * Can open single or multiple accordion items at a time
     */
    get multiple(): boolean;

    /**
     * Accordion orientation
     */
    get orientation(): Orientation;

    /**
     * Accordion is globally disabled
     */
    get disabled(): boolean;

    /**
     * Check if an accordion item is expanded
     * @param value The accordion item value to check
     * @returns `true` if the item is currently expanded
     */
    isExpanded: (value: Value) => boolean;

    /**
     * Expand an accordion item
     * @param value The accordion item value to expand
     */
    expand: (value: Value) => void;

    /**
     * Collapse an accordion item
     * @param value The accordion item value to collapse
     */
    collapse: (value: Value) => void;

    /**
     * Toggle expand/collapse an accordion item
     * @param value The accordion item value to toggle
     */
    toggle: (value: Value) => void;

    /**
     * Destroy internal reactive effects. Call this when the builder is instantiated
     * outside a Svelte component to avoid memory leaks.
     */
    destroy: () => void;

    /**
     * Accordion root attributes
     */
    get rootAttrs(): AccordionRootAttributes;

    /**
     * Build an accordion item state
     * @param value The accordion item value
     * @param itemOptions Optional per-item configuration overrides
     * @returns The accordion item instance
     */
    getItem: (value: Value, itemOptions?: AccordionItemOptions) => AccordionItem;
}

AccordionBuilderOptions

import type { Orientation } from '$lib/shared.types.js';
import type { SelectionStateOptions } from '$lib/utilities/selection-state/index.js';

export type AccordionHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;

/**
 * Accordion builder options
 *
 * @template Value - Value of single or multiple selection.
 * @template Multiple - Boolean indicating if multiple selection is enabled.
 */
export type AccordionBuilderOptions<Value, Multiple extends boolean> = SelectionStateOptions<
    Value,
    Multiple
> & {
    /**
     * Accordion orientation
     */
    orientation?: Orientation;
    /**
     * Accordion must be disabled or not
     */
    disabled?: boolean;
};

/**
 * Accordion item customization
 */
export type AccordionItemOptions = {
    /**
     * Custom accordion item id
     */
    id?: string;
    /**
     * Disable or not accordion item
     */
    disabled?: boolean;
    /**
     * Custom accordion item heading level
     */
    headingLevel?: AccordionHeadingLevel;
};

/**
 * Accordion item state
 */
export type AccordionItem = {
    /**
     * Accordion item is expanded
     */
    readonly expanded: boolean;
    /**
     * Accordion item is disabled
     */
    readonly disabled: boolean;
    /**
     * Accordion item wrapper attributes
     */
    readonly itemAttrs: AccordionItemAttributes;
    /**
     * Accordion item heading attributes
     */
    readonly headingAttrs: AccordionHeadingAttributes;
    /**
     * Accordion item trigger attributes
     */
    readonly triggerAttrs: AccordionTriggerAttributes;
    /**
     * Accordion item content attributes
     */
    readonly contentAttrs: AccordionContentAttributes;
};

export type AccordionRootAttributes = {
    readonly id: string;
    readonly tabindex: number;
    readonly 'data-disabled': string | undefined;
    readonly 'data-orientation': Orientation;
};

export type AccordionItemAttributes = {
    readonly id: string;
    readonly 'data-state': string;
    readonly 'data-disabled'?: string;
    readonly 'data-orientation': Orientation;
};

export type AccordionHeadingAttributes = {
    readonly id: string;
    readonly 'data-state': string;
    readonly 'data-disabled'?: string;
    readonly 'data-orientation': Orientation;
    readonly 'data-heading-level'?: AccordionHeadingLevel;
    readonly 'aria-level'?: AccordionHeadingLevel;
    readonly role: string;
};

export type AccordionTriggerAttributes = {
    readonly id: string;
    readonly tabindex: number;
    readonly disabled: boolean;
    readonly 'data-state': string;
    readonly 'data-disabled': string | undefined;
    readonly 'data-orientation': Orientation;
    readonly 'aria-disabled': boolean | undefined;
    readonly 'aria-expanded': boolean;
    readonly 'aria-controls': string;
    readonly onclick: (e: MouseEvent) => void;
};

export type AccordionContentAttributes = {
    readonly id: string;
    readonly role: string;
    readonly 'data-state': string;
    readonly 'data-disabled': string | undefined;
    readonly 'data-orientation': Orientation;
    readonly 'aria-labelledby': string;
};