Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bede72f
chore: scaffold core package
rise-erpelding May 14, 2026
79b82b3
chore: scaffold swc package
rise-erpelding May 14, 2026
8c69ece
chore: add Chevron300Icon for xl accordion
rise-erpelding May 14, 2026
cb55ae1
chore: set up storybook story
rise-erpelding May 14, 2026
8c61687
chore: commit preview.ts updates
rise-erpelding May 14, 2026
629c233
feat: implement AccordionItem API and render template
rise-erpelding May 14, 2026
176c95a
feat: implement Accordion API and propagation
rise-erpelding May 15, 2026
5896710
feat: implement open and toggle logic
rise-erpelding May 15, 2026
7318895
feat: wire up dynamic heading levels
rise-erpelding May 15, 2026
1221bee
feat: implement disabled item functionality
rise-erpelding May 15, 2026
d4d945d
fix: handle space keyboard behavior (SWC-1487)
rise-erpelding May 15, 2026
b26f30f
refactor: address missing migration-setup items
rise-erpelding May 15, 2026
4ee7d20
refactor: rename methods, align file names
rise-erpelding May 15, 2026
885c98a
refactor: typing, heading rendering, sizing
rise-erpelding May 18, 2026
e2cb7d3
refactor: move toggle() into base, smooth a11y behavior
rise-erpelding May 18, 2026
365577a
refactor: use assignedItems() helper
rise-erpelding May 18, 2026
12d5760
test: add a11y tests
rise-erpelding May 18, 2026
e10e351
docs: add a11y docs
rise-erpelding May 18, 2026
b16028b
fix: freeze accordion open state when disabled
rise-erpelding May 18, 2026
b102a88
feat: 1st-gen deprecation warnings/tests
rise-erpelding May 18, 2026
b9b7d88
docs: jsdoc comments
rise-erpelding May 18, 2026
cf3c2a5
chore: changeset
rise-erpelding May 19, 2026
c940abb
fix: revert 1st-gen refactor work
rise-erpelding May 19, 2026
e63c363
chore(accordion): defer tests, deprecations, and changeset to part two
rise-erpelding May 19, 2026
e8dc485
fix: class selectors
rise-erpelding May 20, 2026
4cc8384
refactor: use ObserveSlotPresence instead of bespoke method
rise-erpelding May 21, 2026
b6141bd
refactor: remove stop propagation on actions container
rise-erpelding May 21, 2026
354f1d4
refactor: add open/close events
rise-erpelding May 21, 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
208 changes: 208 additions & 0 deletions 2nd-gen/packages/core/components/accordion/Accordion.base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { PropertyValues } from 'lit';
import { property } from 'lit/decorators.js';

import { SpectrumElement } from '@spectrum-web-components/core/element/index.js';
import { SizedMixin } from '@spectrum-web-components/core/mixins/index.js';

import {
ACCORDION_VALID_SIZES,
type AccordionDensity,
type AccordionHeadingLevel,
type AccordionSize,
SWC_ACCORDION_ITEM_TOGGLE_EVENT,
} from './Accordion.types.js';
import { AccordionItemBase } from './AccordionItem.base.js';

