Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sbb-chip-group): initial implementation #3382

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
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
56f5602
feat(sbb-chip-group): initial implementation
TomMenga Dec 13, 2024
d2c1d2e
build(generate): update generate component boilerplate
TomMenga Dec 16, 2024
d91d39d
feat(sbb-chip): initial implementation
TomMenga Dec 18, 2024
5d5956e
feat(sbb-chip-group): initial implementation pt.2
TomMenga Jan 15, 2025
0955b0a
Merge branch 'refs/heads/main' into feat/sbb-chip-group
TomMenga Jan 16, 2025
e67dfa5
feat(sbb-chip-group): add keyboard navigation
TomMenga Jan 16, 2025
af3e4db
feat(sbb-form-field): allow nested slotted input
TomMenga Jan 20, 2025
52b3b90
style(sbb-chip-group): input styles
TomMenga Jan 20, 2025
b6cde45
style(sbb-chip-group): handle disable and readonly
TomMenga Jan 22, 2025
9a747ca
feat(sbb-chip): docs and tests
TomMenga Jan 22, 2025
b642f1b
feat(sbb-chip-group): docs and tests
TomMenga Jan 22, 2025
780ae38
feat(sbb-chip): adapt to the new design
TomMenga Jan 28, 2025
af16ece
feat(sbb-chip-group): adapt to the new design
TomMenga Jan 28, 2025
400bb59
feat(sbb-chip): docs and tests pt.2
TomMenga Jan 28, 2025
4384c37
feat(sbb-chip-group): docs and tests pt.2
TomMenga Jan 28, 2025
c553a85
feat(sbb-chip): adapt to the new design pt.2
TomMenga Jan 28, 2025
ab6db1a
feat(sbb-chip): implemented negative
TomMenga Jan 28, 2025
e400748
feat(sbb-chip-group): implemented negative
TomMenga Jan 28, 2025
01da495
feat(sbb-chip): implemented negative
TomMenga Jan 28, 2025
4dc2c29
feat(sbb-chip): implemented negative
TomMenga Jan 31, 2025
931f1b8
test(sbb-chip-group, sbb-chip): add spec tests
TomMenga Jan 31, 2025
bbf1e60
test(sbb-chip-group, sbb-chip): add spec tests
TomMenga Jan 31, 2025
0835926
test(sbb-chip-group, sbb-chip): add spec tests pt.2
TomMenga Feb 3, 2025
962a2f4
test(sbb-chip-group, sbb-chip): docs and snapshot update
TomMenga Feb 3, 2025
991e3ce
feat(sbb-chip): fix delete button aria-label
TomMenga Feb 3, 2025
b8150a5
feat(sbb-chip-group): autocomplete integration
TomMenga Feb 3, 2025
fbbcb55
chore: fix imports
TomMenga Feb 3, 2025
d4c7c69
Merge branch 'refs/heads/main' into feat/sbb-chip-group
TomMenga Feb 3, 2025
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
9 changes: 9 additions & 0 deletions src/elements/autocomplete/autocomplete-base-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import style from './autocomplete-base-element.scss?lit&inline';
*/
const ariaRoleOnHost = isSafari;

/**
* Custom event emitted on the input when an option is selected
*/
export const inputAutocompleteEvent = 'inputAutocomplete';

export
@hostAttributes({
popover: 'manual',
Expand Down Expand Up @@ -205,6 +210,9 @@ abstract class SbbAutocompleteBaseElement extends SbbNegativeMixin(
// Manually trigger the change events
this.triggerElement.dispatchEvent(new Event('change', { bubbles: true }));
this.triggerElement.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));

// Custom input event emitted when input value changes after an option is selected
this.triggerElement.dispatchEvent(new Event(inputAutocompleteEvent));
this.triggerElement.focus();
}

Expand Down Expand Up @@ -400,6 +408,7 @@ abstract class SbbAutocompleteBaseElement extends SbbNegativeMixin(
'keydown',
(event: KeyboardEvent) => this.openedPanelKeyboardInteraction(event),
{
capture: true,
signal: this._openPanelEventsController.signal,
},
);
Expand Down
5 changes: 5 additions & 0 deletions src/elements/autocomplete/autocomplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { describeIf, EventSpy, waitForLitRender } from '../core/testing.js';
import { SbbFormFieldElement } from '../form-field.js';
import { SbbOptionElement } from '../option.js';

