Skip to content

Commit

Permalink
feat(sbb-select): implement native form support (#3101)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomMenga authored Oct 14, 2024
1 parent 97c1fca commit b9156ab
Show file tree
Hide file tree
Showing 13 changed files with 726 additions and 435 deletions.
29 changes: 16 additions & 13 deletions src/elements/checkbox/checkbox-panel/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,23 +71,26 @@ The component provides the same accessibility features as the native checkbox.
Always provide an accessible label via `aria-label` for checkboxes without descriptive text content.
If you don't want the label to appear next to the checkbox, you can use `aria-label` to specify an appropriate label.

<!-- Override
@type value => string \| null
-->
<!-- Auto Generated Below -->

## Properties

| Name | Attribute | Privacy | Type | Default | Description |
| --------------- | --------------- | ------- | --------------------------------- | --------- | ----------------------------------------------------------- |
| `borderless` | `borderless` | public | `boolean` | `false` | Whether the unselected panel has a border. |
| `checked` | `checked` | public | `boolean` | `false` | Whether the checkbox is checked. |
| `color` | `color` | public | `'white' \| 'milk'` | `'white'` | The background color of the panel. |
| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. |
| `form` | - | public | `HTMLFormElement \| null` | | Returns the form owner of internals target element. |
| `group` | - | public | `SbbCheckboxGroupElement \| null` | `null` | Reference to the connected checkbox group. |
| `indeterminate` | `indeterminate` | public | `boolean` | `false` | Whether the checkbox is indeterminate. |
| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. |
| `required` | `required` | public | `boolean` | `false` | Whether the component is required. |
| `size` | `size` | public | `SbbPanelSize` | `'m'` | Size variant. |
| `value` | `value` | public | `string \| null` | `null` | Value of the form element. |
| Name | Attribute | Privacy | Type | Default | Description |
| --------------- | --------------- | ------- | --------------------------------- | --------- | -------------------------------------------------------------- |
| `borderless` | `borderless` | public | `boolean` | `false` | Whether the unselected panel has a border. |
| `checked` | `checked` | public | `boolean` | `false` | Whether the checkbox is checked. |
| `color` | `color` | public | `'white' \| 'milk'` | `'white'` | The background color of the panel. |
| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. |
| `form` | - | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. |
| `group` | - | public | `SbbCheckboxGroupElement \| null` | `null` | Reference to the connected checkbox group. |
| `indeterminate` | `indeterminate` | public | `boolean` | `false` | Whether the checkbox is indeterminate. |
| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. |
| `required` | `required` | public | `boolean` | `false` | Whether the component is required. |
| `size` | `size` | public | `SbbPanelSize` | `'m'` | Size variant. |
| `value` | `value` | public | `string \| null` | `null` | Value of the form element. |

## Events

Expand Down
5 changes: 4 additions & 1 deletion src/elements/checkbox/checkbox/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ If you don't want the label to appear next to the checkbox, you can use `aria-la
<sbb-checkbox aria-label="Subscribed to email message"></sbb-checkbox>
```

<!-- Override
@type value => string \| null
-->
<!-- Auto Generated Below -->

## Properties
Expand All @@ -85,7 +88,7 @@ If you don't want the label to appear next to the checkbox, you can use `aria-la
| --------------- | ---------------- | ------- | --------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `checked` | `checked` | public | `boolean` | `false` | Whether the checkbox is checked. |
| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. |
| `form` | - | public | `HTMLFormElement \| null` | | Returns the form owner of internals target element. |
| `form` | - | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. |
| `group` | - | public | `SbbCheckboxGroupElement \| null` | `null` | Reference to the connected checkbox group. |
| `iconName` | `icon-name` | public | `string \| undefined` | | 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` | `'end'` | The label position relative to the labelIcon. Defaults to end |
Expand Down
1 change: 1 addition & 0 deletions src/elements/core/mixins/form-associated-checkbox-mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export declare abstract class SbbFormAssociatedCheckboxMixinType
protected isDisabledExternally(): boolean;
protected isRequiredExternally(): boolean;
protected withUserInteraction?(): void;
protected updateFormValue(): void;
}

/**
Expand Down
37 changes: 20 additions & 17 deletions src/elements/core/mixins/form-associated-mixin.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import type { LitElement } from 'lit';
import { property, state } from 'lit/decorators.js';

import type { Constructor } from './constructor.js';
import type { AbstractConstructor } from './constructor.js';

export declare abstract class SbbFormAssociatedMixinType {
export declare abstract class SbbFormAssociatedMixinType<V = string> {
public get form(): HTMLFormElement | null;
public get name(): string;
public set name(value: string);
public get type(): string;
public get value(): string | null;
public set value(value: string | null);
public get value(): V | null;
public set value(value: V | null);

public get validity(): ValidityState;
public get validationMessage(): string;
Expand All @@ -29,24 +29,24 @@ export declare abstract class SbbFormAssociatedMixinType {
reason: FormRestoreReason,
): void;

protected updateFormValue(): void;
protected abstract updateFormValue(): void;
}

/**
* The FormAssociatedMixin enables native form support for custom controls.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const SbbFormAssociatedMixin = <T extends Constructor<LitElement>>(
export const SbbFormAssociatedMixin = <T extends AbstractConstructor<LitElement>, V = string>(
superClass: T,
): Constructor<SbbFormAssociatedMixinType> & T => {
): AbstractConstructor<SbbFormAssociatedMixinType<V>> & T => {
abstract class SbbFormAssociatedElement
extends superClass
implements Partial<SbbFormAssociatedMixinType>
implements Partial<SbbFormAssociatedMixinType<V>>
{
public static formAssociated = true;

/**
* Returns the form owner of internals target element.
* Returns the form owner of the internals of the target element.
*/
public get form(): HTMLFormElement | null {
return this.internals.form;
Expand All @@ -73,14 +73,14 @@ export const SbbFormAssociatedMixin = <T extends Constructor<LitElement>>(

/** Value of the form element. */
@property()
public set value(value: string | null) {
public set value(value: V | null) {
this._value = value;
this.updateFormValue();
}
public get value(): string | null {
public get value(): V | null {
return this._value;
}
private _value: string | null = null;
private _value: V | null = null;

/**
* Returns the ValidityState object for internals target element.
Expand Down Expand Up @@ -192,12 +192,15 @@ export const SbbFormAssociatedMixin = <T extends Constructor<LitElement>>(
reason: FormRestoreReason,
): void;

/** Should be called when form value is changed. */
protected updateFormValue(): void {
this.internals.setFormValue(this.value);
}
/**
* Should be called when form value is changed.
* Adapts and sets the formValue in the supported format (string | FormData | File | null)
* https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/setFormValue
*/
protected abstract updateFormValue(): void;
}
return SbbFormAssociatedElement as unknown as Constructor<SbbFormAssociatedMixinType> & T;
return SbbFormAssociatedElement as unknown as AbstractConstructor<SbbFormAssociatedMixinType<V>> &
T;
};

/**
Expand Down
3 changes: 2 additions & 1 deletion src/elements/core/mixins/required-mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export declare class SbbRequiredMixinType {
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const SbbRequiredMixin = <
T extends AbstractConstructor<LitElement & SbbFormAssociatedMixinType>,
T extends AbstractConstructor<LitElement & SbbFormAssociatedMixinType<V>>,
V,
>(
superClass: T,
): AbstractConstructor<SbbRequiredMixinType> & T => {
Expand Down
25 changes: 15 additions & 10 deletions src/elements/select/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,25 @@ Opened panel:
| <kbd>Shift</kbd><kbd>Up Arrow</kbd> | If `multiple`, moves to the next non-disabled option and toggle its selection. |
| Any char or number | If exists, select the first non-disabled matching option after the selected value. |

<!-- Override
@type value => string \| string[] \| null
-->
<!-- Auto Generated Below -->

## Properties

| Name | Attribute | Privacy | Type | Default | Description |
| ------------- | ------------- | ------- | --------------------------------- | ------- | ------------------------------------------------------------------------ |
| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. |
| `isOpen` | - | public | `boolean` | | Whether the element is open. |
| `multiple` | `multiple` | public | `boolean` | `false` | Whether the select allows for multiple selection. |
| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. |
| `placeholder` | `placeholder` | public | `string \| undefined` | | The placeholder used if no value has been selected. |
| `readonly` | `readonly` | public | `boolean` | `false` | Whether the select is readonly. |
| `required` | `required` | public | `boolean` | `false` | Whether the select is required. |
| `value` | `value` | public | `string \| string[] \| undefined` | | The value of the select component. If `multiple` is true, it's an array. |
| Name | Attribute | Privacy | Type | Default | Description |
| ------------- | ------------- | ------- | ---------------------------- | ------- | -------------------------------------------------------------- |
| `disabled` | `disabled` | public | `boolean` | `false` | Whether the component is disabled. |
| `form` | - | public | `HTMLFormElement \| null` | | Returns the form owner of the internals of the target element. |
| `isOpen` | - | public | `boolean` | | Whether the element is open. |
| `multiple` | `multiple` | public | `boolean` | `false` | Whether the select allows for multiple selection. |
| `name` | `name` | public | `string` | | Name of the form element. Will be read from name attribute. |
| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. |
| `placeholder` | `placeholder` | public | `string \| undefined` | | The placeholder used if no value has been selected. |
| `readonly` | `readonly` | public | `boolean` | `false` | Whether the select is readonly. |
| `required` | `required` | public | `boolean` | `false` | Whether the component is required. |
| `value` | `value` | public | `string \| string[] \| null` | `null` | Value of the form element. |

## Methods

Expand Down
Loading

0 comments on commit b9156ab

Please sign in to comment.