diff --git a/src/elements-experimental/timetable-row/__snapshots__/timetable-row.snapshot.spec.snap.js b/src/elements-experimental/timetable-row/__snapshots__/timetable-row.snapshot.spec.snap.js index b609d2f0b9..1e0c4d08c9 100644 --- a/src/elements-experimental/timetable-row/__snapshots__/timetable-row.snapshot.spec.snap.js +++ b/src/elements-experimental/timetable-row/__snapshots__/timetable-row.snapshot.spec.snap.js @@ -17,7 +17,6 @@ snapshots["sbb-timetable-row renders defaultTrip Shadow DOM"] = @@ -97,7 +96,6 @@ snapshots["sbb-timetable-row renders platform Shadow DOM"] = @@ -196,7 +194,6 @@ snapshots["sbb-timetable-row renders bus strip Shadow DOM"] = diff --git a/src/elements/accordion/__snapshots__/accordion.snapshot.spec.snap.js b/src/elements/accordion/__snapshots__/accordion.snapshot.spec.snap.js index 18aca7c5d1..122bf37f22 100644 --- a/src/elements/accordion/__snapshots__/accordion.snapshot.spec.snap.js +++ b/src/elements/accordion/__snapshots__/accordion.snapshot.spec.snap.js @@ -16,7 +16,6 @@ snapshots["sbb-accordion renders DOM"] = data-size="l" data-slot-names="unnamed" id="sbb-expansion-panel-header-1" - role="button" slot="header" tabindex="0" > @@ -46,7 +45,6 @@ snapshots["sbb-accordion renders DOM"] = data-size="l" data-slot-names="unnamed" id="sbb-expansion-panel-header-2" - role="button" slot="header" tabindex="0" > diff --git a/src/elements/action-group/__snapshots__/action-group.snapshot.spec.snap.js b/src/elements/action-group/__snapshots__/action-group.snapshot.spec.snap.js index 69bd6bba44..f557942ff2 100644 --- a/src/elements/action-group/__snapshots__/action-group.snapshot.spec.snap.js +++ b/src/elements/action-group/__snapshots__/action-group.snapshot.spec.snap.js @@ -14,7 +14,6 @@ snapshots["sbb-action-group renders renders DOM"] = data-button="" data-sbb-button="" data-slot-names="unnamed" - role="button" size="l" tabindex="0" > diff --git a/src/elements/alert/alert/__snapshots__/alert.snapshot.spec.snap.js b/src/elements/alert/alert/__snapshots__/alert.snapshot.spec.snap.js index a8d02f3b86..6f81cc6856 100644 --- a/src/elements/alert/alert/__snapshots__/alert.snapshot.spec.snap.js +++ b/src/elements/alert/alert/__snapshots__/alert.snapshot.spec.snap.js @@ -63,7 +63,6 @@ snapshots["sbb-alert should render default properties Shadow DOM"] = data-sbb-button="" icon-name="cross-small" negative="" - role="button" size="m" tabindex="0" > @@ -158,7 +157,6 @@ snapshots["sbb-alert should render customized properties Shadow DOM"] = data-sbb-button="" icon-name="cross-small" negative="" - role="button" size="m" tabindex="0" > diff --git a/src/elements/button/button-link/readme.md b/src/elements/button/button-link/readme.md index 8a5aac2318..49558eebbb 100644 --- a/src/elements/button/button-link/readme.md +++ b/src/elements/button/button-link/readme.md @@ -76,7 +76,7 @@ Use the accessibility properties in case of an icon-only button to describe the | --------------------- | ---------------------- | ------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- | | `accessibilityLabel` | `accessibility-label` | public | `string` | `''` | This will be forwarded as aria-label to the inner anchor element. | | `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | | `download` | `download` | public | `boolean` | `false` | Whether the browser will show the download dialog on click. | | `href` | `href` | public | `string` | `''` | The href value you want to link to. | | `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | diff --git a/src/elements/button/button/__snapshots__/button.snapshot.spec.snap.js b/src/elements/button/button/__snapshots__/button.snapshot.spec.snap.js index 7a6679a059..90958971da 100644 --- a/src/elements/button/button/__snapshots__/button.snapshot.spec.snap.js +++ b/src/elements/button/button/__snapshots__/button.snapshot.spec.snap.js @@ -3,7 +3,6 @@ export const snapshots = {}; snapshots["sbb-button renders a sbb-button without icon DOM"] = ` diff --git a/src/elements/button/button/button.stories.ts b/src/elements/button/button/button.stories.ts index 979d1ce631..e725aeb8ed 100644 --- a/src/elements/button/button/button.stories.ts +++ b/src/elements/button/button/button.stories.ts @@ -24,7 +24,6 @@ import { } from '../common/common-stories.js'; import readme from './readme.md?raw'; -import '../../loading-indicator.js'; import './button.js'; const defaultArgTypes: ArgTypes = { ...buttonDefaultArgTypes }; diff --git a/src/elements/button/button/button.visual.spec.ts b/src/elements/button/button/button.visual.spec.ts index 1879c3ac53..1eee675e1e 100644 --- a/src/elements/button/button/button.visual.spec.ts +++ b/src/elements/button/button/button.visual.spec.ts @@ -69,6 +69,27 @@ describe(`sbb-button`, () => { ); }); + describe(`disabledInteractive`, () => { + for (const negative of [false, true]) { + describe(`negative=${negative}`, () => { + for (const state of visualDiffStandardStates) { + it( + `${state.name}`, + state.with(async (setup) => { + await setup.withFixture( + html`Button`, + { + backgroundColor: negative ? 'var(--sbb-color-iron)' : undefined, + focusOutlineDark: negative, + }, + ); + }), + ); + } + }); + } + }); + describe('forcedColors=true', () => { describeEach(forcedColorCases, ({ disabled, negative }) => { beforeEach(async function () { diff --git a/src/elements/button/button/readme.md b/src/elements/button/button/readme.md index 527c48d04e..979f647086 100644 --- a/src/elements/button/button/readme.md +++ b/src/elements/button/button/readme.md @@ -79,17 +79,17 @@ guard against such cases in your component. ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| --------------------- | ---------------------- | ------- | --------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | -| `form` | `form` | public | `string` | `''` | The
element to associate the button with. | -| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | -| `size` | `size` | public | `SbbButtonSize` | `'l'` | Size variant, either l or m. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| --------------------- | ---------------------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | +| `size` | `size` | public | `SbbButtonSize` | `'l'` | Size variant, either l or m. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Slots diff --git a/src/elements/button/common/button-common-stories.ts b/src/elements/button/common/button-common-stories.ts index 960b9530f5..3861d2e19a 100644 --- a/src/elements/button/common/button-common-stories.ts +++ b/src/elements/button/common/button-common-stories.ts @@ -1,23 +1,49 @@ import type { InputType } from '@storybook/types'; import type { Args, ArgTypes, StoryObj } from '@storybook/web-components'; -import type { TemplateResult } from 'lit'; +import { nothing, type TemplateResult } from 'lit'; import { html, unsafeStatic } from 'lit/static-html.js'; +import { sbbSpread } from '../../../storybook/helpers/spread.js'; + import { commonDefaultArgs, commonDefaultArgTypes } from './common-stories.js'; +import '../../action-group.js'; +import '../../form-field.js'; + /* eslint-disable lit/binding-positions, @typescript-eslint/naming-convention */ -const RequestSubmitTemplate = ({ tag, text }: Args): TemplateResult => html` - - - <${unsafeStatic(tag)} type="submit" form="my-fake-form" name="input" value="input"> ${text} -
-`; +const FormTemplate = ({ + tag, + name, + value, + type: _type, + reset: _reset, + ...args +}: Args): TemplateResult => html` +
{ + e.preventDefault(); + const form = (e.target as HTMLFormElement)!; + form.querySelector('#form-data')!.innerHTML = JSON.stringify( + Object.fromEntries(new FormData(form, e.submitter)), + ); + }}> +

Input required; submit with empty value is impossible due to 'requestSubmit' API validation.