import { inputAutocompleteEvent } from './autocomplete-base-element.js';
import { SbbAutocompleteElement } from './autocomplete.js';

describe(`sbb-autocomplete`, () => {
Expand Down Expand Up @@ -141,6 +142,7 @@ describe(`sbb-autocomplete`, () => {
const optionSelectedEventSpy = new EventSpy(SbbOptionElement.events.optionSelected);
const inputEventSpy = new EventSpy('input', input);
const changeEventSpy = new EventSpy('change', input);
const inputAutocompleteEventSpy = new EventSpy(inputAutocompleteEvent, input);
const optTwo = element.querySelector<SbbOptionElement>('#option-2')!;

input.focus();
Expand All @@ -159,6 +161,7 @@ describe(`sbb-autocomplete`, () => {

expect(inputEventSpy.count).to.be.equal(1);
expect(changeEventSpy.count).to.be.equal(1);
expect(inputAutocompleteEventSpy.count).to.be.equal(1);
expect(optionSelectedEventSpy.count).to.be.equal(1);
expect(optionSelectedEventSpy.firstEvent!.target).to.have.property('id', 'option-2');
expect(document.activeElement).to.be.equal(input);
Expand All @@ -170,6 +173,7 @@ describe(`sbb-autocomplete`, () => {
const optionSelectedEventSpy = new EventSpy(SbbOptionElement.events.optionSelected);
const inputEventSpy = new EventSpy('input', input);
const changeEventSpy = new EventSpy('change', input);
const inputAutocompleteEventSpy = new EventSpy(inputAutocompleteEvent, input);
const optOne = element.querySelector<SbbOptionElement>('#option-1');
const optTwo = element.querySelector<SbbOptionElement>('#option-2');
const keydownSpy = new EventSpy('keydown', input);
Expand Down Expand Up @@ -197,6 +201,7 @@ describe(`sbb-autocomplete`, () => {
expect(optTwo).to.have.attribute('selected');
expect(inputEventSpy.count).to.be.equal(1);
expect(changeEventSpy.count).to.be.equal(1);
expect(inputAutocompleteEventSpy.count).to.be.equal(1);
expect(optionSelectedEventSpy.count).to.be.equal(1);
expect(input).to.have.attribute('aria-expanded', 'false');
expect(input).not.to.have.attribute('aria-activedescendant');
Expand Down
2 changes: 2 additions & 0 deletions src/elements/chip-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './chip-group/chip.js';
export * from './chip-group/chip-group.js';
1 change: 1 addition & 0 deletions src/elements/chip-group/chip-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './chip-group/chip-group.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/* @web/test-runner snapshot v1 */
export const snapshots = {};

snapshots["sbb-chip-group renders DOM"] =
`<sbb-chip-group tabindex="0">
<sbb-chip value="Value 1">
</sbb-chip>
</sbb-chip-group>
`;
/* end snapshot sbb-chip-group renders DOM */

snapshots["sbb-chip-group renders Shadow DOM"] =
`<div
class="sbb-chip-group"
role="grid"
>
<slot>
</slot>
</div>
`;
/* end snapshot sbb-chip-group renders Shadow DOM */

snapshots["sbb-chip-group renders with form-field DOM"] =
`<sbb-form-field
data-input-empty=""
data-input-type="input"
data-slot-names="label unnamed"
error-space="none"
size="m"
width="default"
>
<label
for="sbb-form-field-input-0"
slot="label"
>
Field label
</label>
<sbb-chip-group
name="field-1"
tabindex="0"
>
<sbb-chip value="Value 1">
</sbb-chip>
<sbb-chip value="Value 2">
</sbb-chip>
</sbb-chip-group>
<input id="sbb-form-field-input-0">
</sbb-form-field>
`;
/* end snapshot sbb-chip-group renders with form-field DOM */

snapshots["sbb-chip-group renders with form-field Shadow DOM"] =
`<div class="sbb-form-field__space-wrapper">
<div
class="sbb-form-field__wrapper"
id="overlay-anchor"
>
<slot name="prefix">
</slot>
<div class="sbb-form-field__input-container">
<span
aria-hidden="true"
class="sbb-form-field__label-spacer"
>
</span>
<span class="sbb-form-field__label">
<span class="sbb-form-field__label-ellipsis">
<slot name="label">
</slot>
</span>
</span>
<div class="sbb-form-field__input">
<slot>
</slot>
</div>
</div>
<slot name="suffix">
</slot>
</div>
<div class="sbb-form-field__error">
<slot name="error">
</slot>
</div>
</div>
`;
/* end snapshot sbb-chip-group renders with form-field Shadow DOM */

snapshots["sbb-chip-group renders with form-field A11y tree Firefox"] =
`<p>
{
"role": "document",
"name": "",
"children": [
{
"role": "statictext",
"name": "​"
},
{
"role": "text leaf",
"name": "Field label"
},
{
"role": "text container",
"name": "",
"children": [
{
"role": "grid",
"name": "",
"children": [
{
"role": "gridcell",
"name": "Value 1"
},
{
"role": "button",
"name": "Remove Value 1"
},
{
"role": "gridcell",
"name": "Value 2"
},
{
"role": "button",
"name": "Remove Value 2"
}
]
}
]
},
{
"role": "textbox",
"name": "Field label"
}
]
}
</p>
`;
/* end snapshot sbb-chip-group renders with form-field A11y tree Firefox */

snapshots["sbb-chip-group renders with form-field A11y tree Chrome"] =
`<p>
{
"role": "WebArea",
"name": "",
"children": [
{
"role": "text",
"name": "​"
},
{
"role": "text",
"name": "Field label"
},
{
"role": "generic",
"name": "",
"children": [
{
"role": "gridcell",
"name": "Value 1"
},
{
"role": "button",
"name": "Remove Value 1"
},
{
"role": "gridcell",
"name": "Value 2"
},
{
"role": "button",
"name": "Remove Value 2"
}
]
},
{
"role": "textbox",
"name": "Field label"
}
]
}
</p>
`;
/* end snapshot sbb-chip-group renders with form-field A11y tree Chrome */

18 changes: 18 additions & 0 deletions src/elements/chip-group/chip-group/chip-group.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@use '../../core/styles/index' as sbb;

// Box-sizing rules contained in typography are not traversing Shadow DOM boundaries. We need to include box-sizing mixin in every component.
@include sbb.box-sizing;

:host {
--sbb-chip-group-input-min-width: #{sbb.px-to-rem-build(150)};
}

::slotted(input) {
flex: 1 1 var(--sbb-chip-group-input-min-width);
}

.sbb-chip-group {
display: flex;
flex-wrap: wrap;
gap: var(--sbb-spacing-fixed-1x);
}
58 changes: 58 additions & 0 deletions src/elements/chip-group/chip-group/chip-group.snapshot.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { expect } from '@open-wc/testing';
import { html } from 'lit/static-html.js';

import { fixture, testA11yTreeSnapshot } from '../../core/testing/private.js';

import type { SbbChipGroupElement } from './chip-group.js';
import './chip-group.js';
import '../chip.js';
import '../../form-field.js';

describe(`sbb-chip-group`, () => {
describe('renders', () => {
let element: SbbChipGroupElement;

beforeEach(async () => {
element = await fixture(
html`<sbb-chip-group>
<sbb-chip value="Value 1"></sbb-chip>
</sbb-chip-group>`,
);
});

it('DOM', async () => {
await expect(element).dom.to.be.equalSnapshot();
});

it('Shadow DOM', async () => {
await expect(element).shadowDom.to.be.equalSnapshot();
});
});

describe('renders with form-field', () => {
let element: SbbChipGroupElement;

beforeEach(async () => {
element = await fixture(html`
<sbb-form-field>
<label>Field label</label>
<sbb-chip-group name="field-1">
<sbb-chip value="Value 1"></sbb-chip>
<sbb-chip value="Value 2"></sbb-chip>
</sbb-chip-group>
<input />
</sbb-form-field>
`);
});

it('DOM', async () => {
await expect(element).dom.to.be.equalSnapshot();
});

it('Shadow DOM', async () => {
await expect(element).shadowDom.to.be.equalSnapshot();
});

testA11yTreeSnapshot();
});
});
Loading
Loading