/**
* Base class for accordion components. Manages item propagation, heading
* level, density, and the exclusive-open constraint.
*
* @attribute {boolean} allowMultiple - Reflected as `allow-multiple`. When set,
* multiple items may be open at the same time.
* @attribute {number} level - Heading level (2–6) applied to every item header.
* Values outside that range are clamped.
* @attribute {AccordionSize} size - Size applied to all items.
* @attribute {AccordionDensity} density - Vertical spacing between items.
* @attribute {boolean} quiet - Renders the accordion in its quiet visual variant.
* @attribute {boolean} disabled - Disables all items in the accordion.
*
* @slot - One or more `swc-accordion-item` elements.
*/
export abstract class AccordionBase extends SizedMixin(SpectrumElement, {
validSizes: ACCORDION_VALID_SIZES,
defaultSize: 'm',
}) {
// ──────────────────
// PUBLIC API
// ──────────────────

/**
* When set, multiple items may be open at the same time. By default only
* one item can be open.
*/
@property({ type: Boolean, reflect: true, attribute: 'allow-multiple' })
public allowMultiple: boolean = false;

/**
* Heading level applied to every item header (2–6). Defaults to 3.
* Values outside that range are clamped.
*/
@property({ type: Number, reflect: true })
public level: AccordionHeadingLevel = 3;

/**
* Size applied to all items. Defaults to `m`.
*/
declare public size: AccordionSize;

/**
* Controls vertical spacing between items.
*
* @default regular
*/
@property({ type: String, reflect: true })
public density: AccordionDensity = 'regular';

/**
* Renders the accordion in its quiet (no-border) visual variant.
*/
@property({ type: Boolean, reflect: true })
public quiet: boolean = false;

/**
* Disables all items in the accordion. Individual items may also be
* disabled independently.
*/
@property({ type: Boolean, reflect: true })
public disabled: boolean = false;

// ──────────────────────
// IMPLEMENTATION
// ──────────────────────

private assignedItems(): AccordionItemBase[] {
const slot = this.renderRoot?.querySelector('slot');
if (!slot) {
return [];
}
return slot
.assignedElements({ flatten: true })
.filter((el): el is AccordionItemBase => el instanceof AccordionItemBase);
}

private closeSiblingsOnOpen = (event: Event): void => {
if (this.disabled) {
event.preventDefault();
return;
}
if (this.allowMultiple) {
return;
}
const toggling = event.target;
if (!(toggling instanceof AccordionItemBase)) {
return;
}
// Defer until after dispatch returns so that a canceled toggle (where the
// item reverts open back to false) does not incorrectly close siblings.
queueMicrotask(() => {
if (!toggling.open) {
return;
}
for (const item of this.assignedItems()) {
if (item !== toggling) {
item.open = false;
}
}
});
};

protected syncAccordionItems(): void {
for (const item of this.assignedItems()) {
item.setManagedHeading(this.level);
item.size = this.size;
item.setManagedParentDisabled(this.disabled);
}
}

private enforceExclusiveOpen(): void {
let foundOpen = false;
for (const item of this.assignedItems()) {
if (item.open) {
if (foundOpen) {
item.open = false;
} else {
foundOpen = true;
}
}
}
}

public override connectedCallback(): void {
super.connectedCallback();
this.addEventListener(
SWC_ACCORDION_ITEM_TOGGLE_EVENT,
this.closeSiblingsOnOpen
);
}

public override disconnectedCallback(): void {
super.disconnectedCallback();
this.removeEventListener(
SWC_ACCORDION_ITEM_TOGGLE_EVENT,
this.closeSiblingsOnOpen
);
}

protected override update(changedProperties: PropertyValues): void {
if (changedProperties.has('level')) {
const clamped = Math.min(
6,
Math.max(2, this.level)
) as AccordionHeadingLevel;
if (this.level !== clamped) {
this.level = clamped;
}
}
if (
changedProperties.has('level') ||
changedProperties.has('size') ||
changedProperties.has('disabled')
) {
this.syncAccordionItems();
}
// changedProperties.get() returns the previous value; this fires only when
// disabled transitions from true → false (re-enable).
if (
changedProperties.has('disabled') &&
changedProperties.get('disabled') === true &&
!this.allowMultiple
) {
this.enforceExclusiveOpen();
}
super.update(changedProperties);
}

protected override firstUpdated(changedProperties: PropertyValues): void {
super.firstUpdated(changedProperties);
if (window.__swc?.DEBUG && !this.hasAttribute('density')) {
window.__swc.warn(
this,
`<${this.localName}> should have an explicit "density" attribute set. Defaulting to "regular".`,
'https://opensource.adobe.com/spectrum-web-components/components/accordion/',
{ type: 'api', level: 'low' }
);
}
}
}
33 changes: 33 additions & 0 deletions 2nd-gen/packages/core/components/accordion/Accordion.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import type { ElementSize } from '@spectrum-web-components/core/mixins/index.js';

export const ACCORDION_VALID_SIZES = [
's',
'm',
'l',
'xl',
] as const satisfies readonly ElementSize[];
export type AccordionSize = (typeof ACCORDION_VALID_SIZES)[number];

export const ACCORDION_DENSITIES = ['compact', 'regular', 'spacious'] as const;
export type AccordionDensity = (typeof ACCORDION_DENSITIES)[number];

export const ACCORDION_HEADING_LEVELS = [2, 3, 4, 5, 6] as const;
export type AccordionHeadingLevel = (typeof ACCORDION_HEADING_LEVELS)[number];

export const SWC_ACCORDION_ITEM_TOGGLE_EVENT = 'swc-accordion-item-toggle';
export const SWC_ACCORDION_ITEM_OPEN_EVENT = 'swc-open';
export const SWC_ACCORDION_ITEM_CLOSE_EVENT = 'swc-close';
export const SWC_ACCORDION_ITEM_AFTER_OPEN_EVENT = 'swc-after-open';
export const SWC_ACCORDION_ITEM_AFTER_CLOSE_EVENT = 'swc-after-close';
Loading
Loading