+ + + +
+ + <${unsafeStatic(tag)} ${sbbSpread(args)} type="reset"> + Reset + + <${unsafeStatic(tag)} ${sbbSpread(args)} value=${value ?? nothing} name=${name ?? nothing} type="submit"> + Submit + + +
+
+
`; + /* eslint-enable lit/binding-positions, @typescript-eslint/naming-convention */ const type: InputType = { @@ -104,6 +130,6 @@ export const buttonDefaultArgs: Args = { }; export const requestSubmit: StoryObj = { - render: RequestSubmitTemplate, - args: { text: 'Submit form' }, + render: FormTemplate, + args: { text: undefined, type: undefined, value: 'submit button' }, }; diff --git a/src/elements/button/common/button-common.scss b/src/elements/button/common/button-common.scss index 5adbaa8caa..f07885d73a 100644 --- a/src/elements/button/common/button-common.scss +++ b/src/elements/button/common/button-common.scss @@ -4,6 +4,8 @@ @include sbb.box-sizing; $icon-only: ':where([data-slot-names~=icon], [icon-name]):not([data-slot-names~=unnamed])'; +$disabled: '[disabled], :disabled, [disabled-interactive]'; +$active: ':active, [data-active]'; :host { display: inline-block; @@ -109,7 +111,7 @@ $icon-only: ':where([data-slot-names~=icon], [icon-name]):not([data-slot-names~= --sbb-button-padding-inline: 0; } -:host(:not([disabled], :active, [data-active]):hover) { +:host(:not(#{$disabled}, #{$active}):hover) { @include sbb.hover-mq($hover: true) { --sbb-button-translate-y-content-hover: #{sbb.px-to-rem-build(-1)}; --sbb-button-shadow-1-offset-y: calc( @@ -166,14 +168,14 @@ $icon-only: ':where([data-slot-names~=icon], [icon-name]):not([data-slot-names~= transition-property: inset, background-color, border-color, box-shadow; box-shadow: var(--sbb-button-box-shadow); - :host([disabled]) & { + :host(:is(#{$disabled})) & { background-color: var(--sbb-button-color-disabled-background); border-width: var(--sbb-button-border-disabled-width); border-color: var(--sbb-button-color-disabled-border); border-style: var(--sbb-button-border-disabled-style); } - :host(:not([disabled], :active, [data-active]):hover) & { + :host(:not(#{$disabled}, #{$active}):hover) & { @include sbb.hover-mq($hover: true) { inset: calc(var(--sbb-button-border-width) * -1); background-color: var(--sbb-button-color-hover-background); @@ -181,7 +183,7 @@ $icon-only: ':where([data-slot-names~=icon], [icon-name]):not([data-slot-names~= } } - :host(:not([disabled]):is(:active, [data-active])) & { + :host(:not(#{$disabled}):is(#{$active})) & { color: var(--sbb-button-color-active-text); background-color: var(--sbb-button-color-active-background); border-color: var(--sbb-button-color-active-border); @@ -193,13 +195,13 @@ $icon-only: ':where([data-slot-names~=icon], [icon-name]):not([data-slot-names~= justify-content: center; } - :host([disabled]) & { + :host(:is(#{$disabled})) & { color: var(--sbb-button-color-disabled-text); cursor: default; pointer-events: none; } - :host(:not([disabled], :active, [data-active]):hover) & { + :host(:not(#{$disabled}, #{$active}):hover) & { @include sbb.hover-mq($hover: true) { color: var(--sbb-button-color-hover-text); } diff --git a/src/elements/button/common/primary-button.scss b/src/elements/button/common/primary-button.scss index 0ddc2ad4fd..29aa1ccd48 100644 --- a/src/elements/button/common/primary-button.scss +++ b/src/elements/button/common/primary-button.scss @@ -24,6 +24,6 @@ --sbb-button-shadow-2-color: var(--sbb-color-metal-alpha-20); } -:host(:not([disabled], :active, [data-active])) { +:host(:not([disabled], :disabled, [disabled-interactive], :active, [data-active])) { --sbb-button-box-shadow: var(--sbb-button-box-shadow-definition); } diff --git a/src/elements/button/common/secondary-button.scss b/src/elements/button/common/secondary-button.scss index b692a51acd..2637d54c96 100644 --- a/src/elements/button/common/secondary-button.scss +++ b/src/elements/button/common/secondary-button.scss @@ -24,6 +24,6 @@ --sbb-button-color-hover-text: var(--sbb-color-milk); } -:host(:not([disabled], [negative], :active, [data-active])) { +:host(:not([disabled], :disabled, [disabled-interactive], [negative], :active, [data-active])) { --sbb-button-box-shadow: var(--sbb-button-box-shadow-definition); } diff --git a/src/elements/button/common/tertiary-button.scss b/src/elements/button/common/tertiary-button.scss index 262a4696f1..9d13e759f4 100644 --- a/src/elements/button/common/tertiary-button.scss +++ b/src/elements/button/common/tertiary-button.scss @@ -12,6 +12,6 @@ --sbb-button-shadow-2-color: var(--sbb-color-cement-alpha-20); } -:host(:not([disabled], :active, [data-active])) { +:host(:not([disabled], :disabled, [disabled-interactive], :active, [data-active])) { --sbb-button-box-shadow: var(--sbb-button-box-shadow-definition); } diff --git a/src/elements/button/mini-button-group/__snapshots__/mini-button-group.snapshot.spec.snap.js b/src/elements/button/mini-button-group/__snapshots__/mini-button-group.snapshot.spec.snap.js index ab95877fc0..5e3060e377 100644 --- a/src/elements/button/mini-button-group/__snapshots__/mini-button-group.snapshot.spec.snap.js +++ b/src/elements/button/mini-button-group/__snapshots__/mini-button-group.snapshot.spec.snap.js @@ -10,7 +10,6 @@ snapshots["sbb-mini-button-group renders DOM"] = data-action="" data-button="" icon-name="pen-small" - role="button" slot="li-0" tabindex="0" > @@ -26,7 +25,6 @@ snapshots["sbb-mini-button-group renders DOM"] = data-action="" data-button="" icon-name="pen-small" - role="button" slot="li-2" tabindex="0" > @@ -70,7 +68,6 @@ snapshots["sbb-mini-button-group renders negative DOM"] = data-button="" icon-name="pen-small" negative="" - role="button" slot="li-0" tabindex="0" > @@ -80,7 +77,6 @@ snapshots["sbb-mini-button-group renders negative DOM"] = data-button="" icon-name="pen-small" negative="" - role="button" slot="li-1" tabindex="0" > diff --git a/src/elements/button/mini-button/mini-button.stories.ts b/src/elements/button/mini-button/mini-button.stories.ts index 1a7ba3bf82..0cbf093dc9 100644 --- a/src/elements/button/mini-button/mini-button.stories.ts +++ b/src/elements/button/mini-button/mini-button.stories.ts @@ -11,9 +11,11 @@ import type { import { html, type TemplateResult } from 'lit'; import { sbbSpread } from '../../../storybook/helpers/spread.js'; +import { buttonDefaultArgs, buttonDefaultArgTypes } from '../common/button-common-stories.js'; + import '../../form-field.js'; +import '../../icon.js'; import './mini-button.js'; -import { buttonDefaultArgs, buttonDefaultArgTypes } from '../common/button-common-stories.js'; import readme from './readme.md?raw'; diff --git a/src/elements/button/mini-button/mini-button.visual.spec.ts b/src/elements/button/mini-button/mini-button.visual.spec.ts index bb1b309282..e4005cf3ae 100644 --- a/src/elements/button/mini-button/mini-button.visual.spec.ts +++ b/src/elements/button/mini-button/mini-button.visual.spec.ts @@ -54,6 +54,37 @@ describe(`sbb-mini-button`, () => { } }); + describe(`disabledInteractive`, () => { + for (const negative of [false, true]) { + describe(`negative=${negative}`, () => { + for (const state of visualDiffStandardStates) { + it( + `${state.name}`, + state.with(async (setup) => { + await setup.withFixture( + html` + + + + + + `, + { + backgroundColor: negative ? 'var(--sbb-color-black)' : undefined, + }, + ); + setup.withStateElement(setup.snapshotElement.querySelector('sbb-mini-button')!); + }), + ); + } + }); + } + }); + describe('slotted icon', () => { beforeEach(async function () { root = await visualRegressionFixture(html` diff --git a/src/elements/button/mini-button/readme.md b/src/elements/button/mini-button/readme.md index d9635b9463..cb2ef610b7 100644 --- a/src/elements/button/mini-button/readme.md +++ b/src/elements/button/mini-button/readme.md @@ -87,16 +87,16 @@ guard against such cases in your component. ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| --------------------- | ---------------------- | ------- | --------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | -| `form` | `form` | public | `string` | `''` | The
element to associate the button with. | -| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| --------------------- | ---------------------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Slots diff --git a/src/elements/button/secondary-button-link/readme.md b/src/elements/button/secondary-button-link/readme.md index 086b6ab7ae..54bc151fb5 100644 --- a/src/elements/button/secondary-button-link/readme.md +++ b/src/elements/button/secondary-button-link/readme.md @@ -81,7 +81,7 @@ Use the accessibility properties in case of an icon-only button to describe the | --------------------- | ---------------------- | ------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- | | `accessibilityLabel` | `accessibility-label` | public | `string` | `''` | This will be forwarded as aria-label to the inner anchor element. | | `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | | `download` | `download` | public | `boolean` | `false` | Whether the browser will show the download dialog on click. | | `href` | `href` | public | `string` | `''` | The href value you want to link to. | | `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | diff --git a/src/elements/button/secondary-button/__snapshots__/secondary-button.snapshot.spec.snap.js b/src/elements/button/secondary-button/__snapshots__/secondary-button.snapshot.spec.snap.js index fc5ba895fb..51c2ecf697 100644 --- a/src/elements/button/secondary-button/__snapshots__/secondary-button.snapshot.spec.snap.js +++ b/src/elements/button/secondary-button/__snapshots__/secondary-button.snapshot.spec.snap.js @@ -3,7 +3,6 @@ export const snapshots = {}; snapshots["sbb-secondary-button renders a sbb-secondary-button without icon DOM"] = ` diff --git a/src/elements/button/secondary-button/readme.md b/src/elements/button/secondary-button/readme.md index 57ca9fb6da..504ad5556a 100644 --- a/src/elements/button/secondary-button/readme.md +++ b/src/elements/button/secondary-button/readme.md @@ -84,17 +84,17 @@ guard against such cases in your component. ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| --------------------- | ---------------------- | ------- | --------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | -| `size` | `size` | public | `SbbButtonSize` | `'l'` | Size variant, either l or m. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| --------------------- | ---------------------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | +| `size` | `size` | public | `SbbButtonSize` | `'l'` | Size variant, either l or m. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Slots diff --git a/src/elements/button/secondary-button/secondary-button.stories.ts b/src/elements/button/secondary-button/secondary-button.stories.ts index 317f486a73..9a563b8fbe 100644 --- a/src/elements/button/secondary-button/secondary-button.stories.ts +++ b/src/elements/button/secondary-button/secondary-button.stories.ts @@ -24,7 +24,6 @@ import { } from '../common/common-stories.js'; import readme from './readme.md?raw'; -import '../../loading-indicator.js'; import './secondary-button.js'; const defaultArgTypes: ArgTypes = { ...buttonDefaultArgTypes }; diff --git a/src/elements/button/tertiary-button-link/readme.md b/src/elements/button/tertiary-button-link/readme.md index 06162ad796..1252b2f40b 100644 --- a/src/elements/button/tertiary-button-link/readme.md +++ b/src/elements/button/tertiary-button-link/readme.md @@ -81,7 +81,7 @@ Use the accessibility properties in case of an icon-only button to describe the | --------------------- | ---------------------- | ------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- | | `accessibilityLabel` | `accessibility-label` | public | `string` | `''` | This will be forwarded as aria-label to the inner anchor element. | | `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | | `download` | `download` | public | `boolean` | `false` | Whether the browser will show the download dialog on click. | | `href` | `href` | public | `string` | `''` | The href value you want to link to. | | `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | diff --git a/src/elements/button/tertiary-button/__snapshots__/tertiary-button.snapshot.spec.snap.js b/src/elements/button/tertiary-button/__snapshots__/tertiary-button.snapshot.spec.snap.js index bc471dc362..a6c4b71215 100644 --- a/src/elements/button/tertiary-button/__snapshots__/tertiary-button.snapshot.spec.snap.js +++ b/src/elements/button/tertiary-button/__snapshots__/tertiary-button.snapshot.spec.snap.js @@ -3,7 +3,6 @@ export const snapshots = {}; snapshots["sbb-tertiary-button renders a sbb-tertiary-button without icon DOM"] = ` diff --git a/src/elements/button/tertiary-button/readme.md b/src/elements/button/tertiary-button/readme.md index 736a65410d..25599bf35a 100644 --- a/src/elements/button/tertiary-button/readme.md +++ b/src/elements/button/tertiary-button/readme.md @@ -84,17 +84,17 @@ guard against such cases in your component. ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| --------------------- | ---------------------- | ------- | --------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | -| `size` | `size` | public | `SbbButtonSize` | `'l'` | Size variant, either l or m. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| --------------------- | ---------------------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | +| `size` | `size` | public | `SbbButtonSize` | `'l'` | Size variant, either l or m. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Slots diff --git a/src/elements/button/tertiary-button/tertiary-button.stories.ts b/src/elements/button/tertiary-button/tertiary-button.stories.ts index c80b955915..dcf371f9b2 100644 --- a/src/elements/button/tertiary-button/tertiary-button.stories.ts +++ b/src/elements/button/tertiary-button/tertiary-button.stories.ts @@ -24,7 +24,6 @@ import { } from '../common/common-stories.js'; import readme from './readme.md?raw'; -import '../../loading-indicator.js'; import './tertiary-button.js'; const defaultArgTypes: ArgTypes = { ...buttonDefaultArgTypes }; diff --git a/src/elements/button/transparent-button-link/readme.md b/src/elements/button/transparent-button-link/readme.md index 7a01c1e65f..7e5b799a00 100644 --- a/src/elements/button/transparent-button-link/readme.md +++ b/src/elements/button/transparent-button-link/readme.md @@ -81,7 +81,7 @@ Use the accessibility properties in case of an icon-only button to describe the | --------------------- | ---------------------- | ------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- | | `accessibilityLabel` | `accessibility-label` | public | `string` | `''` | This will be forwarded as aria-label to the inner anchor element. | | `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | | `download` | `download` | public | `boolean` | `false` | Whether the browser will show the download dialog on click. | | `href` | `href` | public | `string` | `''` | The href value you want to link to. | | `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | diff --git a/src/elements/button/transparent-button/__snapshots__/transparent-button.snapshot.spec.snap.js b/src/elements/button/transparent-button/__snapshots__/transparent-button.snapshot.spec.snap.js index 64d3e150ad..29eda9d9c5 100644 --- a/src/elements/button/transparent-button/__snapshots__/transparent-button.snapshot.spec.snap.js +++ b/src/elements/button/transparent-button/__snapshots__/transparent-button.snapshot.spec.snap.js @@ -3,7 +3,6 @@ export const snapshots = {}; snapshots["sbb-transparent-button renders a sbb-transparent-button without icon DOM"] = ` diff --git a/src/elements/button/transparent-button/readme.md b/src/elements/button/transparent-button/readme.md index 581d050939..e76af3e528 100644 --- a/src/elements/button/transparent-button/readme.md +++ b/src/elements/button/transparent-button/readme.md @@ -84,17 +84,17 @@ guard against such cases in your component. ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| --------------------- | ---------------------- | ------- | --------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | -| `size` | `size` | public | `SbbButtonSize` | `'l'` | Size variant, either l or m. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| --------------------- | ---------------------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | +| `size` | `size` | public | `SbbButtonSize` | `'l'` | Size variant, either l or m. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Slots diff --git a/src/elements/button/transparent-button/transparent-button.stories.ts b/src/elements/button/transparent-button/transparent-button.stories.ts index bd440c12c2..e111785122 100644 --- a/src/elements/button/transparent-button/transparent-button.stories.ts +++ b/src/elements/button/transparent-button/transparent-button.stories.ts @@ -24,7 +24,6 @@ import { } from '../common/common-stories.js'; import readme from './readme.md?raw'; -import '../../loading-indicator.js'; import './transparent-button.js'; const defaultArgTypes: ArgTypes = { ...buttonDefaultArgTypes }; diff --git a/src/elements/calendar/__snapshots__/calendar.snapshot.spec.snap.js b/src/elements/calendar/__snapshots__/calendar.snapshot.spec.snap.js index 9e48ae378f..25512b68cc 100644 --- a/src/elements/calendar/__snapshots__/calendar.snapshot.spec.snap.js +++ b/src/elements/calendar/__snapshots__/calendar.snapshot.spec.snap.js @@ -20,7 +20,6 @@ snapshots["sbb-calendar renders Shadow DOM"] = data-sbb-button="" icon-name="chevron-small-left-small" id="sbb-calendar__controls-previous" - role="button" size="m" tabindex="0" > @@ -52,7 +51,6 @@ snapshots["sbb-calendar renders Shadow DOM"] = data-sbb-button="" icon-name="chevron-small-right-small" id="sbb-calendar__controls-next" - role="button" size="m" tabindex="0" > diff --git a/src/elements/card/card-button/__snapshots__/card-button.snapshot.spec.snap.js b/src/elements/card/card-button/__snapshots__/card-button.snapshot.spec.snap.js index baa3015b7e..82c774af0f 100644 --- a/src/elements/card/card-button/__snapshots__/card-button.snapshot.spec.snap.js +++ b/src/elements/card/card-button/__snapshots__/card-button.snapshot.spec.snap.js @@ -13,7 +13,6 @@ snapshots["sbb-card-button renders DOM"] = active="" data-action="" data-button="" - role="button" slot="action" tabindex="0" > diff --git a/src/elements/card/card-button/readme.md b/src/elements/card/card-button/readme.md index 6cdd288b11..e47d30f448 100644 --- a/src/elements/card/card-button/readme.md +++ b/src/elements/card/card-button/readme.md @@ -22,13 +22,13 @@ as it is used for search engines and screen-reader users. ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| -------- | --------- | ------- | --------------- | ---------- | ------------------------------------------------ | -| `active` | `active` | public | `boolean` | `false` | Whether the card is active. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| -------- | --------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------- | +| `active` | `active` | public | `boolean` | `false` | Whether the card is active. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Slots diff --git a/src/elements/checkbox/checkbox-panel/checkbox-panel.ts b/src/elements/checkbox/checkbox-panel/checkbox-panel.ts index ac980e708b..43abf00d6d 100644 --- a/src/elements/checkbox/checkbox-panel/checkbox-panel.ts +++ b/src/elements/checkbox/checkbox-panel/checkbox-panel.ts @@ -41,7 +41,6 @@ export type SbbCheckboxPanelStateChange = Extract< * @event {CustomEvent} didChange - Deprecated. used for React. Will probably be removed once React 19 is available. * @event {Event} change - Event fired on change. * @event {InputEvent} input - Event fired on input. - * @overrideType value - string | null */ export @customElement('sbb-checkbox-panel') diff --git a/src/elements/checkbox/checkbox/checkbox.ts b/src/elements/checkbox/checkbox/checkbox.ts index ca07883742..01afd89802 100644 --- a/src/elements/checkbox/checkbox/checkbox.ts +++ b/src/elements/checkbox/checkbox/checkbox.ts @@ -22,7 +22,6 @@ import '../../visual-checkbox.js'; * @event {CustomEvent} didChange - Deprecated. used for React. Will probably be removed once React 19 is available. * @event {Event} change - Event fired on change. * @event {InputEvent} input - Event fired on input. - * @overrideType value - string | null */ export @customElement('sbb-checkbox') diff --git a/src/elements/core/a11y/focus.spec.ts b/src/elements/core/a11y/focus.spec.ts index 6152ae7c94..efcf7d945b 100644 --- a/src/elements/core/a11y/focus.spec.ts +++ b/src/elements/core/a11y/focus.spec.ts @@ -103,8 +103,8 @@ describe('focus', () => { - Disabled interactive button - Disabled interactive button + Disabled interactive button + Inert button `; } }, diff --git a/src/elements/core/base-elements/action-base-element.ts b/src/elements/core/base-elements/action-base-element.ts index 2136910fb3..36ba63fd84 100644 --- a/src/elements/core/base-elements/action-base-element.ts +++ b/src/elements/core/base-elements/action-base-element.ts @@ -64,6 +64,6 @@ abstract class SbbActionBaseElement extends LitElement { /** Default render method for button-like components. */ protected override render(): TemplateResult { - return html` ${this.renderTemplate()} `; + return html`${this.renderTemplate()}`; } } diff --git a/src/elements/core/base-elements/button-base-element.spec.ts b/src/elements/core/base-elements/button-base-element.spec.ts index 5f5954eeee..23897b7a66 100644 --- a/src/elements/core/base-elements/button-base-element.spec.ts +++ b/src/elements/core/base-elements/button-base-element.spec.ts @@ -1,22 +1,22 @@ -import { assert, expect } from '@open-wc/testing'; -import { sendKeys } from '@web/test-runner-commands'; +import { assert, aTimeout, expect } from '@open-wc/testing'; +import { a11ySnapshot, sendKeys } from '@web/test-runner-commands'; import { html, type TemplateResult } from 'lit'; -import { property } from 'lit/decorators.js'; -import { forceType } from '../decorators.js'; -import { fixture } from '../testing/private.js'; +import { SbbDisabledInteractiveMixin, SbbDisabledMixin } from '../mixins.js'; +import { tabKey } from '../testing/private/keys.js'; +import { fixture, typeInElement } from '../testing/private.js'; import { EventSpy, waitForLitRender } from '../testing.js'; import { SbbButtonBaseElement } from './button-base-element.js'; -class GenericButton extends SbbButtonBaseElement { - @forceType() - @property({ type: Boolean }) - public accessor disabled: boolean = false; - @forceType() - @property({ type: Boolean }) - public accessor disabledInteractive: boolean = false; +type FormDataEntry = { [p: string]: FormDataEntryValue }; +interface ButtonAccessibilitySnapshot { + role: string; + disabled: boolean; +} + +class GenericButton extends SbbDisabledInteractiveMixin(SbbDisabledMixin(SbbButtonBaseElement)) { protected override renderTemplate(): TemplateResult { return html`Button`; } @@ -34,14 +34,6 @@ describe(`SbbButtonBaseElement`, () => { it('renders', async () => { assert.instanceOf(element, GenericButton); }); - - it('check host attributes and content', () => { - expect(element.getAttribute('role')).to.be.equal('button'); - expect(element.getAttribute('tabindex')).to.be.equal('0'); - expect(element.shadowRoot!.firstElementChild!.classList.contains('generic-button')).to.be - .true; - expect(element.shadowRoot!.textContent!.trim()).to.be.equal('Button'); - }); }); describe('events', () => { @@ -60,10 +52,9 @@ describe(`SbbButtonBaseElement`, () => { expect(clickSpy.count).not.to.be.greaterThan(0); }); - it('dispatch click if disabled and disabledInteractive', async () => { + it('dispatch click if disabledInteractive', async () => { const clickSpy = new EventSpy('click'); - element.disabled = true; element.disabledInteractive = true; await waitForLitRender(element); @@ -110,6 +101,238 @@ describe(`SbbButtonBaseElement`, () => { expect(clickSpy.count).to.be.equal(1); }); }); + + describe('form association', () => { + let form: HTMLFormElement; + let submitButton: GenericButton | HTMLButtonElement; + let resetButton: GenericButton | HTMLButtonElement; + let input: HTMLInputElement; + let submitEventSpyPromise: Promise; + let formDataEventSpyPromise: Promise; + + for (const entry of [ + { + selector: 'generic-button', + button: html` + Submit + `, + resetButton: html`Reset`, + }, + { + selector: 'button', + button: html``, + resetButton: html``, + }, + ]) { + describe(entry.selector, () => { + describe('included in form', () => { + let fieldSet: HTMLFieldSetElement; + + beforeEach(async () => { + let submitResolve: (value: PromiseLike | FormDataEntry) => void; + let formDataResolve: (value: PromiseLike | FormDataEntry) => void; + + submitEventSpyPromise = new Promise((r) => (submitResolve = r)); + formDataEventSpyPromise = new Promise((r) => (formDataResolve = r)); + + form = await fixture(html` + { + e.preventDefault(); + submitResolve(Object.fromEntries(new FormData(form, e.submitter))); + }} + @formdata=${(e: FormDataEvent) => formDataResolve(Object.fromEntries(e.formData))} + > + +
${entry.button} ${entry.resetButton}
+ + `); + submitButton = form.querySelector(`[type="submit"]`)!; + resetButton = form.querySelector(`[type="reset"]`)!; + fieldSet = form.querySelector('fieldset')!; + input = form.querySelector('input')!; + }); + + it('should have role button', async () => { + const snapshot = (await a11ySnapshot({ + selector: entry.selector, + })) as unknown as ButtonAccessibilitySnapshot; + + expect(snapshot.role).to.be.equal('button'); + }); + + it('should be focusable', async () => { + const snapshot = (await a11ySnapshot({ + selector: entry.selector, + })) as unknown as ButtonAccessibilitySnapshot; + + expect(snapshot.disabled).to.be.undefined; + expect(submitButton).not.to.have.attribute('disabled'); + expect(submitButton).not.to.match(':disabled'); + + await sendKeys({ press: tabKey }); + await sendKeys({ press: tabKey }); + expect(document.activeElement!).to.be.equal(submitButton); + }); + + it('should not be focusable if disabled', async () => { + submitButton.disabled = true; + await waitForLitRender(submitButton); + + const snapshot = (await a11ySnapshot({ + selector: entry.selector, + })) as unknown as ButtonAccessibilitySnapshot; + + expect(snapshot.disabled).to.be.true; + expect(submitButton).to.have.attribute('disabled'); + expect(submitButton).to.match(':disabled'); + + await sendKeys({ press: tabKey }); + await sendKeys({ press: tabKey }); + expect(document.activeElement!).not.to.be.equal(submitButton); + }); + + it('should not be focusable if inside a disabled fieldset', async () => { + fieldSet.disabled = true; + await waitForLitRender(submitButton); + + const snapshot = (await a11ySnapshot({ + selector: entry.selector, + })) as unknown as ButtonAccessibilitySnapshot; + + expect(snapshot.disabled).to.be.true; + expect(submitButton).to.match(':disabled'); + + await sendKeys({ press: tabKey }); + expect(document.activeElement!).not.to.be.equal(submitButton); + }); + + it('should set default value', () => { + expect(submitButton.value).to.be.equal('submit'); + }); + + it('should not reset on form reset', async () => { + submitButton.value = 'changed-value'; + typeInElement(input, '123'); + expect(input.value).to.be.equal('test123'); + expect(input).to.have.attribute('value', 'test'); + + form.reset(); + + await waitForLitRender(form); + + expect(input.value).to.be.equal('test'); + + // Submit button is not considered as part of the form and cannot be reset therefore + expect(submitButton.value).to.be.equal('changed-value'); + }); + + it('should reset form but not button on button reset', async () => { + submitButton.value = 'changed-value'; + typeInElement(input, '123'); + + expect(input.value).to.be.equal('test123'); + expect(input).to.have.attribute('value', 'test'); + + resetButton.click(); + + // Needed to handle button click + await aTimeout(0); + + expect(input.value).to.be.equal('test'); + + // Submit button is not considered as part of the form and cannot be reset therefore + expect(submitButton.value).to.be.equal('changed-value'); + }); + + it('should contain button in formData on submit click', async () => { + submitButton.click(); + + const formData = await formDataEventSpyPromise; + expect(formData).to.be.deep.equal({ + 'submit-button': 'submit', + test: 'test', + }); + + const submitFormData = await submitEventSpyPromise; + + expect(submitFormData).to.be.deep.equal({ + 'submit-button': 'submit', + test: 'test', + }); + }); + }); + + describe('outside a form', () => { + beforeEach(async () => { + let submitResolve: (value: PromiseLike | FormDataEntry) => void; + let formDataResolve: (value: PromiseLike | FormDataEntry) => void; + + submitEventSpyPromise = new Promise((r) => (submitResolve = r)); + formDataEventSpyPromise = new Promise((r) => (formDataResolve = r)); + + const root = await fixture(html` +
+
{ + e.preventDefault(); + submitResolve(Object.fromEntries(new FormData(form, e.submitter))); + }} + @formdata=${(e: FormDataEvent) => formDataResolve(Object.fromEntries(e.formData))} + > + +
+ ${entry.button} ${entry.resetButton} +
+ `); + form = root.querySelector('form')!; + input = root.querySelector('input')!; + submitButton = root.querySelector(`[type="submit"]`)!; + submitButton.setAttribute('form', 'formid'); + resetButton = root.querySelector(`[type="reset"]`)!; + resetButton.setAttribute('form', 'formid'); + }); + + it('should submit form linked by id', async () => { + expect(submitButton.form).to.be.equal(form); + + submitButton.click(); + + const formData = await formDataEventSpyPromise; + expect(formData).to.be.deep.equal({ + 'submit-button': 'submit', + test: 'test', + }); + + const submitFormData = await submitEventSpyPromise; + + expect(submitFormData).to.be.deep.equal({ + 'submit-button': 'submit', + test: 'test', + }); + }); + + it('should reset form linked by id but not button on button reset', async () => { + submitButton.value = 'changed-value'; + typeInElement(input, '123'); + + expect(input.value).to.be.equal('test123'); + expect(input).to.have.attribute('value', 'test'); + + resetButton.click(); + // Needed to handle button click + await aTimeout(0); + + expect(input.value).to.be.equal('test'); + + // Submit button is not considered as part of the form and cannot be reset therefore + expect(submitButton.value).to.be.equal('changed-value'); + }); + }); + }); + } + }); }); declare global { diff --git a/src/elements/core/base-elements/button-base-element.ts b/src/elements/core/base-elements/button-base-element.ts index a4e4386e6a..05fce0bbfa 100644 --- a/src/elements/core/base-elements/button-base-element.ts +++ b/src/elements/core/base-elements/button-base-element.ts @@ -1,8 +1,13 @@ import { isServer } from 'lit'; import { property } from 'lit/decorators.js'; -import { forceType, hostAttributes } from '../decorators.js'; +import { hostAttributes } from '../decorators.js'; import { isEventPrevented } from '../eventing.js'; +import { + type FormRestoreReason, + type FormRestoreState, + SbbFormAssociatedMixin, +} from '../mixins.js'; import { SbbActionBaseElement } from './action-base-element.js'; @@ -12,49 +17,42 @@ export type SbbButtonType = 'button' | 'reset' | 'submit'; /** Button base class. */ export @hostAttributes({ - role: 'button', tabindex: '0', 'data-button': '', }) -abstract class SbbButtonBaseElement extends SbbActionBaseElement { - /** The type attribute to use for the button. */ - @property() public accessor type: SbbButtonType = 'button'; - +abstract class SbbButtonBaseElement extends SbbFormAssociatedMixin(SbbActionBaseElement) { /** - * The name of the button element. - * - * @description Developer note: In this case updating the attribute must be synchronous. - * Due to this it is implemented as a getter/setter and the attributeChangedCallback() handles the diff check. + * The type attribute to use for the button. + * @default 'button' */ @property() - public set name(name: string) { - this.setAttribute('name', `${name}`); + public override set type(name: SbbButtonType) { + this.setAttribute('type', `${name}`); } - public get name(): string { - return this.getAttribute('name') ?? ''; + public override get type(): SbbButtonType { + return (this.getAttribute('type') as SbbButtonType) ?? 'button'; } - /** - * The value of the button element. - * - * @description Developer note: In this case updating the attribute must be synchronous. - * Due to this it is implemented as a getter/setter and the attributeChangedCallback() handles the diff check. - */ + /** The
element to associate the button with. */ @property() - public set value(value: string) { - this.setAttribute('value', `${value}`); + public override set form(value: string) { + this._formId = value; } - public get value(): string { - return this.getAttribute('value') ?? ''; + public override get form(): HTMLFormElement | null { + // Use querySelector with form and id selector, as the form property must + // reference a valid element + return this._formId + ? ((this.ownerDocument?.querySelector?.(`form#${this._formId}`) as HTMLFormElement) ?? null) + : this.internals.form; } - - /** The element to associate the button with. */ - @forceType() - @property() - public accessor form: string = ''; + private _formId: string = ''; public constructor() { super(); + + /** @internal */ + this.internals.role = 'button'; + if (!isServer) { this.setupBaseEventHandlers(); @@ -74,23 +72,28 @@ abstract class SbbButtonBaseElement extends SbbActionBaseElement { ); } } - private _handleButtonClick = async (event: MouseEvent): Promise => { if (this.type === 'button' || (await isEventPrevented(event))) { return; } - // Use querySelector with form and id selector, as the form property must - // reference a valid element - const form = this.form - ? (this.ownerDocument.querySelector(`form#${this.form}`) as HTMLFormElement) - : this.closest('form'); + const form = this.form; if (!form) { return; } else if (this.type === 'submit') { // `form.requestSubmit(element);` seems not to work for CustomElements, so the `element` parameter has been removed; // TODO: Check if solved in any way, see https://github.com/WICG/webcomponents/issues/814#issuecomment-1218452137 - form.requestSubmit(); + // We use the workaround described in the github issue by cloning the submit button and pass this one as an argument. + + const submitButtonClone = document.createElement('button'); + submitButtonClone.inert = true; + submitButtonClone.hidden = true; + submitButtonClone.name = this.name; + submitButtonClone.value = this.value ?? ''; + + form.append(submitButtonClone); + form.requestSubmit(submitButtonClone); + submitButtonClone.remove(); } else if (this.type === 'reset') { form.reset(); } @@ -151,4 +154,26 @@ abstract class SbbButtonBaseElement extends SbbActionBaseElement { super.attributeChangedCallback(name, old, value); } } + + /** + * Intentionally empty, as buttons are not targeted by form reset + * @internal + */ + public override formResetCallback(): void {} + + /** + * Intentionally empty, as buttons are not targeted by form restore + * @internal + */ + public override formStateRestoreCallback( + _state: FormRestoreState | null, + _reason: FormRestoreReason, + ): void {} + + /** + * Intentionally empty, as button does not write its data in form. + * The data is only applied on submit button click as submitter of requestSubmit(); + * @internal + */ + protected updateFormValue(): void {} } diff --git a/src/elements/core/mixins/disabled-mixin.spec.ts b/src/elements/core/mixins/disabled-mixin.spec.ts index 56caae5c2c..5642b297d6 100644 --- a/src/elements/core/mixins/disabled-mixin.spec.ts +++ b/src/elements/core/mixins/disabled-mixin.spec.ts @@ -1,8 +1,10 @@ import { expect } from '@open-wc/testing'; -import { LitElement } from 'lit'; +import { a11ySnapshot } from '@web/test-runner-commands'; +import type { TemplateResult } from 'lit'; import { customElement } from 'lit/decorators.js'; import { html } from 'lit/static-html.js'; +import { SbbButtonBaseElement } from '../base-elements.js'; import { fixture } from '../testing/private.js'; import { waitForLitRender } from '../testing.js'; @@ -10,110 +12,129 @@ import { SbbDisabledMixin, SbbDisabledTabIndexActionMixin } from './disabled-mix /** Dummy docs */ @customElement('sbb-disabled-test') -class SbbDisabledTestElement extends SbbDisabledTabIndexActionMixin(SbbDisabledMixin(LitElement)) {} +class SbbDisabledTestElement extends SbbDisabledTabIndexActionMixin( + SbbDisabledMixin(SbbButtonBaseElement), +) { + public override renderTemplate(): TemplateResult { + return html`Button`; + } +} -describe(`sbb-button`, () => { +describe(`disabled mixin`, () => { let element: SbbDisabledTestElement; - function assertDisabled(element: SbbDisabledTestElement): void { + async function getA11ySnapshot(): Promise<{ disabled: boolean }> { + return (await a11ySnapshot({ selector: 'sbb-disabled-test' })) as unknown as { + disabled: boolean; + }; + } + + async function assertDisabled(element: SbbDisabledTestElement): Promise { expect(element.tabIndex).to.be.equal(-1); expect(element).not.to.have.attribute('tabindex'); - expect(element).to.have.attribute('aria-disabled', 'true'); + expect((await getA11ySnapshot()).disabled).to.be.true; } - function assertEnabled(element: SbbDisabledTestElement): void { + async function assertEnabled(element: SbbDisabledTestElement): Promise { expect(element.tabIndex).to.be.equal(0); expect(element).to.have.attribute('tabindex', '0'); - expect(element).not.to.have.attribute('aria-disabled'); + expect((await getA11ySnapshot()).disabled).to.be.undefined; } - function assertDisabledInteractive(element: SbbDisabledTestElement): void { + async function assertDisabledInteractive(element: SbbDisabledTestElement): Promise { expect(element.tabIndex).to.be.equal(0); expect(element).to.have.attribute('tabindex', '0'); - expect(element).to.have.attribute('aria-disabled', 'true'); + expect((await getA11ySnapshot()).disabled).to.be.true; } - beforeEach(async () => { - element = await fixture(html`I am a test button`); - }); - - it('should set attributes when enabled', async () => { - assertEnabled(element); + describe('disabled initially', () => { + it('should set attributes', async () => { + element = await fixture( + html`I am a test button`, + ); + await assertDisabled(element); + }); }); - it('should set attributes when disabled initially', async () => { - element = await fixture( - html`I am a test button`, - ); + describe('disabled', () => { + beforeEach(async () => { + element = await fixture(html`I am a test button`); + }); - assertDisabled(element); - }); + it('should set attributes when enabled', async () => { + await assertEnabled(element); + }); - it('should update attributes on attribute change', async () => { - element.setAttribute('disabled', 'true'); - await waitForLitRender(element); + it('should update attributes on attribute change', async () => { + element.setAttribute('disabled', 'true'); + await waitForLitRender(element); - assertDisabled(element); + await assertDisabled(element); - element.removeAttribute('disabled'); - await waitForLitRender(element); + element.removeAttribute('disabled'); + await waitForLitRender(element); - assertEnabled(element); - }); + await assertEnabled(element); + }); - it('should update attributes on property change', async () => { - element.disabled = true; - await waitForLitRender(element); + it('should update attributes on property change', async () => { + element.disabled = true; + await waitForLitRender(element); - assertDisabled(element); + await assertDisabled(element); - element.disabled = false; - await waitForLitRender(element); + element.disabled = false; + await waitForLitRender(element); - assertEnabled(element); - }); + await assertEnabled(element); + }); - it('should ignore disabledInteractive when enabled', async () => { - element.disabledInteractive = true; + it('should ignore disabledInteractive when disabled', async () => { + element.disabled = true; + element.disabledInteractive = true; + await waitForLitRender(element); - assertEnabled(element); + await assertDisabled(element); + }); }); - describe('disabled interactive', () => { + describe('disabled interactive initially', () => { it('should set attributes when disabled initially', async () => { element = await fixture( - html` - I am a test button - `, + html`I am a test button`, ); - assertDisabledInteractive(element); + await assertDisabledInteractive(element); + }); + }); + + describe('disabled interactive', () => { + beforeEach(async () => { + element = await fixture(html`I am a test button`); }); it('should update attributes on attribute change', async () => { - element.setAttribute('disabled', 'true'); element.setAttribute('disabled-interactive', 'true'); await waitForLitRender(element); - assertDisabledInteractive(element); + await assertDisabledInteractive(element); element.removeAttribute('disabled-interactive'); await waitForLitRender(element); - assertDisabled(element); + await assertEnabled(element); }); it('should update attributes on property change', async () => { - element.disabled = true; element.disabledInteractive = true; await waitForLitRender(element); - assertDisabledInteractive(element); + await assertDisabledInteractive(element); element.disabledInteractive = false; await waitForLitRender(element); - assertDisabled(element); + await assertEnabled(element); }); }); }); diff --git a/src/elements/core/mixins/disabled-mixin.ts b/src/elements/core/mixins/disabled-mixin.ts index 4e1bdd350e..e6803edb63 100644 --- a/src/elements/core/mixins/disabled-mixin.ts +++ b/src/elements/core/mixins/disabled-mixin.ts @@ -4,6 +4,7 @@ import { property } from 'lit/decorators.js'; import { forceType, getOverride } from '../decorators.js'; import type { AbstractConstructor } from './constructor.js'; +import type { SbbFormAssociatedMixinType } from './form-associated-mixin.js'; export declare class SbbDisabledMixinType { public accessor disabled: boolean; @@ -53,9 +54,9 @@ export const SbbDisabledInteractiveMixin = < extends superClass implements Partial { - /** Whether disabled buttons should be interactive. */ + /** Whether the button should be aria-disabled but stay interactive. */ @forceType() - @property({ attribute: 'disabled-interactive', type: Boolean }) + @property({ attribute: 'disabled-interactive', type: Boolean, reflect: true }) public accessor disabledInteractive: boolean = false; } @@ -64,7 +65,9 @@ export const SbbDisabledInteractiveMixin = < }; // eslint-disable-next-line @typescript-eslint/naming-convention -export const SbbDisabledTabIndexActionMixin = >( +export const SbbDisabledTabIndexActionMixin = < + T extends AbstractConstructor, +>( superClass: T, ): AbstractConstructor & T => { abstract class SbbDisabledTabIndexAction @@ -74,21 +77,19 @@ export const SbbDisabledTabIndexActionMixin = ): void { super.willUpdate(changedProperties); - if (!changedProperties.has('disabled') && !changedProperties.has('disabledInteractive')) { + if (changedProperties.has('disabledInteractive')) { + this.internals.ariaDisabled = this.disabledInteractive ? 'true' : null; + } + + if (!changedProperties.has('disabled')) { return; } - if (!this.disabled || this.disabledInteractive) { + if (!this.disabled) { this.setAttribute('tabindex', '0'); } else { this.removeAttribute('tabindex'); } - - if (this.disabled) { - this.setAttribute('aria-disabled', 'true'); - } else { - this.removeAttribute('aria-disabled'); - } } } return SbbDisabledTabIndexAction as AbstractConstructor< diff --git a/src/elements/core/styles/mixins/buttons.scss b/src/elements/core/styles/mixins/buttons.scss index 21140462fa..2b389d2753 100644 --- a/src/elements/core/styles/mixins/buttons.scss +++ b/src/elements/core/styles/mixins/buttons.scss @@ -3,6 +3,9 @@ @use '../core/functions'; @use './typo'; +$disabled: '[disabled], :disabled, [disabled-interactive], [data-disabled], [data-group-disabled]'; +$active: ':active, [data-active]'; + // ---------------------------------------------------------------------------------------------------- // Buttons Mixins // ---------------------------------------------------------------------------------------------------- @@ -29,7 +32,7 @@ @include icon-button-variables-negative; } - :host(:is([disabled], [data-disabled], [data-group-disabled])) { + :host(:is(#{$disabled})) { @include icon-button-disabled(#{$button-selector}); } @@ -42,11 +45,11 @@ @include icon-button-focus-visible(#{$button-selector}); } - :host(:not([disabled], [data-disabled], [data-group-disabled], :active, [data-active]):hover) { + :host(:not(#{$disabled}, #{$active}):hover) { @include icon-button-hover(#{$button-selector}); } - :host(:not([disabled], [data-disabled], [data-group-disabled]):is(:active, [data-active])) { + :host(:not(#{$disabled}):is(#{$active})) { @include icon-button-active(#{$button-selector}); } } diff --git a/src/elements/datepicker/common/datepicker-button.ts b/src/elements/datepicker/common/datepicker-button.ts index e77f23784d..100c487e06 100644 --- a/src/elements/datepicker/common/datepicker-button.ts +++ b/src/elements/datepicker/common/datepicker-button.ts @@ -205,6 +205,6 @@ export abstract class SbbDatepickerButton extends SbbNegativeMixin(Sbb } protected override renderTemplate(): TemplateResult { - return html` `; + return html``; } } diff --git a/src/elements/datepicker/datepicker-next-day/__snapshots__/datepicker-next-day.snapshot.spec.snap.js b/src/elements/datepicker/datepicker-next-day/__snapshots__/datepicker-next-day.snapshot.spec.snap.js index 2fde99342a..86908b6201 100644 --- a/src/elements/datepicker/datepicker-next-day/__snapshots__/datepicker-next-day.snapshot.spec.snap.js +++ b/src/elements/datepicker/datepicker-next-day/__snapshots__/datepicker-next-day.snapshot.spec.snap.js @@ -8,7 +8,6 @@ snapshots["sbb-datepicker-next-day renders DOM"] = data-action="" data-button="" data-disabled="" - role="button" slot="suffix" > @@ -34,7 +33,6 @@ snapshots["sbb-datepicker-next-day renders with connected datepicker DOM"] = data-action="" data-button="" date-picker="datepicker" - role="button" slot="suffix" tabindex="0" > diff --git a/src/elements/datepicker/datepicker-next-day/readme.md b/src/elements/datepicker/datepicker-next-day/readme.md index bf3781daea..2d1d3d616b 100644 --- a/src/elements/datepicker/datepicker-next-day/readme.md +++ b/src/elements/datepicker/datepicker-next-day/readme.md @@ -35,11 +35,11 @@ both standalone or within the `sbb-form-field`, they must have the same parent e ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| ------------ | ------------- | ------- | ------------------------------------------- | ---------- | ------------------------------------------------ | -| `datePicker` | `date-picker` | public | `string \| SbbDatepickerElement \| null` | `null` | Datepicker reference. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| ------------ | ------------- | ------- | ------------------------------------------- | ---------- | -------------------------------------------------------------- | +| `datePicker` | `date-picker` | public | `string \| SbbDatepickerElement \| null` | `null` | Datepicker reference. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | diff --git a/src/elements/datepicker/datepicker-previous-day/__snapshots__/datepicker-previous-day.snapshot.spec.snap.js b/src/elements/datepicker/datepicker-previous-day/__snapshots__/datepicker-previous-day.snapshot.spec.snap.js index 3831f8724d..a12477ca7c 100644 --- a/src/elements/datepicker/datepicker-previous-day/__snapshots__/datepicker-previous-day.snapshot.spec.snap.js +++ b/src/elements/datepicker/datepicker-previous-day/__snapshots__/datepicker-previous-day.snapshot.spec.snap.js @@ -8,7 +8,6 @@ snapshots["sbb-datepicker-previous-day renders DOM"] = data-action="" data-button="" data-disabled="" - role="button" slot="prefix" > @@ -34,7 +33,6 @@ snapshots["sbb-datepicker-previous-day renders with connected datepicker DOM"] = data-action="" data-button="" date-picker="datepicker" - role="button" slot="prefix" tabindex="0" > diff --git a/src/elements/datepicker/datepicker-previous-day/readme.md b/src/elements/datepicker/datepicker-previous-day/readme.md index b3dbf709cd..b62f832abb 100644 --- a/src/elements/datepicker/datepicker-previous-day/readme.md +++ b/src/elements/datepicker/datepicker-previous-day/readme.md @@ -35,11 +35,11 @@ both standalone or within the `sbb-form-field`, they must have the same parent e ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| ------------ | ------------- | ------- | ------------------------------------------- | ---------- | ------------------------------------------------ | -| `datePicker` | `date-picker` | public | `string \| SbbDatepickerElement \| null` | `null` | Datepicker reference. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| ------------ | ------------- | ------- | ------------------------------------------- | ---------- | -------------------------------------------------------------- | +| `datePicker` | `date-picker` | public | `string \| SbbDatepickerElement \| null` | `null` | Datepicker reference. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | diff --git a/src/elements/datepicker/datepicker-toggle/__snapshots__/datepicker-toggle.snapshot.spec.snap.js b/src/elements/datepicker/datepicker-toggle/__snapshots__/datepicker-toggle.snapshot.spec.snap.js index 23fcbc9e04..7508fb0501 100644 --- a/src/elements/datepicker/datepicker-toggle/__snapshots__/datepicker-toggle.snapshot.spec.snap.js +++ b/src/elements/datepicker/datepicker-toggle/__snapshots__/datepicker-toggle.snapshot.spec.snap.js @@ -10,7 +10,6 @@ snapshots["sbb-datepicker-toggle renders DOM"] = snapshots["sbb-datepicker-toggle renders Shadow DOM"] = ` @@ -73,7 +70,6 @@ snapshots["sbb-datepicker-toggle in form-field renders disabled DOM"] = snapshots["sbb-datepicker-toggle in form-field renders disabled Shadow DOM"] = ` diff --git a/src/elements/datepicker/datepicker/__snapshots__/datepicker.snapshot.spec.snap.js b/src/elements/datepicker/datepicker/__snapshots__/datepicker.snapshot.spec.snap.js index 137363da53..fbc148c1b6 100644 --- a/src/elements/datepicker/datepicker/__snapshots__/datepicker.snapshot.spec.snap.js +++ b/src/elements/datepicker/datepicker/__snapshots__/datepicker.snapshot.spec.snap.js @@ -22,7 +22,6 @@ snapshots["sbb-datepicker renders DOM"] = data-action="" data-button="" data-disabled="" - role="button" slot="prefix" > @@ -32,7 +31,6 @@ snapshots["sbb-datepicker renders DOM"] = data-action="" data-button="" data-disabled="" - role="button" slot="suffix" > diff --git a/src/elements/dialog/dialog-title/__snapshots__/dialog-title.snapshot.spec.snap.js b/src/elements/dialog/dialog-title/__snapshots__/dialog-title.snapshot.spec.snap.js index fdd84dd76c..5557f7cc6c 100644 --- a/src/elements/dialog/dialog-title/__snapshots__/dialog-title.snapshot.spec.snap.js +++ b/src/elements/dialog/dialog-title/__snapshots__/dialog-title.snapshot.spec.snap.js @@ -29,7 +29,6 @@ snapshots["sbb-dialog-title renders Shadow DOM"] = data-button="" data-sbb-button="" icon-name="cross-small" - role="button" sbb-dialog-close="" size="m" tabindex="0" diff --git a/src/elements/expansion-panel/expansion-panel-header/__snapshots__/expansion-panel-header.snapshot.spec.snap.js b/src/elements/expansion-panel/expansion-panel-header/__snapshots__/expansion-panel-header.snapshot.spec.snap.js index 3b8cc0ff36..31ebac62c8 100644 --- a/src/elements/expansion-panel/expansion-panel-header/__snapshots__/expansion-panel-header.snapshot.spec.snap.js +++ b/src/elements/expansion-panel/expansion-panel-header/__snapshots__/expansion-panel-header.snapshot.spec.snap.js @@ -6,7 +6,6 @@ snapshots["sbb-expansion-panel-header renders DOM"] = data-action="" data-button="" data-slot-names="unnamed" - role="button" slot="header" tabindex="0" > @@ -46,7 +45,6 @@ snapshots["sbb-expansion-panel-header renders with icon DOM"] = data-icon="" data-slot-names="unnamed" icon-name="pie-medium" - role="button" slot="header" tabindex="0" > @@ -92,7 +90,6 @@ snapshots["sbb-expansion-panel-header renders with slotted icon DOM"] = data-button="" data-icon="" data-slot-names="icon unnamed" - role="button" slot="header" tabindex="0" > diff --git a/src/elements/expansion-panel/expansion-panel-header/expansion-panel-header.scss b/src/elements/expansion-panel/expansion-panel-header/expansion-panel-header.scss index 4ded5d713e..e610cf5521 100644 --- a/src/elements/expansion-panel/expansion-panel-header/expansion-panel-header.scss +++ b/src/elements/expansion-panel/expansion-panel-header/expansion-panel-header.scss @@ -23,7 +23,7 @@ --sbb-expansion-panel-header-padding-inline: var(--sbb-spacing-fixed-5x); } -:host([disabled]) { +:host(:is(:disabled, [disabled-interactive])) { --sbb-expansion-panel-header-cursor: default; --sbb-expansion-panel-header-text-color: var(--sbb-color-granite); diff --git a/src/elements/expansion-panel/expansion-panel-header/readme.md b/src/elements/expansion-panel/expansion-panel-header/readme.md index 6f66c79c3e..cdb6b62fb0 100644 --- a/src/elements/expansion-panel/expansion-panel-header/readme.md +++ b/src/elements/expansion-panel/expansion-panel-header/readme.md @@ -35,15 +35,15 @@ When the element is clicked, the `toggleExpanded` event is emitted. ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| --------------------- | ---------------------- | ------- | --------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| --------------------- | ---------------------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Events diff --git a/src/elements/expansion-panel/expansion-panel/__snapshots__/expansion-panel.snapshot.spec.snap.js b/src/elements/expansion-panel/expansion-panel/__snapshots__/expansion-panel.snapshot.spec.snap.js index 13346581eb..217977e0a0 100644 --- a/src/elements/expansion-panel/expansion-panel/__snapshots__/expansion-panel.snapshot.spec.snap.js +++ b/src/elements/expansion-panel/expansion-panel/__snapshots__/expansion-panel.snapshot.spec.snap.js @@ -11,7 +11,6 @@ snapshots["sbb-expansion-panel renders DOM"] = data-size="l" data-slot-names="unnamed" id="sbb-expansion-panel-header-1" - role="button" slot="header" tabindex="0" > @@ -57,7 +56,6 @@ snapshots["sbb-expansion-panel renders size s DOM"] = data-size="s" data-slot-names="unnamed" id="sbb-expansion-panel-header-3" - role="button" slot="header" tabindex="0" > @@ -106,7 +104,6 @@ snapshots["sbb-expansion-panel renders with level set DOM"] = data-size="l" data-slot-names="unnamed" id="sbb-expansion-panel-header-5" - role="button" slot="header" tabindex="0" > diff --git a/src/elements/expansion-panel/expansion-panel/expansion-panel.spec.ts b/src/elements/expansion-panel/expansion-panel/expansion-panel.spec.ts index 1e5025d637..2ce8516076 100644 --- a/src/elements/expansion-panel/expansion-panel/expansion-panel.spec.ts +++ b/src/elements/expansion-panel/expansion-panel/expansion-panel.spec.ts @@ -101,17 +101,14 @@ describe(`sbb-expansion-panel`, () => { const header: SbbExpansionPanelHeaderElement = element.querySelector('sbb-expansion-panel-header')!; expect(header.disabled).to.be.equal(false); - expect(header).not.to.have.attribute('aria-disabled'); element.disabled = true; await waitForLitRender(element); expect(header.disabled).to.be.equal(true); - expect(header).to.have.attribute('aria-disabled', 'true'); element.disabled = false; await waitForLitRender(element); expect(header.disabled).to.be.equal(false); - expect(header).not.to.have.attribute('aria-disabled'); }); it('size property is proxied to children', async () => { diff --git a/src/elements/file-selector/file-selector.spec.ts b/src/elements/file-selector/file-selector.spec.ts index 9358788a46..36de689f15 100644 --- a/src/elements/file-selector/file-selector.spec.ts +++ b/src/elements/file-selector/file-selector.spec.ts @@ -156,7 +156,6 @@ describe(`sbb-file-selector`, () => { data-action data-button data-sbb-button - role="button" size="m" tabindex="0"> diff --git a/src/elements/file-selector/file-selector.ts b/src/elements/file-selector/file-selector.ts index 3736a5e156..c6d5d6c30d 100644 --- a/src/elements/file-selector/file-selector.ts +++ b/src/elements/file-selector/file-selector.ts @@ -36,7 +36,6 @@ export type DOMEvent = globalThis.Event; * @event {CustomEvent} fileChanged - An event which is emitted each time the file list changes. * @event change - An event which is emitted each time the user modifies the value. Unlike the input event, the change event is not necessarily fired for each alteration to an element's value * @event input - An event which is emitted each time the value changes as a direct result of a user action. - * @overrideType value - string | null */ export @customElement('sbb-file-selector') diff --git a/src/elements/form-field/form-field-clear/__snapshots__/form-field-clear.snapshot.spec.snap.js b/src/elements/form-field/form-field-clear/__snapshots__/form-field-clear.snapshot.spec.snap.js index 28dd472d61..37c30a7dcc 100644 --- a/src/elements/form-field/form-field-clear/__snapshots__/form-field-clear.snapshot.spec.snap.js +++ b/src/elements/form-field/form-field-clear/__snapshots__/form-field-clear.snapshot.spec.snap.js @@ -25,7 +25,6 @@ snapshots["sbb-form-field-clear renders form-field DOM"] = aria-label="Clear input value" data-action="" data-button="" - role="button" slot="suffix" tabindex="0" > diff --git a/src/elements/form-field/form-field-clear/readme.md b/src/elements/form-field/form-field-clear/readme.md index 5f6395457b..206c6bdf69 100644 --- a/src/elements/form-field/form-field-clear/readme.md +++ b/src/elements/form-field/form-field-clear/readme.md @@ -15,10 +15,10 @@ to provide the possibility to display a clear button which can clear the input v ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| ---------- | ---------- | ------- | --------------- | ---------- | ------------------------------------------------ | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| ---------- | ---------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------- | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | diff --git a/src/elements/form-field/form-field/form-field.ts b/src/elements/form-field/form-field/form-field.ts index 72f3f4aae0..45e587a6fd 100644 --- a/src/elements/form-field/form-field/form-field.ts +++ b/src/elements/form-field/form-field/form-field.ts @@ -184,7 +184,7 @@ class SbbFormFieldElement extends SbbNegativeMixin(SbbHydrationMixin(LitElement) .composedPath() .some( (el) => - (el instanceof window.HTMLElement && el.getAttribute('role') === 'button') || + (el instanceof window.HTMLElement && el.getAttribute('tabindex') === '0') || this._excludedFocusElements.includes((el as HTMLElement).localName), ); } diff --git a/src/elements/header/common/header-action.scss b/src/elements/header/common/header-action.scss index 53d13435b0..6aedcf7a26 100644 --- a/src/elements/header/common/header-action.scss +++ b/src/elements/header/common/header-action.scss @@ -75,7 +75,7 @@ } } -:host([role='button']) { +:host([data-button]) { @include sbb.if-forced-colors { --sbb-header-action-color: ButtonText; } diff --git a/src/elements/header/header-button/__snapshots__/header-button.snapshot.spec.snap.js b/src/elements/header/header-button/__snapshots__/header-button.snapshot.spec.snap.js index 64d98a5438..50611f7642 100644 --- a/src/elements/header/header-button/__snapshots__/header-button.snapshot.spec.snap.js +++ b/src/elements/header/header-button/__snapshots__/header-button.snapshot.spec.snap.js @@ -9,7 +9,6 @@ snapshots["sbb-header-button renders DOM"] = expand-from="zero" icon-name="pie-small" name="test" - role="button" tabindex="0" type="reset" value="value" diff --git a/src/elements/header/header-button/readme.md b/src/elements/header/header-button/readme.md index 9140ecbb74..bc317b9729 100644 --- a/src/elements/header/header-button/readme.md +++ b/src/elements/header/header-button/readme.md @@ -48,14 +48,14 @@ accepting its associated properties (`type`, `name`, `value` and `form`). ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| ------------ | ------------- | ------- | ------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `expandFrom` | `expand-from` | public | `SbbHorizontalFrom` | `'medium'` | Used to set the minimum breakpoint from which the text is displayed. E.g. if set to 'large', the text will be visible for breakpoints large, wide, ultra, and hidden for all the others. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| ------------ | ------------- | ------- | ------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `expandFrom` | `expand-from` | public | `SbbHorizontalFrom` | `'medium'` | Used to set the minimum breakpoint from which the text is displayed. E.g. if set to 'large', the text will be visible for breakpoints large, wide, ultra, and hidden for all the others. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Slots diff --git a/src/elements/link-list/link-list-anchor/link-list-anchor.scss b/src/elements/link-list/link-list-anchor/link-list-anchor.scss index e44dcd0dc7..9ac662298e 100644 --- a/src/elements/link-list/link-list-anchor/link-list-anchor.scss +++ b/src/elements/link-list/link-list-anchor/link-list-anchor.scss @@ -25,7 +25,7 @@ } } -::slotted([data-link]:is(:active, [data-active]):not([disabled])) { +::slotted([data-link]:is(:active, [data-active]):not([disabled], :disabled)) { --sbb-link-list-anchor-border-color: var(--sbb-color-iron); :host([negative]) & { @@ -33,7 +33,7 @@ } } -::slotted([data-link]:hover:not([disabled])) { +::slotted([data-link]:hover:not([disabled], :disabled)) { --sbb-link-list-anchor-border-color: var(--sbb-color-charcoal); :host([negative]) & { diff --git a/src/elements/link/block-link-button/__snapshots__/block-link-button.snapshot.spec.snap.js b/src/elements/link/block-link-button/__snapshots__/block-link-button.snapshot.spec.snap.js index fcbb0db3c6..5c4a3fdd02 100644 --- a/src/elements/link/block-link-button/__snapshots__/block-link-button.snapshot.spec.snap.js +++ b/src/elements/link/block-link-button/__snapshots__/block-link-button.snapshot.spec.snap.js @@ -11,7 +11,6 @@ snapshots["sbb-block-link-button renders DOM"] = icon-placement="end" name="name" negative="" - role="button" size="m" tabindex="0" type="submit" diff --git a/src/elements/link/block-link-button/readme.md b/src/elements/link/block-link-button/readme.md index 75d3499ade..4e6dfba110 100644 --- a/src/elements/link/block-link-button/readme.md +++ b/src/elements/link/block-link-button/readme.md @@ -61,17 +61,17 @@ guard against such cases in your component. ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| --------------------- | ---------------------- | ------- | ------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | -| `iconPlacement` | `icon-placement` | public | `SbbIconPlacement` | `'start'` | Moves the icon to the end of the component if set to true. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `size` | `size` | public | `SbbLinkSize` | `'s'` | Text size, the link should get in the non-button variation. With inline variant, the text size adapts to where it is used. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| --------------------- | ---------------------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | +| `iconPlacement` | `icon-placement` | public | `SbbIconPlacement` | `'start'` | Moves the icon to the end of the component if set to true. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `size` | `size` | public | `SbbLinkSize` | `'s'` | Text size, the link should get in the non-button variation. With inline variant, the text size adapts to where it is used. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Slots diff --git a/src/elements/link/common/link.scss b/src/elements/link/common/link.scss index 9299427d62..e357e87874 100644 --- a/src/elements/link/common/link.scss +++ b/src/elements/link/common/link.scss @@ -1,5 +1,7 @@ @use '../../core/styles' as sbb; +$disabled: '[disabled], :disabled, [disabled-interactive]'; + // 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; @@ -9,7 +11,7 @@ outline: none !important; } -:host([role='button']) { +:host([data-button]) { @include sbb.link-variables--button; } @@ -26,7 +28,7 @@ outline: none; } - :host([disabled]) & { + :host(:is(#{$disabled})) & { pointer-events: none; cursor: default; @@ -45,11 +47,11 @@ border-radius: calc(var(--sbb-border-radius-4x) - var(--sbb-focus-outline-offset)); } - :host(:hover:not([disabled])) & { + :host(:hover:not(#{$disabled})) & { @include sbb.link-hover-rules; } - :host(:is(:active, [data-active]):not([disabled])) & { + :host(:is(:active, [data-active]):not(#{$disabled})) & { // Active definitions have to be after :hover definitions @include sbb.link-active-rules; } diff --git a/src/elements/link/link-button/__snapshots__/link-button.snapshot.spec.snap.js b/src/elements/link/link-button/__snapshots__/link-button.snapshot.spec.snap.js index 158e27ca90..962270ffea 100644 --- a/src/elements/link/link-button/__snapshots__/link-button.snapshot.spec.snap.js +++ b/src/elements/link/link-button/__snapshots__/link-button.snapshot.spec.snap.js @@ -10,7 +10,6 @@ snapshots["sbb-link-button renders DOM"] = data-slot-names="unnamed" form="form" name="name" - role="button" size="m" tabindex="0" type="button" diff --git a/src/elements/link/link-button/link-button.visual.spec.ts b/src/elements/link/link-button/link-button.visual.spec.ts index cbba698e34..9d3441d557 100644 --- a/src/elements/link/link-button/link-button.visual.spec.ts +++ b/src/elements/link/link-button/link-button.visual.spec.ts @@ -1,6 +1,10 @@ import { html } from 'lit'; -import { describeViewports, visualDiffStandardStates } from '../../core/testing/private.js'; +import { + describeViewports, + visualDiffDefault, + visualDiffStandardStates, +} from '../../core/testing/private.js'; import './link-button.js'; @@ -26,5 +30,14 @@ describe(`sbb-link-button`, () => { ); } } + + it( + `disabledInteractive`, + visualDiffDefault.with(async (setup) => { + await setup.withFixture( + html`Show more.`, + ); + }), + ); }); }); diff --git a/src/elements/link/link-button/readme.md b/src/elements/link/link-button/readme.md index 2420e9eb08..11745d680d 100644 --- a/src/elements/link/link-button/readme.md +++ b/src/elements/link/link-button/readme.md @@ -47,16 +47,16 @@ guard against such cases in your component. ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| --------------------- | ---------------------- | ------- | --------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------- | -| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | -| `size` | `size` | public | `SbbLinkSize` | `'s'` | Text size, the link should get in the non-button variation. With inline variant, the text size adapts to where it is used. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| --------------------- | ---------------------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------- | +| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | +| `size` | `size` | public | `SbbLinkSize` | `'s'` | Text size, the link should get in the non-button variation. With inline variant, the text size adapts to where it is used. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Slots diff --git a/src/elements/map-container/__snapshots__/map-container.snapshot.spec.snap.js b/src/elements/map-container/__snapshots__/map-container.snapshot.spec.snap.js index d141081fe9..6b8864a525 100644 --- a/src/elements/map-container/__snapshots__/map-container.snapshot.spec.snap.js +++ b/src/elements/map-container/__snapshots__/map-container.snapshot.spec.snap.js @@ -26,7 +26,6 @@ snapshots["sbb-map-container renders Shadow DOM"] = data-slot-names="unnamed" icon-name="location-pin-map-small" inert="" - role="button" size="l" tabindex="0" type="button" diff --git a/src/elements/menu/common/menu-action.scss b/src/elements/menu/common/menu-action.scss index 40f0fac485..9b77c1d261 100644 --- a/src/elements/menu/common/menu-action.scss +++ b/src/elements/menu/common/menu-action.scss @@ -1,5 +1,7 @@ @use '../../core/styles' as sbb; +$disabled: '[disabled], :disabled, [disabled-interactive]'; + // 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; @@ -18,14 +20,14 @@ display: block; } -:host(:hover:not([disabled])) { +:host(:hover:not(#{$disabled})) { @include sbb.hover-mq($hover: true) { --sbb-menu-background-color: var(--sbb-color-iron); --sbb-menu-action-forced-color-border-color: Highlight; } } -:host([disabled]) { +:host(:is(#{$disabled})) { --sbb-menu-action-cursor: default; --sbb-menu-action-color: var(--sbb-color-graphite); --sbb-menu-action-forced-color-border-color: GrayText; @@ -38,7 +40,7 @@ } } -:host([role='button']) { +:host([data-button]) { @include sbb.if-forced-colors { --sbb-menu-action-color: ButtonText; } @@ -90,7 +92,7 @@ .sbb-menu-action__label { @include sbb.ellipsis; - :host([disabled]) & { + :host(:is(#{$disabled})) & { text-decoration: line-through; } } diff --git a/src/elements/menu/menu-button/__snapshots__/menu-button.snapshot.spec.snap.js b/src/elements/menu/menu-button/__snapshots__/menu-button.snapshot.spec.snap.js index c4503f108b..37147bdd58 100644 --- a/src/elements/menu/menu-button/__snapshots__/menu-button.snapshot.spec.snap.js +++ b/src/elements/menu/menu-button/__snapshots__/menu-button.snapshot.spec.snap.js @@ -8,7 +8,6 @@ snapshots["sbb-menu-button renders DOM"] = data-button="" form="formid" name="name" - role="button" tabindex="0" type="submit" > @@ -41,7 +40,6 @@ snapshots["sbb-menu-button renders component with icon and amount DOM"] = data-action="" data-button="" icon-name="menu-small" - role="button" tabindex="0" > diff --git a/src/elements/menu/menu-button/menu-button.visual.spec.ts b/src/elements/menu/menu-button/menu-button.visual.spec.ts index 056c472493..14edb7482c 100644 --- a/src/elements/menu/menu-button/menu-button.visual.spec.ts +++ b/src/elements/menu/menu-button/menu-button.visual.spec.ts @@ -18,6 +18,7 @@ describe(`sbb-menu-button`, () => { iconName: 'tick-small', label: 'Button', disabled: false, + disabledInteractive: false, slottedIcon: false, }; @@ -26,6 +27,7 @@ describe(`sbb-menu-button`, () => { iconName, label, disabled, + disabledInteractive, slottedIcon, }: typeof defaultArgs): TemplateResult => html` ${repeat( @@ -35,6 +37,7 @@ describe(`sbb-menu-button`, () => { amount=${amount || nothing} icon-name=${iconName || nothing} ?disabled=${disabled} + ?disabled-interactive=${disabledInteractive} > ${label} ${index} ${slottedIcon @@ -71,6 +74,16 @@ describe(`sbb-menu-button`, () => { }), ); + it( + `disabledInteractive ${visualDiffState.name}`, + visualDiffState.with(async (setup) => { + await setup.withFixture( + template({ ...defaultArgs, disabledInteractive: true }), + wrapperStyles, + ); + }), + ); + it( `long label ${visualDiffState.name}`, visualDiffState.with(async (setup) => { diff --git a/src/elements/menu/menu-button/readme.md b/src/elements/menu/menu-button/readme.md index e49a86105b..1470c64dca 100644 --- a/src/elements/menu/menu-button/readme.md +++ b/src/elements/menu/menu-button/readme.md @@ -45,16 +45,16 @@ guard against such cases in your component. ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| --------------------- | ---------------------- | ------- | --------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `amount` | `amount` | public | `string` | `''` | Value shown as badge at component end. | -| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| --------------------- | ---------------------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `amount` | `amount` | public | `string` | `''` | Value shown as badge at component end. | +| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## CSS Properties diff --git a/src/elements/menu/menu-link/menu-link.visual.spec.ts b/src/elements/menu/menu-link/menu-link.visual.spec.ts index 35a8ae77e7..e66e1c3ec7 100644 --- a/src/elements/menu/menu-link/menu-link.visual.spec.ts +++ b/src/elements/menu/menu-link/menu-link.visual.spec.ts @@ -18,6 +18,7 @@ describe(`sbb-menu-link`, () => { iconName: 'tick-small', label: 'Link', disabled: false, + disabledInteractive: false, slottedIcon: false, }; @@ -26,6 +27,7 @@ describe(`sbb-menu-link`, () => { iconName, label, disabled, + disabledInteractive, slottedIcon, }: typeof defaultArgs): TemplateResult => html` ${repeat( @@ -36,6 +38,7 @@ describe(`sbb-menu-link`, () => { amount=${amount || nothing} icon-name=${iconName || nothing} ?disabled=${disabled} + ?disabled-interactive=${disabledInteractive} > ${label} ${index} ${slottedIcon @@ -71,6 +74,16 @@ describe(`sbb-menu-link`, () => { }), ); + it( + `disabledInteractive ${visualDiffState.name}`, + visualDiffState.with(async (setup) => { + await setup.withFixture( + template({ ...defaultArgs, disabledInteractive: true }), + wrapperStyles, + ); + }), + ); + it( `long label ${visualDiffState.name}`, visualDiffState.with(async (setup) => { diff --git a/src/elements/menu/menu-link/readme.md b/src/elements/menu/menu-link/readme.md index 005c8fbfcc..8d476bbcff 100644 --- a/src/elements/menu/menu-link/readme.md +++ b/src/elements/menu/menu-link/readme.md @@ -35,7 +35,7 @@ accepting its associated properties (`href`, `target`, `rel` and `download`). | `accessibilityLabel` | `accessibility-label` | public | `string` | `''` | This will be forwarded as aria-label to the inner anchor element. | | `amount` | `amount` | public | `string` | `''` | Value shown as badge at component end. | | `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | | `download` | `download` | public | `boolean` | `false` | Whether the browser will show the download dialog on click. | | `href` | `href` | public | `string` | `''` | The href value you want to link to. | | `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | diff --git a/src/elements/menu/menu/__snapshots__/menu.snapshot.spec.snap.js b/src/elements/menu/menu/__snapshots__/menu.snapshot.spec.snap.js index 12b168a8a0..13a5b2316c 100644 --- a/src/elements/menu/menu/__snapshots__/menu.snapshot.spec.snap.js +++ b/src/elements/menu/menu/__snapshots__/menu.snapshot.spec.snap.js @@ -22,19 +22,16 @@ snapshots["sbb-menu renders DOM"] = data-action="" data-button="" icon-name="tick-small" - role="button" tabindex="0" > View Edit @@ -43,7 +40,6 @@ snapshots["sbb-menu renders DOM"] = data-action="" data-button="" icon-name="swisspass-small" - role="button" tabindex="0" > Details @@ -58,7 +54,6 @@ snapshots["sbb-menu renders DOM"] = data-action="" data-button="" icon-name="cross-small" - role="button" tabindex="0" > Cancel @@ -89,7 +84,6 @@ snapshots["sbb-menu renders with list DOM"] = data-action="" data-button="" icon-name="tick-small" - role="button" slot="li-0" tabindex="0" > @@ -97,12 +91,10 @@ snapshots["sbb-menu renders with list DOM"] = Edit @@ -112,7 +104,6 @@ snapshots["sbb-menu renders with list DOM"] = data-action="" data-button="" icon-name="swisspass-small" - role="button" slot="li-2" tabindex="0" > @@ -122,7 +113,6 @@ snapshots["sbb-menu renders with list DOM"] = data-action="" data-button="" icon-name="cross-small" - role="button" slot="li-3" tabindex="0" > diff --git a/src/elements/message/__snapshots__/message.snapshot.spec.snap.js b/src/elements/message/__snapshots__/message.snapshot.spec.snap.js index 3125bfd7ad..bd5bb84d96 100644 --- a/src/elements/message/__snapshots__/message.snapshot.spec.snap.js +++ b/src/elements/message/__snapshots__/message.snapshot.spec.snap.js @@ -21,7 +21,6 @@ snapshots["sbb-message renders DOM"] = data-button="" data-sbb-button="" icon-name="arrows-circle-small" - role="button" size="l" slot="action" tabindex="0" diff --git a/src/elements/navigation/common/navigation-action.scss b/src/elements/navigation/common/navigation-action.scss index c13aff1bf8..70e6947ae9 100644 --- a/src/elements/navigation/common/navigation-action.scss +++ b/src/elements/navigation/common/navigation-action.scss @@ -40,7 +40,7 @@ sbb-icon { } } -:host([role='button']) { +:host([data-button]) { @include sbb.if-forced-colors { --sbb-navigation-action-color: ButtonText; } diff --git a/src/elements/navigation/navigation-button/__snapshots__/navigation-button.snapshot.spec.snap.js b/src/elements/navigation/navigation-button/__snapshots__/navigation-button.snapshot.spec.snap.js index cbb9014f3e..b5006b60c4 100644 --- a/src/elements/navigation/navigation-button/__snapshots__/navigation-button.snapshot.spec.snap.js +++ b/src/elements/navigation/navigation-button/__snapshots__/navigation-button.snapshot.spec.snap.js @@ -5,7 +5,6 @@ snapshots["sbb-navigation-button renders DOM"] = ` diff --git a/src/elements/navigation/navigation-button/readme.md b/src/elements/navigation/navigation-button/readme.md index 7be9392a56..d2f78177ce 100644 --- a/src/elements/navigation/navigation-button/readme.md +++ b/src/elements/navigation/navigation-button/readme.md @@ -31,16 +31,16 @@ The component has three different sizes, which can be changed using the `size` p ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| ------------------ | --------- | ------- | ------------------------------------------ | ---------- | ----------------------------------------------------------- | -| `connectedSection` | - | public | `SbbNavigationSectionElement \| undefined` | | The section that is beign controlled by the action, if any. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `marker` | - | public | `SbbNavigationMarkerElement \| null` | | The navigation marker in which the action is nested. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `section` | - | public | `SbbNavigationSectionElement \| null` | | The section in which the action is nested. | -| `size` | `size` | public | `SbbNavigationActionSize` | `'l'` | Action size variant. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| ------------------ | --------- | ------- | ------------------------------------------ | ---------- | -------------------------------------------------------------- | +| `connectedSection` | - | public | `SbbNavigationSectionElement \| undefined` | | The section that is beign controlled by the action, if any. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `marker` | - | public | `SbbNavigationMarkerElement \| null` | | The navigation marker in which the action is nested. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `section` | - | public | `SbbNavigationSectionElement \| null` | | The section in which the action is nested. | +| `size` | `size` | public | `SbbNavigationActionSize` | `'l'` | Action size variant. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Slots diff --git a/src/elements/navigation/navigation-list/__snapshots__/navigation-list.snapshot.spec.snap.js b/src/elements/navigation/navigation-list/__snapshots__/navigation-list.snapshot.spec.snap.js index 8dcb4f1fc0..e9a424870c 100644 --- a/src/elements/navigation/navigation-list/__snapshots__/navigation-list.snapshot.spec.snap.js +++ b/src/elements/navigation/navigation-list/__snapshots__/navigation-list.snapshot.spec.snap.js @@ -6,7 +6,6 @@ snapshots["sbb-navigation-list renders DOM"] = @@ -120,7 +119,6 @@ snapshots["sbb-notification renders with a title Shadow DOM"] = data-button="" data-sbb-button="" icon-name="cross-small" - role="button" size="m" tabindex="0" > @@ -187,7 +185,6 @@ snapshots["sbb-notification renders with a slotted title Shadow DOM"] = data-button="" data-sbb-button="" icon-name="cross-small" - role="button" size="m" tabindex="0" > @@ -299,7 +296,6 @@ snapshots["sbb-notification renders size s Shadow DOM"] = data-button="" data-sbb-button="" icon-name="cross-small" - role="button" size="s" tabindex="0" > diff --git a/src/elements/option/option/option.ssr.spec.ts b/src/elements/option/option/option.ssr.spec.ts index 771af8c3b6..641da0aea2 100644 --- a/src/elements/option/option/option.ssr.spec.ts +++ b/src/elements/option/option/option.ssr.spec.ts @@ -94,7 +94,7 @@ describe(`sbb-option ssr`, () => { ); }); - it.only('renders', () => { + it('renders', () => { assert.instanceOf(root.querySelector('sbb-option'), SbbOptionElement); }); }); diff --git a/src/elements/overlay/__snapshots__/overlay.snapshot.spec.snap.js b/src/elements/overlay/__snapshots__/overlay.snapshot.spec.snap.js index d299d353bd..62f63597b1 100644 --- a/src/elements/overlay/__snapshots__/overlay.snapshot.spec.snap.js +++ b/src/elements/overlay/__snapshots__/overlay.snapshot.spec.snap.js @@ -19,7 +19,6 @@ snapshots["sbb-overlay renders Shadow DOM"] = data-button="" data-sbb-button="" icon-name="cross-small" - role="button" sbb-overlay-close="" size="m" tabindex="0" diff --git a/src/elements/overlay/overlay-base-element.ts b/src/elements/overlay/overlay-base-element.ts index d492880151..b6b4a4ef06 100644 --- a/src/elements/overlay/overlay-base-element.ts +++ b/src/elements/overlay/overlay-base-element.ts @@ -2,10 +2,10 @@ import { type PropertyValues } from 'lit'; import { property } from 'lit/decorators.js'; import { SbbFocusHandler } from '../core/a11y.js'; -import { SbbOpenCloseBaseElement } from '../core/base-elements.js'; +import { type SbbButtonBaseElement, SbbOpenCloseBaseElement } from '../core/base-elements.js'; import { SbbInertController, SbbLanguageController } from '../core/controllers.js'; import { forceType } from '../core/decorators.js'; -import { hostContext, SbbScrollHandler } from '../core/dom.js'; +import { SbbScrollHandler } from '../core/dom.js'; import { EventEmitter } from '../core/eventing.js'; import { i18nDialog } from '../core/i18n.js'; import type { SbbOverlayCloseEventDetails } from '../core/interfaces.js'; @@ -135,8 +135,8 @@ export abstract class SbbOverlayBaseElement extends SbbNegativeMixin(SbbOpenClos // Check if the target is a submission element within a form and return the form, if present const closestForm = overlayCloseElement.getAttribute('type') === 'submit' - ? (hostContext('form', overlayCloseElement) as HTMLFormElement) - : undefined; + ? ((overlayCloseElement as HTMLButtonElement | SbbButtonBaseElement).form ?? null) + : null; overlayRefs[overlayRefs.length - 1].close(closestForm, overlayCloseElement); } diff --git a/src/elements/overlay/overlay.spec.ts b/src/elements/overlay/overlay.spec.ts index ef0c0d982c..f9e26b0d8e 100644 --- a/src/elements/overlay/overlay.spec.ts +++ b/src/elements/overlay/overlay.spec.ts @@ -2,6 +2,7 @@ import { assert, expect, fixture } from '@open-wc/testing'; import { sendKeys, setViewport } from '@web/test-runner-commands'; import { html } from 'lit/static-html.js'; +import type { SbbButtonElement } from '../button.js'; import { i18nDialog } from '../core/i18n.js'; import { tabKey } from '../core/testing/private.js'; import { EventSpy, waitForCondition, waitForLitRender } from '../core/testing.js'; @@ -129,6 +130,32 @@ describe('sbb-overlay', () => { expect(element).to.have.attribute('data-state', 'closed'); }); + it('closes the overlay on close button click with linked form', async () => { + element = await fixture(html` +
+ + +

Overlay content

+ Close +
+
+ `); + + const overlay = element.querySelector('sbb-overlay')!; + const closeButton = element.querySelector('[type="submit"]')!; + const form = element.querySelector('form')!; + const willClose = new EventSpy(SbbOverlayElement.events.willClose); + + await openOverlay(overlay); + + closeButton.click(); + await waitForLitRender(element); + + await waitForCondition(() => willClose.events.length === 1); + + expect(willClose.firstEvent?.detail.returnValue).to.be.deep.equal(form); + }); + it('closes the overlay on Esc key press', async () => { const willClose = new EventSpy(SbbOverlayElement.events.willClose); const didClose = new EventSpy(SbbOverlayElement.events.didClose); diff --git a/src/elements/paginator/compact-paginator/__snapshots__/compact-paginator.snapshot.spec.snap.js b/src/elements/paginator/compact-paginator/__snapshots__/compact-paginator.snapshot.spec.snap.js index 539ddf67c0..62e2069ade 100644 --- a/src/elements/paginator/compact-paginator/__snapshots__/compact-paginator.snapshot.spec.snap.js +++ b/src/elements/paginator/compact-paginator/__snapshots__/compact-paginator.snapshot.spec.snap.js @@ -17,14 +17,12 @@ snapshots["sbb-compact-paginator renders Shadow DOM"] = `
@@ -41,7 +39,6 @@ snapshots["sbb-compact-paginator renders Shadow DOM"] = data-button="" icon-name="chevron-small-right-small" id="sbb-paginator-next-page" - role="button" slot="li-2" tabindex="0" > diff --git a/src/elements/paginator/paginator/__snapshots__/paginator.snapshot.spec.snap.js b/src/elements/paginator/paginator/__snapshots__/paginator.snapshot.spec.snap.js index c533c9e2b0..dd2f70ed84 100644 --- a/src/elements/paginator/paginator/__snapshots__/paginator.snapshot.spec.snap.js +++ b/src/elements/paginator/paginator/__snapshots__/paginator.snapshot.spec.snap.js @@ -18,14 +18,12 @@ snapshots["sbb-paginator renders Shadow DOM"] = @@ -37,14 +35,12 @@ snapshots["sbb-paginator renders Shadow DOM"] = > @@ -89,14 +85,12 @@ snapshots["sbb-paginator renders ellipsis on end side Shadow DOM"] = @@ -113,7 +107,6 @@ snapshots["sbb-paginator renders ellipsis on end side Shadow DOM"] = data-button="" icon-name="chevron-small-right-small" id="sbb-paginator-next-page" - role="button" slot="li-2" tabindex="0" > @@ -214,7 +207,6 @@ snapshots["sbb-paginator renders ellipsis on start side Shadow DOM"] = data-button="" icon-name="chevron-small-left-small" id="sbb-paginator-prev-page" - role="button" slot="li-0" tabindex="0" > @@ -232,7 +224,6 @@ snapshots["sbb-paginator renders ellipsis on start side Shadow DOM"] = data-button="" icon-name="chevron-small-right-small" id="sbb-paginator-next-page" - role="button" slot="li-2" tabindex="0" > @@ -333,7 +324,6 @@ snapshots["sbb-paginator renders ellipsis on both side Shadow DOM"] = data-button="" icon-name="chevron-small-left-small" id="sbb-paginator-prev-page" - role="button" slot="li-0" tabindex="0" > @@ -351,7 +341,6 @@ snapshots["sbb-paginator renders ellipsis on both side Shadow DOM"] = data-button="" icon-name="chevron-small-right-small" id="sbb-paginator-next-page" - role="button" slot="li-2" tabindex="0" > @@ -451,14 +440,12 @@ snapshots["sbb-paginator renders with options Chrome-Firefox Shadow DOM"] = @@ -475,7 +462,6 @@ snapshots["sbb-paginator renders with options Chrome-Firefox Shadow DOM"] = data-button="" icon-name="chevron-small-right-small" id="sbb-paginator-next-page" - role="button" slot="li-2" tabindex="0" > @@ -634,14 +620,12 @@ snapshots["sbb-paginator renders with options Safari Shadow DOM"] = @@ -658,7 +642,6 @@ snapshots["sbb-paginator renders with options Safari Shadow DOM"] = data-button="" icon-name="chevron-small-right-small" id="sbb-paginator-next-page" - role="button" slot="li-2" tabindex="0" > diff --git a/src/elements/popover/popover-trigger/__snapshots__/popover-trigger.snapshot.spec.snap.js b/src/elements/popover/popover-trigger/__snapshots__/popover-trigger.snapshot.spec.snap.js index 2e03b9e812..505f63da96 100644 --- a/src/elements/popover/popover-trigger/__snapshots__/popover-trigger.snapshot.spec.snap.js +++ b/src/elements/popover/popover-trigger/__snapshots__/popover-trigger.snapshot.spec.snap.js @@ -5,7 +5,6 @@ snapshots["sbb-popover-trigger renders DOM"] = ` @@ -31,7 +30,6 @@ snapshots["sbb-popover-trigger renders with custom content DOM"] = ` Custom Content diff --git a/src/elements/popover/popover-trigger/popover-trigger.scss b/src/elements/popover/popover-trigger/popover-trigger.scss index f6387526f2..5b9667d0f2 100644 --- a/src/elements/popover/popover-trigger/popover-trigger.scss +++ b/src/elements/popover/popover-trigger/popover-trigger.scss @@ -1,5 +1,8 @@ @use '../../core/styles' as sbb; +$disabled: ':disabled, [disabled-interactive]'; +$active: ':active, [data-active]'; + // 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; @@ -31,15 +34,15 @@ --sbb-focus-outline-color: var(--sbb-focus-outline-color-dark); } -:host(:is(:active, [data-active])) { +:host(:is(#{$active})) { --sbb-popover-color: var(--sbb-color-anthracite); } -:host(:is(:active, [data-active])[negative]) { +:host(:is(#{$active})[negative]) { --sbb-popover-color: var(--sbb-color-cement); } -:host([disabled]) { +:host(:is(#{$disabled})) { pointer-events: none; --sbb-popover-color: var(--sbb-color-graphite); @@ -49,7 +52,7 @@ } } -:host([disabled][negative]) { +:host(:is(#{$disabled})[negative]) { --sbb-popover-color: var(--sbb-color-smoke); } @@ -59,7 +62,7 @@ @include sbb.icon-button-variables-negative; } -:host([data-icon-small][disabled]) { +:host([data-icon-small]:is(#{$disabled})) { @include sbb.icon-button-disabled('.sbb-popover-trigger'); } @@ -69,11 +72,11 @@ @include sbb.icon-button-focus-visible('.sbb-popover-trigger'); } -:host([data-icon-small]:not([disabled], :active, [data-active]):hover) { +:host([data-icon-small]:not(#{$disabled}, #{active}):hover) { @include sbb.icon-button-hover('.sbb-popover-trigger'); } -:host([data-icon-small]:is(:active, [data-active])) { +:host([data-icon-small]:is(#{active})) { @include sbb.icon-button-active('.sbb-popover-trigger'); } diff --git a/src/elements/popover/popover-trigger/popover-trigger.visual.spec.ts b/src/elements/popover/popover-trigger/popover-trigger.visual.spec.ts index 99d48a7fa6..4432e20e4b 100644 --- a/src/elements/popover/popover-trigger/popover-trigger.visual.spec.ts +++ b/src/elements/popover/popover-trigger/popover-trigger.visual.spec.ts @@ -59,5 +59,38 @@ describe(`sbb-popover-trigger`, () => { ); } }); + + describe(`disabledInteractive`, () => { + for (const negative of [false, true]) { + describe(`negative=${negative}`, () => { + for (const state of visualDiffStandardStates) { + it( + state.name, + state.with(async (setup) => { + await setup.withFixture( + html` + + + This is a demo text. + + + + `, + { backgroundColor: negative ? 'var(--sbb-color-charcoal)' : undefined }, + ); + }), + ); + } + }); + } + }); }); }); diff --git a/src/elements/popover/popover-trigger/readme.md b/src/elements/popover/popover-trigger/readme.md index 9941a98095..bcbd0fee0e 100644 --- a/src/elements/popover/popover-trigger/readme.md +++ b/src/elements/popover/popover-trigger/readme.md @@ -84,16 +84,16 @@ guard against such cases in your component. ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| --------------------- | ---------------------- | ------- | --------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | -| `form` | `form` | public | `string` | `''` | The
element to associate the button with. | -| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| --------------------- | ---------------------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Slots diff --git a/src/elements/popover/popover/__snapshots__/popover.snapshot.spec.snap.js b/src/elements/popover/popover/__snapshots__/popover.snapshot.spec.snap.js index 87da862679..92fbdccb4c 100644 --- a/src/elements/popover/popover/__snapshots__/popover.snapshot.spec.snap.js +++ b/src/elements/popover/popover/__snapshots__/popover.snapshot.spec.snap.js @@ -24,7 +24,6 @@ snapshots["sbb-popover renders Shadow DOM"] = data-button="" data-sbb-button="" icon-name="cross-small" - role="button" sbb-popover-close="" size="s" tabindex="0" diff --git a/src/elements/slider/slider.ts b/src/elements/slider/slider.ts index 3a2ec4a045..705a334cba 100644 --- a/src/elements/slider/slider.ts +++ b/src/elements/slider/slider.ts @@ -24,7 +24,6 @@ import '../icon.js'; * @slot prefix - Use this slot to render an icon on the left side of the input. * @slot suffix - Use this slot to render an icon on the right side of the input. * @event {CustomEvent} didChange - Deprecated. used for React. Will probably be removed once React 19 is available. - * @overrideType value - string | null */ export @customElement('sbb-slider') diff --git a/src/elements/stepper/step-label/readme.md b/src/elements/stepper/step-label/readme.md index cb4f7d596c..2aad06dd21 100644 --- a/src/elements/stepper/step-label/readme.md +++ b/src/elements/stepper/step-label/readme.md @@ -36,15 +36,15 @@ The accessibility properties `aria-controls`, `aria-setsize`, `aria-posinset` ar ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| ---------- | ----------- | ------- | ------------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `step` | - | public | `SbbStepElement \| null` | `null` | The step controlled by the label. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| ---------- | ----------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `step` | - | public | `SbbStepElement \| null` | `null` | The step controlled by the label. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Slots diff --git a/src/elements/stepper/step-label/step-label.scss b/src/elements/stepper/step-label/step-label.scss index 11b1ea4447..26f4133278 100644 --- a/src/elements/stepper/step-label/step-label.scss +++ b/src/elements/stepper/step-label/step-label.scss @@ -72,7 +72,7 @@ } } -:host([disabled]) { +:host(:disabled) { --sbb-step-label-color: var(--sbb-color-granite); --sbb-step-label-prefix-border-style: dashed; @@ -81,7 +81,7 @@ } } -:host(:hover:not([disabled])) { +:host(:hover:not(:disabled)) { @include sbb.hover-mq($hover: true) { --sbb-step-label-cursor: pointer; --sbb-step-label-prefix-background-color: var(--sbb-color-milk); diff --git a/src/elements/stepper/step-label/step-label.ts b/src/elements/stepper/step-label/step-label.ts index 641b4e825f..a48f1ea0aa 100644 --- a/src/elements/stepper/step-label/step-label.ts +++ b/src/elements/stepper/step-label/step-label.ts @@ -29,9 +29,6 @@ export class SbbStepLabelElement extends SbbIconNameMixin(SbbDisabledMixin(SbbButtonBaseElement)) { public static override styles: CSSResultGroup = style; - /** @internal */ - private readonly _internals: ElementInternals = this.attachInternals(); - /** The step controlled by the label. */ public get step(): SbbStepElement | null { return this._step; @@ -53,7 +50,7 @@ class SbbStepLabelElement extends SbbIconNameMixin(SbbDisabledMixin(SbbButtonBas super.connectedCallback(); const signal = this._abort.signal; this.id = this.id || `sbb-step-label-${nextId++}`; - this._internals.ariaSelected = 'false'; + this.internals.ariaSelected = 'false'; this._stepper = this.closest('sbb-stepper'); this._step = this._getStep(); // The `data-disabled` attribute is used to preserve the initial disabled state of @@ -83,7 +80,7 @@ class SbbStepLabelElement extends SbbIconNameMixin(SbbDisabledMixin(SbbButtonBas */ public select(): void { this.tabIndex = 0; - this._internals.ariaSelected = 'true'; + this.internals.ariaSelected = 'true'; this.toggleAttribute('data-selected', true); } @@ -93,7 +90,7 @@ class SbbStepLabelElement extends SbbIconNameMixin(SbbDisabledMixin(SbbButtonBas */ public deselect(): void { this.tabIndex = -1; - this._internals.ariaSelected = 'false'; + this.internals.ariaSelected = 'false'; this.toggleAttribute('data-selected', false); } @@ -105,8 +102,8 @@ class SbbStepLabelElement extends SbbIconNameMixin(SbbDisabledMixin(SbbButtonBas if (stepperLoaded) { this._step = this._getStep(); } - this._internals.ariaPosInSet = `${posInSet}`; - this._internals.ariaSetSize = `${setSize}`; + this.internals.ariaPosInSet = `${posInSet}`; + this.internals.ariaSetSize = `${setSize}`; } protected override render(): TemplateResult { diff --git a/src/elements/stepper/stepper/__snapshots__/stepper.snapshot.spec.snap.js b/src/elements/stepper/stepper/__snapshots__/stepper.snapshot.spec.snap.js index f00dc6c07c..2820fc390c 100644 --- a/src/elements/stepper/stepper/__snapshots__/stepper.snapshot.spec.snap.js +++ b/src/elements/stepper/stepper/__snapshots__/stepper.snapshot.spec.snap.js @@ -126,7 +126,8 @@ snapshots["sbb-stepper renders A11y tree Chrome"] = }, { "role": "tab", - "name": "Test step label 3" + "name": "Test step label 3", + "disabled": true }, { "role": "tab", @@ -159,7 +160,8 @@ snapshots["sbb-stepper renders A11y tree Firefox"] = }, { "role": "tab", - "name": "3 Test step label 3" + "name": "3 Test step label 3", + "disabled": true }, { "role": "tab", diff --git a/src/elements/stepper/stepper/stepper.snapshot.spec.ts b/src/elements/stepper/stepper/stepper.snapshot.spec.ts index fe677808d4..b6bb667104 100644 --- a/src/elements/stepper/stepper/stepper.snapshot.spec.ts +++ b/src/elements/stepper/stepper/stepper.snapshot.spec.ts @@ -2,7 +2,6 @@ import { expect } from '@open-wc/testing'; import { html } from 'lit/static-html.js'; import { fixture, testA11yTreeSnapshot } from '../../core/testing/private.js'; -import { waitForLitRender } from '../../core/testing.js'; import type { SbbStepperElement } from './stepper.js'; import './stepper.js'; @@ -25,7 +24,6 @@ describe('sbb-stepper', () => { Test step label 4 `); - await waitForLitRender(element); }); it('DOM', async () => { diff --git a/src/elements/tag/tag-group/__snapshots__/tag-group.snapshot.spec.snap.js b/src/elements/tag/tag-group/__snapshots__/tag-group.snapshot.spec.snap.js index 38870dd478..1497b47389 100644 --- a/src/elements/tag/tag-group/__snapshots__/tag-group.snapshot.spec.snap.js +++ b/src/elements/tag/tag-group/__snapshots__/tag-group.snapshot.spec.snap.js @@ -11,7 +11,6 @@ snapshots["sbb-tag-group renders DOM"] = data-action="" data-button="" data-slot-names="unnamed" - role="button" size="m" slot="li-0" tabindex="0" @@ -24,7 +23,6 @@ snapshots["sbb-tag-group renders DOM"] = data-action="" data-button="" data-slot-names="unnamed" - role="button" size="m" slot="li-1" tabindex="0" @@ -39,7 +37,6 @@ snapshots["sbb-tag-group renders DOM"] = data-action="" data-button="" data-slot-names="unnamed" - role="button" size="m" slot="li-3" tabindex="0" diff --git a/src/elements/tag/tag-group/readme.md b/src/elements/tag/tag-group/readme.md index 29d6005a47..ca2dab053c 100644 --- a/src/elements/tag/tag-group/readme.md +++ b/src/elements/tag/tag-group/readme.md @@ -103,13 +103,13 @@ that communicates the collective meaning of all `sbb-tag`s. ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| ------------------------ | -------------------------- | ------- | ---------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `listAccessibilityLabel` | `list-accessibility-label` | public | `string` | `''` | This will be forwarded as aria-label to the inner list. | -| `multiple` | `multiple` | public | `boolean` | `false` | If set multiple to false, the selection is exclusive and the value is a string (or null). If set multiple to true, the selection can have multiple values and therefore value is an array. Changing multiple during run time is not supported. | -| `size` | `size` | public | `SbbTagSize` | `'m'` | Tag group size. | -| `tags` | - | public | `SbbTagElement[]` | | The child instances of sbb-tag as an array. | -| `value` | `value` | public | `string \| string[] \| null` | `null` | Value of the sbb-tag-group. If set multiple to false, the value is a string (or null). If set multiple to true, the value is an array. | +| Name | Attribute | Privacy | Type | Default | Description | +| ------------------------ | -------------------------- | ------- | -------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `listAccessibilityLabel` | `list-accessibility-label` | public | `string` | `''` | This will be forwarded as aria-label to the inner list. | +| `multiple` | `multiple` | public | `boolean` | `false` | If set multiple to false, the selection is exclusive and the value is a string (or null). If set multiple to true, the selection can have multiple values and therefore value is an array. Changing multiple during run time is not supported. | +| `size` | `size` | public | `SbbTagSize` | `'m'` | Tag group size. | +| `tags` | - | public | `SbbTagElement[]` | | The child instances of sbb-tag as an array. | +| `value` | `value` | public | `string \| (string \| null)[] \| null` | `null` | Value of the sbb-tag-group. If set multiple to false, the value is a string (or null). If set multiple to true, the value is an array. | ## Slots diff --git a/src/elements/tag/tag-group/tag-group.ts b/src/elements/tag/tag-group/tag-group.ts index fd3f029994..5519e61c69 100644 --- a/src/elements/tag/tag-group/tag-group.ts +++ b/src/elements/tag/tag-group/tag-group.ts @@ -81,7 +81,7 @@ class SbbTagGroupElement extends SbbNamedSlotListMixin @@ -122,7 +118,6 @@ snapshots["sbb-tag renders slotted icon and amount DOM"] = data-action="" data-button="" data-slot-names="amount icon unnamed" - role="button" size="m" tabindex="0" value="foo" diff --git a/src/elements/tag/tag/readme.md b/src/elements/tag/tag/readme.md index 9f6cd71b43..d2a0861535 100644 --- a/src/elements/tag/tag/readme.md +++ b/src/elements/tag/tag/readme.md @@ -78,18 +78,18 @@ This can be achieved by adding an `aria-label`, `aria-labelledby` or `aria-descr ## Properties -| Name | Attribute | Privacy | Type | Default | Description | -| --------------------- | ---------------------- | ------- | --------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `amount` | `amount` | public | `string` | `''` | Amount displayed inside the tag. | -| `checked` | `checked` | public | `boolean` | `false` | Whether the tag is checked. | -| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | -| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether disabled buttons should be interactive. | -| `form` | `form` | public | `string` | `''` | The element to associate the button with. | -| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | -| `name` | `name` | public | `string` | | The name of the button element. | -| `size` | `size` | public | `SbbTagSize` | `'m'` | Tag size. | -| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | -| `value` | `value` | public | `string` | | The value of the button element. | +| Name | Attribute | Privacy | Type | Default | Description | +| --------------------- | ---------------------- | ------- | ------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `amount` | `amount` | public | `string` | `''` | Amount displayed inside the tag. | +| `checked` | `checked` | public | `boolean` | `false` | Whether the tag is checked. | +| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. | +| `disabledInteractive` | `disabled-interactive` | public | `boolean` | `false` | Whether the button should be aria-disabled but stay interactive. | +| `form` | `form` | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. | +| `iconName` | `icon-name` | public | `string` | `''` | The icon name we want to use, choose from the small icon variants from the ui-icons category from here https://icons.app.sbb.ch. | +| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. | +| `size` | `size` | public | `SbbTagSize` | `'m'` | Tag size. | +| `type` | `type` | public | `SbbButtonType` | `'button'` | The type attribute to use for the button. | +| `value` | `value` | public | `string \| null` | `null` | Value of the form element. | ## Events diff --git a/src/elements/tag/tag/tag.scss b/src/elements/tag/tag/tag.scss index da8ccac7f4..ef9d54537e 100644 --- a/src/elements/tag/tag/tag.scss +++ b/src/elements/tag/tag/tag.scss @@ -1,5 +1,8 @@ @use '../../core/styles' as sbb; +$disabled: ':disabled, [disabled-interactive]'; +$active: ':active, [data-active]'; + // 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; @@ -48,7 +51,7 @@ } } -:host([disabled]) { +:host(:is(#{$disabled})) { --sbb-tag-text-color: var(--sbb-color-granite); --sbb-tag-amount-color: var(--sbb-tag-text-color); --sbb-tag-background-color: var(--sbb-color-milk); @@ -62,11 +65,11 @@ } } -:host([checked][disabled]) { +:host([checked]:is(#{$disabled})) { --sbb-tag-border-color: var(--sbb-color-metal); } -:host(:hover:not([disabled], :active, [data-active])) { +:host(:hover:not(#{$disabled}, #{$active})) { @include sbb.hover-mq($hover: true) { --sbb-tag-background-color: var(--sbb-color-milk); --sbb-tag-inset: calc(var(--sbb-border-width-2x) * -1); @@ -79,7 +82,7 @@ } // Pressed state -:host(:is(:active, [data-active]):not([disabled])) { +:host(:is(#{$active}):not(#{$disabled})) { --sbb-tag-background-color: var(--sbb-color-milk); --sbb-tag-border-color: var(--sbb-color-iron); --sbb-tag-border-width: var(--sbb-border-width-2x); @@ -130,7 +133,7 @@ } } - :host([disabled]) & { + :host(#{$disabled}) & { cursor: unset; pointer-events: none; } diff --git a/src/elements/tag/tag/tag.visual.spec.ts b/src/elements/tag/tag/tag.visual.spec.ts index aaa161dec7..da3d64e58c 100644 --- a/src/elements/tag/tag/tag.visual.spec.ts +++ b/src/elements/tag/tag/tag.visual.spec.ts @@ -48,6 +48,26 @@ describe(`sbb-tag`, () => { ); }); + describe(`disabledInteractive`, () => { + for (const checked of [false, true]) { + it( + `checked=${checked}`, + visualDiffDefault.with(async (setup) => { + await setup.withFixture(html` + + Tag label + + `); + }), + ); + } + }); + describeEach(visualCases, ({ icon, amount, size }) => { it( '', diff --git a/src/elements/toast/__snapshots__/toast.snapshot.spec.snap.js b/src/elements/toast/__snapshots__/toast.snapshot.spec.snap.js index 55a7306434..843098a17c 100644 --- a/src/elements/toast/__snapshots__/toast.snapshot.spec.snap.js +++ b/src/elements/toast/__snapshots__/toast.snapshot.spec.snap.js @@ -47,7 +47,6 @@ snapshots["sbb-toast renders Chrome-Safari Shadow DOM"] = data-sbb-button="" icon-name="cross-small" negative="" - role="button" sbb-toast-close="" size="m" tabindex="0" @@ -119,7 +118,6 @@ snapshots["sbb-toast renders Firefox Shadow DOM"] = data-sbb-button="" icon-name="cross-small" negative="" - role="button" sbb-toast-close="" size="m" tabindex="0" diff --git a/src/elements/toggle-check/toggle-check.ts b/src/elements/toggle-check/toggle-check.ts index ce38bb1eaf..21382b758e 100644 --- a/src/elements/toggle-check/toggle-check.ts +++ b/src/elements/toggle-check/toggle-check.ts @@ -16,7 +16,6 @@ import style from './toggle-check.scss?lit&inline'; * @event {CustomEvent} didChange - Deprecated. used for React. Will probably be removed once React 19 is available. * @event {Event} change - Event fired on change. * @event {InputEvent} input - Event fired on input. - * @overrideType value - string | null */ export @customElement('sbb-toggle-check') diff --git a/tools/manifest/custom-elements-manifest.config.js b/tools/manifest/custom-elements-manifest.config.js index 05258833fd..a51e1fdf8d 100644 --- a/tools/manifest/custom-elements-manifest.config.js +++ b/tools/manifest/custom-elements-manifest.config.js @@ -27,6 +27,81 @@ export function createManifestConfig(library = '') { classDoc['_ssrslotstate'] = true; } + function replace(typeObj, typeName, typeValue) { + if (typeObj && typeObj.text) { + typeObj.text = typeObj.text.replace(typeName, typeValue); + } + } + + /** + * Replaces the mixins generics types with the default value. + * + * It has been created mainly referencing the `SbbFormAssociatedMixin`, + * in which it is necessary to replace the generic V type with its default (string). + * This allows using the jsDoc `@overrideType` annotation only for cases where the default is actually overridden. + */ + if (ts.isVariableStatement(node)) { + node.declarationList?.declarations?.forEach((decl) => { + if (decl.initializer?.typeParameters) { + const moduleDeclaration = moduleDoc.declarations.find( + (e) => e.name === decl.name?.getText(), + ); + decl.initializer?.typeParameters?.forEach((typeParam) => { + if (typeParam.default) { + const typeName = typeParam.name.getText(); + let typeValue; + switch (typeParam.default.kind) { + case ts.SyntaxKind.StringKeyword: + case ts.SyntaxKind.NumberKeyword: { + typeValue = ts.tokenToString(typeParam.default.kind); + break; + } + case ts.SyntaxKind.TypeReference: { + typeValue = typeParam.default.typeName.getText(); + break; + } + case ts.SyntaxKind.LiteralType: { + switch (typeParam.default.literal.kind) { + case ts.SyntaxKind.TrueKeyword: + case ts.SyntaxKind.FalseKeyword: + case ts.SyntaxKind.NullKeyword: { + typeValue = typeParam.default.literal.getText(); + break; + } + default: { + typeValue = `'${typeParam.default.literal.getText()}'`; + break; + } + } + break; + } + default: { + // missing cases: intersection types, union types,..? + typeValue = typeParam.default.getText(); + break; + } + } + moduleDeclaration?.members?.forEach((member) => { + if (member.kind === 'field') { + replace(member.type, typeName, typeValue); + } + if (member.kind === 'method') { + if (member.return) { + replace(member.return.type, typeName, typeValue); + } + if (member.parameters) { + member.parameters.forEach((parameter) => { + replace(parameter.type, typeName, typeValue); + }); + } + } + }); + } + }); + } + }); + } + if (ts.isNewExpression(node) && node.expression.getText() === 'SbbSlotStateController') { let classNode = node; while (classNode) {