diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index 11ab6a1c51..22c00aeba7 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -646,6 +646,99 @@ export namespace Components { */ "size": BalProps.BalCloseSize; } + interface BalCombobox { + /** + * Indicates whether the value of the control can be automatically completed by the browser. + */ + "autocomplete": BalProps.BalInputAutocomplete; + /** + * If `true`, the selected options are shown as chips + */ + "chips": boolean; + /** + * Sets the value to `[]`, the input value to ´''´ and the focus index to ´0´. + */ + "clear": () => Promise; + /** + * If `true`, a cross at the end is visible to clear the selection + */ + "clearable": boolean; + /** + * Closes the popup with option list + */ + "close": () => Promise; + "configChanged": (state: BalConfigState) => Promise; + /** + * Defines the max height of the list element + */ + "contentHeight": number; + /** + * If `true`, the user cannot interact with the option. + */ + "disabled": boolean; + /** + * Defines the filter logic of the list + */ + "filter": BalProps.BalOptionListFilter; + /** + * Returns the value of the component + */ + "getValue": () => Promise; + /** + * If `true` there will be on trigger icon visible + */ + "icon": string; + /** + * If `true`, the component will be shown as invalid + */ + "invalid": boolean; + "inverted": boolean; + /** + * Defines if the select is in a loading state. + */ + "loading": boolean; + /** + * If `true`, the user can select multiple options. + */ + "multiple": boolean; + /** + * The name of the control, which is submitted with the form data. + */ + "name": string; + /** + * Opens the popup with option list + */ + "open": () => Promise; + /** + * Steps can be passed as a property or through HTML markup. + */ + "options": BalOption[]; + /** + * Defines the placeholder of the component. Only shown when the value is empty + */ + "placeholder": string; + /** + * If `true` the element can not mutated, meaning the user can not edit the control. + */ + "readonly": boolean; + /** + * If `true`, the user must fill in a value before submitting a form. + */ + "required": boolean; + /** + * Select option by passed value + */ + "select": (newValue: string | string[]) => Promise; + "setAriaForm": (ariaForm: BalAriaForm) => Promise; + /** + * Sets the focus on the input element + */ + "setFocus": () => Promise; + /** + * The value of the selected options. + */ + "value"?: string | string[]; + } interface BalContent { /** * Defines the text positioning like center, end or default to start. @@ -3598,6 +3691,10 @@ export interface BalCheckboxGroupCustomEvent extends CustomEvent { detail: T; target: HTMLBalCheckboxGroupElement; } +export interface BalComboboxCustomEvent extends CustomEvent { + detail: T; + target: HTMLBalComboboxElement; +} export interface BalDataValueCustomEvent extends CustomEvent { detail: T; target: HTMLBalDataValueElement; @@ -3957,6 +4054,25 @@ declare global { prototype: HTMLBalCloseElement; new (): HTMLBalCloseElement; }; + interface HTMLBalComboboxElementEventMap { + "balChange": BalEvents.BalDropdownChangeDetail; + "balFocus": BalEvents.BalDropdownFocusDetail; + "balBlur": BalEvents.BalDropdownBlurDetail; + } + interface HTMLBalComboboxElement extends Components.BalCombobox, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLBalComboboxElement, ev: BalComboboxCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLBalComboboxElement, ev: BalComboboxCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLBalComboboxElement: { + prototype: HTMLBalComboboxElement; + new (): HTMLBalComboboxElement; + }; interface HTMLBalContentElement extends Components.BalContent, HTMLStencilElement { } var HTMLBalContentElement: { @@ -5060,6 +5176,7 @@ declare global { "bal-checkbox-button": HTMLBalCheckboxButtonElement; "bal-checkbox-group": HTMLBalCheckboxGroupElement; "bal-close": HTMLBalCloseElement; + "bal-combobox": HTMLBalComboboxElement; "bal-content": HTMLBalContentElement; "bal-data": HTMLBalDataElement; "bal-data-item": HTMLBalDataItemElement; @@ -5804,6 +5921,85 @@ declare namespace LocalJSX { */ "size"?: BalProps.BalCloseSize; } + interface BalCombobox { + /** + * Indicates whether the value of the control can be automatically completed by the browser. + */ + "autocomplete"?: BalProps.BalInputAutocomplete; + /** + * If `true`, the selected options are shown as chips + */ + "chips"?: boolean; + /** + * If `true`, a cross at the end is visible to clear the selection + */ + "clearable"?: boolean; + /** + * Defines the max height of the list element + */ + "contentHeight"?: number; + /** + * If `true`, the user cannot interact with the option. + */ + "disabled"?: boolean; + /** + * Defines the filter logic of the list + */ + "filter"?: BalProps.BalOptionListFilter; + /** + * If `true` there will be on trigger icon visible + */ + "icon"?: string; + /** + * If `true`, the component will be shown as invalid + */ + "invalid"?: boolean; + "inverted"?: boolean; + /** + * Defines if the select is in a loading state. + */ + "loading"?: boolean; + /** + * If `true`, the user can select multiple options. + */ + "multiple"?: boolean; + /** + * The name of the control, which is submitted with the form data. + */ + "name"?: string; + /** + * Emitted when the input loses focus. + */ + "onBalBlur"?: (event: BalComboboxCustomEvent) => void; + /** + * Emitted when a option got selected. + */ + "onBalChange"?: (event: BalComboboxCustomEvent) => void; + /** + * Emitted when the input has focus. + */ + "onBalFocus"?: (event: BalComboboxCustomEvent) => void; + /** + * Steps can be passed as a property or through HTML markup. + */ + "options"?: BalOption[]; + /** + * Defines the placeholder of the component. Only shown when the value is empty + */ + "placeholder"?: string; + /** + * If `true` the element can not mutated, meaning the user can not edit the control. + */ + "readonly"?: boolean; + /** + * If `true`, the user must fill in a value before submitting a form. + */ + "required"?: boolean; + /** + * The value of the selected options. + */ + "value"?: string | string[]; + } interface BalContent { /** * Defines the text positioning like center, end or default to start. @@ -8775,6 +8971,7 @@ declare namespace LocalJSX { "bal-checkbox-button": BalCheckboxButton; "bal-checkbox-group": BalCheckboxGroup; "bal-close": BalClose; + "bal-combobox": BalCombobox; "bal-content": BalContent; "bal-data": BalData; "bal-data-item": BalDataItem; @@ -8899,6 +9096,7 @@ declare module "@stencil/core" { "bal-checkbox-button": LocalJSX.BalCheckboxButton & JSXBase.HTMLAttributes; "bal-checkbox-group": LocalJSX.BalCheckboxGroup & JSXBase.HTMLAttributes; "bal-close": LocalJSX.BalClose & JSXBase.HTMLAttributes; + "bal-combobox": LocalJSX.BalCombobox & JSXBase.HTMLAttributes; "bal-content": LocalJSX.BalContent & JSXBase.HTMLAttributes; "bal-data": LocalJSX.BalData & JSXBase.HTMLAttributes; "bal-data-item": LocalJSX.BalDataItem & JSXBase.HTMLAttributes; diff --git a/packages/core/src/components/bal-combobox/bal-combobox.interfaces.ts b/packages/core/src/components/bal-combobox/bal-combobox.interfaces.ts new file mode 100644 index 0000000000..726e47feb4 --- /dev/null +++ b/packages/core/src/components/bal-combobox/bal-combobox.interfaces.ts @@ -0,0 +1,22 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +// eslint-disable-next-line @typescript-eslint/triple-slash-reference +/// + +namespace BalProps {} + +namespace BalEvents { + export interface BalComboboxCustomEvent extends CustomEvent { + detail: T + target: HTMLBalComboboxElement + } + + export type BalComboboxChangeDetail = string | string[] + export type BalComboboxChange = BalComboboxCustomEvent + + export type BalComboboxBlurDetail = FocusEvent + export type BalComboboxBlur = BalComboboxCustomEvent + + export type BalComboboxFocusDetail = FocusEvent + export type BalComboboxFocus = BalComboboxCustomEvent +} diff --git a/packages/core/src/components/bal-combobox/bal-combobox.sass b/packages/core/src/components/bal-combobox/bal-combobox.sass new file mode 100644 index 0000000000..2aabd0a9c8 --- /dev/null +++ b/packages/core/src/components/bal-combobox/bal-combobox.sass @@ -0,0 +1,149 @@ +@import '@baloise/ds-styles/sass/mixins' +@import './bal-combobox.vars' + +// Dropdown +// -------------------------------------------------- +.bal-combobox + display: block + position: relative + flex: 1 + +// Root interactive element +// -------------------------------------------------- +.bal-combobox__root + border-width: 0.125rem + border-style: solid + border-color: var(--bal-combobox-control-border-color) + border-radius: var(--bal-combobox-control-border-radius) + background: var(--bal-combobox-control-background) + font-size: var(--bal-weight-regular) + font-family: var(--bal-font-family-text) + color: var(--bal-combobox-control-text-color) + outline: none + box-shadow: var(--bal-shadow-none) + padding-right: 1rem + height: auto + min-height: 3rem + text-overflow: ellipsis + white-space: nowrap + overflow: hidden + display: flex + width: 100% + text-align: left + gap: .5rem + padding-left: calc(0.75em - var(--bal-border-width-normal)) + padding-right: calc(0.75em - var(--bal-border-width-normal)) + justify-content: center + align-items: center + &--autofill + background: rgb(250, 255, 189) !important + background: light-dark(rgb(232, 240, 254), rgba(70, 90, 126, 0.4)) !important + & > span + flex: 1 + hyphens: auto + overflow: hidden + text-overflow: ellipsis + white-space: nowrap + &--focused + border-color: var(--bal-combobox-control-border-color-focused) + &--invalid + border-color: var(--bal-combobox-control-border-color-invalid) + background: var(--bal-combobox-control-background-invalid) + &--disabled + border-color: var(--bal-combobox-control-border-color-disabled) + background: var(--bal-combobox-control-background-disabled) + &__content + &--placeholder + color: var(--bal-form-field-control-placeholder-color) + &--disabled + color: var(--bal-combobox-input-text-color-disabled) + +.bal-combobox__root__content__chips + --bal-tag-size-small-font-size: var(--bal-text-size-normal) + display: flex + flex-wrap: wrap + gap: var(--bal-space-xx-small) + padding-top: var(--bal-space-x-small) + padding-bottom: var(--bal-space-x-small) + & > bal-tag + z-index: 1 + +// Native form controls +// -------------------------------------------------- + +.bal-combobox__root__input + appearance: none + border: none + background: transparent + position: absolute + right: var(--bal-border-width-normal) + left: var(--bal-border-width-normal) + top: var(--bal-border-width-normal) + bottom: var(--bal-border-width-normal) + opacity: 0 + padding-left: calc(.75em - var(--bal-border-width-normal)) + padding-right: calc(.75em - var(--bal-border-width-normal)) + cursor: pointer + &:disabled + cursor: default + &:autofill + opacity: 1 + &:-internal-autofill-selected + opacity: 0 + +.bal-combobox__root__select + +hidden-input + +.bal-combobox__root--autofill .bal-combobox__root__input + opacity: 0 + +// Dropdown list panel +// -------------------------------------------------- + +.bal-combobox__list + display: block + visibility: hidden + opacity: 0 + min-width: 100% + width: 100% + position: absolute + top: 0 + left: 0 + will-change: left, top, opacity + background: var(--bal-color-white) + border-radius: var(--bal-radius-normal) + box-shadow: var(--bal-shadow-normal) + z-index: var(--bal-z-index-popup) + &--expanded + visibility: visible + opacity: 1 + +// Dropdown native input element +// -------------------------------------------------- + +.bal-combobox__native + bottom: 0 + left: 0 + position: absolute + opacity: 0 + pointer-events: none + width: 100% + +// Dropdown clear button +// -------------------------------------------------- + +.bal-combobox__clear + background: transparent + border: none + padding: 0 + margin: 0 + display: flex + justify-content: center + align-items: center + cursor: pointer + +// Dropdown rear icon loading clear element +// -------------------------------------------------- + +.bal-combobox__rear + z-index: 1 diff --git a/packages/core/src/components/bal-combobox/bal-combobox.tsx b/packages/core/src/components/bal-combobox/bal-combobox.tsx new file mode 100644 index 0000000000..1c44900e26 --- /dev/null +++ b/packages/core/src/components/bal-combobox/bal-combobox.tsx @@ -0,0 +1,524 @@ +import { + Component, + h, + ComponentInterface, + Host, + Element, + Prop, + State, + Watch, + Listen, + Event, + EventEmitter, + Method, +} from '@stencil/core' +import { + areArraysEqual, + isArrowDownKey, + isArrowUpKey, + isEnterKey, + isEscapeKey, + isSpaceKey, +} from '@baloise/web-app-utils' +import { BEM } from '../../utils/bem' +import { LogInstance, Loggable, Logger } from '../../utils/log' +import { stopEventBubbling } from '../../utils/form-input' +import { Attributes, inheritAttributes } from '../../utils/attributes' +import { + BalOption, + DropdownEventsUtil, + DropdownFormSubmit, + DropdownFormSubmitUtil, + DropdownOptionUtil, + DropdownPopupUtil, + DropdownValueUtil, + DropdownOptionList, + DropdownFocus, + DropdownFocusUtil, + DropdownIcon, + DropdownNativeSelect, + DropdownInput, + DropdownValue, + DropdownAutoFillUtil, +} from '../../utils/dropdown' +import { + BalConfigObserver, + BalConfigState, + BalLanguage, + BalRegion, + ListenToConfig, + defaultConfig, +} from '../../utils/config' +import { BalAriaForm, BalAriaFormLinking, defaultBalAriaForm } from '../../utils/form' +import { waitAfterIdleCallback } from '../../utils/helpers' + +@Component({ + tag: 'bal-combobox', + styleUrl: 'bal-combobox.sass', +}) +export class Combobox + implements ComponentInterface, Loggable, BalConfigObserver, BalAriaFormLinking, DropdownFormSubmit, DropdownFocus +{ + @Element() el!: HTMLElement + panelEl: HTMLDivElement | undefined + listEl: HTMLBalOptionListElement | undefined + nativeEl: HTMLInputElement | undefined + selectEl: HTMLSelectElement | undefined + + inputId = `bal-combobox-${balComboboxIds++}` + inheritedAttributes: Attributes = {} + initialValue?: string | string[] = [] + nativeOptions: string[] = [] + + @State() rawOptions: BalOption[] = [] + @State() choices: BalOption[] = [] + @State() rawValue: string[] = [] + @State() hasFocus = false + @State() isExpanded = false + @State() isAutoFilled = false + @State() inputLabel = '' + @State() ariaForm: BalAriaForm = defaultBalAriaForm + @State() language: BalLanguage = defaultConfig.language + @State() region: BalRegion = defaultConfig.region + @State() httpFormSubmit: boolean = defaultConfig.httpFormSubmit + @State() labelToFocus = '' + + valueUtil = new DropdownValueUtil() + eventsUtil = new DropdownEventsUtil() + popupUtil = new DropdownPopupUtil() + optionUtil = new DropdownOptionUtil() + formSubmitUtil = new DropdownFormSubmitUtil() + focusUtil = new DropdownFocusUtil() + autoFillUtil = new DropdownAutoFillUtil() + + log!: LogInstance + + @Logger('bal-combobox') + createLogger(log: LogInstance) { + this.log = log + } + + /** + * PUBLIC PROPERTY API + * ------------------------------------------------------ + */ + + /** + * Indicates whether the value of the control can be automatically completed by the browser. + */ + @Prop() autocomplete: BalProps.BalInputAutocomplete = 'off' + + /** + * The name of the control, which is submitted with the form data. + */ + @Prop() name: string = this.inputId + + /** + * Defines the placeholder of the component. Only shown when the value is empty + */ + @Prop() placeholder = '' + + /** + * If `true` there will be on trigger icon visible + */ + @Prop() icon = 'caret-down' + + /** + * If `true`, the user cannot interact with the option. + */ + @Prop() disabled = false + + /** + * If `true` the element can not mutated, meaning the user can not edit the control. + */ + @Prop() readonly = false + + /** + * If `true`, the user can select multiple options. + */ + @Prop() multiple = false + + /** + * If `true`, the selected options are shown as chips + */ + @Prop() chips = false + + /** + * If `true`, a cross at the end is visible to clear the selection + */ + @Prop() clearable = false + + /** + * If `true`, the component will be shown as invalid + */ + @Prop() invalid = false + + /** + * If `true`, the user must fill in a value before submitting a form. + */ + @Prop() required = false + + /** + * Defines if the select is in a loading state. + */ + @Prop() loading = false + + /** + * Defines the filter logic of the list + */ + @Prop() filter: BalProps.BalOptionListFilter = 'includes' + + /** + * Defines the max height of the list element + */ + @Prop() contentHeight = 262 + + /** + * @internal + * Set this to `true` when the component is placed on a dark background. + */ + @Prop() inverted = false + + /** + * Steps can be passed as a property or through HTML markup. + */ + @Prop() options: BalOption[] = [] + @Watch('options') + protected async optionChanged() { + this.optionUtil.optionChanged() + } + + /** + * The value of the selected options. + */ + @Prop() value?: string | string[] = [] + @Watch('value') + valueChanged(newValue: string | string[] | undefined, oldValue: string | string[] | undefined) { + this.valueUtil.valueChanged(newValue, oldValue) + } + + /** + * Emitted when a option got selected. + */ + @Event() balChange!: EventEmitter + + /** + * Emitted when the input has focus. + */ + @Event() balFocus!: EventEmitter + + /** + * Emitted when the input loses focus. + */ + @Event() balBlur!: EventEmitter + + /** + * LIFECYCLE + * ------------------------------------------------------ + */ + + connectedCallback(): void { + this.eventsUtil.connectedCallback(this) + this.valueUtil.connectedCallback(this) + this.popupUtil.connectedCallback(this) + this.optionUtil.connectedCallback(this) + this.formSubmitUtil.connectedCallback(this) + this.focusUtil.connectedCallback(this) + this.autoFillUtil.connectedCallback(this) + } + + async componentWillRender() { + this.inheritedAttributes = inheritAttributes(this.el, ['tabindex']) + await this.optionUtil.componentWillRender() + } + + componentDidRender() { + this.formSubmitUtil.componentDidRender() + } + + componentDidLoad(): void { + this.valueUtil.componentDidLoad() + } + + /** + * LISTENERS + * ------------------------------------------------------ + */ + + /** + * @internal define config for the component + */ + @Method() + @ListenToConfig() + async configChanged(state: BalConfigState): Promise { + this.language = state.language + this.region = state.region + this.httpFormSubmit = state.httpFormSubmit + } + + @Listen('balOptionChange') + async listenToOptionChange(ev: BalEvents.BalOptionChange) { + this.optionUtil.listenToOptionChange(ev) + } + + @Listen('click', { target: 'document' }) + listenOnClickOutside(ev: UIEvent) { + this.eventsUtil.handleOutsideClick(ev) + } + + @Listen('reset', { capture: true, target: 'document' }) + resetHandler(ev: UIEvent) { + this.formSubmitUtil.handle(ev) + } + + /** + * PUBLIC METHODS + * ------------------------------------------------------ + */ + + /** + * Sets the focus on the input element + */ + @Method() + async setFocus() { + if (this.nativeEl && !this.valueUtil.isDisabled()) { + await waitAfterIdleCallback() + this.nativeEl.focus() + } + } + + /** + * Returns the value of the component + */ + @Method() + async getValue() { + return this.rawValue + } + + /** + * Sets the value to `[]`, the input value to ´''´ and the focus index to ´0´. + */ + @Method() + async clear() { + this.valueUtil.updateRawValueBySelection([]) + } + + /** + * Opens the popup with option list + */ + @Method() + async open(): Promise { + if (!this.valueUtil.isDisabled() && this.panelEl) { + await this.popupUtil.expandList() + } + } + + /** + * Closes the popup with option list + */ + @Method() + async close(): Promise { + if (!this.valueUtil.isDisabled() && this.panelEl) { + await this.popupUtil.collapseList() + } + } + + /** + * Select option by passed value + */ + @Method() + async select(newValue: string | string[]): Promise { + const parsedNewValue = this.valueUtil.parseValueString(newValue) + this.valueUtil.updateRawValueBySelection(parsedNewValue) + } + + /** + * @internal + */ + @Method() + async setAriaForm(ariaForm: BalAriaForm): Promise { + this.ariaForm = { ...ariaForm } + } + + /** + * EVENT BINDING + * ------------------------------------------------------ + */ + handleAutoFill = async (ev: Event) => { + this.log('(handleAutoFill)', ev, this.nativeEl.value) + this.autoFillUtil.handleAutoFill(ev) + } + + handleKeyDown = (ev: KeyboardEvent) => { + if (ev && ev.key) { + if (this.isExpanded) { + /** + * ⬇️ Arrow up key + */ + if (isArrowDownKey(ev)) { + stopEventBubbling(ev) + this.listEl?.focusNext() + /** + * ⬆️ Arrow up key + */ + } else if (isArrowUpKey(ev)) { + stopEventBubbling(ev) + this.listEl?.focusPrevious() + /** + * Go to top of the list + */ + } else if (ev.key === 'Home' || ev.key === 'PageUp') { + stopEventBubbling(ev) + this.listEl?.focusFirst() + /** + * Go to bottom of the list + */ + } else if (ev.key === 'End' || ev.key === 'PageDown') { + stopEventBubbling(ev) + this.listEl?.focusLast() + /** + * Select focused option + */ + } else if (isEnterKey(ev)) { + stopEventBubbling(ev) + this.listEl?.selectByFocus() + /** + * Close list + */ + } else if (ev.key === 'Tab' || isEscapeKey(ev)) { + this.popupUtil.collapseList() + /** + * Focus on label + */ + } else if (ev.key.length === 1) { + this.focusUtil.focusOptionByLabel(ev.key) + } + } else { + /** + * Open list + */ + if (isEnterKey(ev) || isSpaceKey(ev)) { + stopEventBubbling(ev) + this.popupUtil.expandList() + /** + * Focus on label + */ + } else if (ev.key.length === 1) { + this.focusUtil.focusOptionByLabel(ev.key, { select: true }) + } + } + } else { + // Close the popup on autofill + if (this.isExpanded) { + this.popupUtil.collapseList() + } + } + } + + /** + * RENDER + * ------------------------------------------------------ + */ + + render() { + const block = BEM.block('combobox') + + return ( + +
this.eventsUtil.handleClick(ev)} + > + + this.valueUtil.removeOption(option)} + > + + (this.nativeEl = el)} + onChange={ev => this.handleAutoFill(ev)} + onFocus={ev => this.eventsUtil.handleFocus(ev)} + onBlur={ev => this.eventsUtil.handleBlur(ev)} + onKeyDown={ev => this.handleKeyDown(ev)} + > + (this.selectEl = el)} + > + +
+ (this.panelEl = el)} + refListEl={el => (this.listEl = el)} + > + + +
+ ) + } +} + +let balComboboxIds = 0 diff --git a/packages/core/src/components/bal-combobox/bal-combobox.vars.sass b/packages/core/src/components/bal-combobox/bal-combobox.vars.sass new file mode 100644 index 0000000000..eb8418e688 --- /dev/null +++ b/packages/core/src/components/bal-combobox/bal-combobox.vars.sass @@ -0,0 +1,74 @@ +/** + * @prop --bal-combobox-control-background: tbd + * @prop --bal-combobox-control-background-hover: tbd + * @prop --bal-combobox-control-background-invalid: tbd + * @prop --bal-combobox-control-background-disabled: tbd + * @prop --bal-combobox-control-input-background: tbd + * @prop --bal-combobox-control-native-input-background: tbd + * @prop --bal-combobox-control-native-input-background-hover: tbd + * @prop --bal-combobox-control-input-inverted-footer-background: tbd + * @prop --bal-combobox-control-input-inverted-footer-background-hover: tbd + * @prop --bal-combobox-control-input-multiple-background: tbd + * @prop --bal-combobox-control-input-multiple-background-read-only-selection: tbd + * @prop --bal-combobox-control-input-option-background: tbd + * @prop --bal-combobox-control-input-option-background-selected: tbd + * @prop --bal-combobox-control-input-option-background-focused: tbd + * @prop --bal-combobox-control-input-option-background-hover: tbd + * + * @prop --bal-combobox-control-border-radius: tbd + * + * @prop --bal-combobox-popover-border-color: tbd + * @prop --bal-combobox-control-border-color: tbd + * @prop --bal-combobox-control-border-color-focused: tbd + * @prop --bal-combobox-control-border-color-hover: tbd + * @prop --bal-combobox-control-border-color-invalid: tbd + * @prop --bal-combobox-control-border-color-disabled: tbd + * @prop --bal-combobox-control-border-color-focus-within: tbd + * @prop --bal-combobox-option-border-top-color: tbd + * + * @prop --bal-combobox-popover-empty-text-color: tbd + * @prop --bal-combobox-control-text-color: tbd + * @prop --bal-combobox-control-text-color-focused: tbd + * @prop --bal-combobox-input-text-color-disabled: tbd + * @prop --bal-combobox-control-inverted-footer-native-input-text-color: tbd + * @prop --bal-combobox-option-content-label-text-color: tbd + */ + +:root + // + // background colors + --bal-combobox-control-background: var(--bal-color-white) + --bal-combobox-control-background-hover: var(--bal-form-field-control-background-hover) + --bal-combobox-control-background-invalid: var(--bal-form-field-control-danger-background) + --bal-combobox-control-background-disabled: var(--bal-form-field-control-disabled-background) + --bal-combobox-control-input-background: var(--bal-color-grey-1) + --bal-combobox-control-native-input-background: transparent + --bal-combobox-control-native-input-background-hover: transparent + --bal-combobox-control-input-inverted-footer-background: transparent + --bal-combobox-control-input-inverted-footer-background-hover: transparent + --bal-combobox-control-input-multiple-background: transparent + --bal-combobox-control-input-multiple-background-read-only-selection: transparent + --bal-combobox-control-input-option-background: transparent + --bal-combobox-control-input-option-background-selected: var(--bal-color-primary-1) + --bal-combobox-control-input-option-background-focused: var(--bal-color-grey-2) + --bal-combobox-control-input-option-background-hover: var(--bal-color-grey-2) + // + // border radius + --bal-combobox-control-border-radius: var(--bal-form-field-control-radius) + // border colors + --bal-combobox-popover-border-color: var(--bal-color-grey-2) + --bal-combobox-control-border-color: var(--bal-form-field-control-border-color) + --bal-combobox-control-border-color-focused: var(--bal-color-primary) + --bal-combobox-control-border-color-hover: var(--bal-form-field-control-border-color-hover) + --bal-combobox-control-border-color-invalid: var(--bal-form-field-control-danger-border-color) + --bal-combobox-control-border-color-disabled: var(--bal-form-field-control-disabled-border-color) + --bal-combobox-control-border-color-focus-within: var(--bal-color-primary) + --bal-combobox-option-border-top-color: var(--bal-color-grey-2) + // + // text colors + --bal-combobox-popover-empty-text-color: var(--bal-form-field-control-color) + --bal-combobox-control-text-color: var(--bal-form-field-control-color) + --bal-combobox-control-text-color-focused: var(--bal-color-primary) + --bal-combobox-input-text-color-disabled: var(--bal-form-field-label-disabled-color) + --bal-combobox-control-inverted-footer-native-input-text-color: var(--bal-color-text-white) + --bal-combobox-option-content-label-text-color: var(--bal-form-field-control-color) diff --git a/packages/core/src/components/bal-combobox/test/bal-combobox.a11y.html b/packages/core/src/components/bal-combobox/test/bal-combobox.a11y.html new file mode 100644 index 0000000000..7f6642b5cd --- /dev/null +++ b/packages/core/src/components/bal-combobox/test/bal-combobox.a11y.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + +
+ + + + Year + + + 1988 + 1989 + 1990 + 1991 + 1992 + + + + + +
+
+ + diff --git a/packages/core/src/components/bal-combobox/test/bal-combobox.auto-fill.html b/packages/core/src/components/bal-combobox/test/bal-combobox.auto-fill.html new file mode 100644 index 0000000000..83822758b1 --- /dev/null +++ b/packages/core/src/components/bal-combobox/test/bal-combobox.auto-fill.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + +
+

Autocomplete

+
+
+ + + + First Name + + + + + + + + Last Name + + + + + + + + Country + + + Switzerland + Germany + Italy + + + + + + + +
+
+
+
+ + diff --git a/packages/core/src/components/bal-combobox/test/bal-combobox.cy.html b/packages/core/src/components/bal-combobox/test/bal-combobox.cy.html new file mode 100644 index 0000000000..4448391c58 --- /dev/null +++ b/packages/core/src/components/bal-combobox/test/bal-combobox.cy.html @@ -0,0 +1,193 @@ + + + + + + + + + + + + + +
+

Basic

+
+ + 1995 + 1996 + 1997 + 1998 + 1999 + 2000 + + + + 1988 + 1989 + 1990 + 1991 + 1992 + 1993 + 1994 + 1995 + 1996 + 1997 + 1998 + 1999 + 2000 + 2001 + 2002 + 2003 + 2004 + 2005 + 2006 + 2007 + +
+

Required

+
+ + 1995 + 1996 + 1997 + 1998 + 1999 + 2000 + +
+

Multiple

+
+ + + Black Widow + S.H.I.E.L.D. + + + Black Panter + Wakanda + + + Iron Man + Malibu + + + Spider Man + Queens + + + Captain America + Broklyn + + + Thor God of Thunder + Asgard + + +
+

Form Submit

+
+
+ + + + Country + + + + Black Widow + S.H.I.E.L.D. + + + Black Panter + Wakanda + + + Iron Man + Malibu + + + Spider Man + Queens + + + Captain America + Broklyn + + + Thor God of Thunder + Asgard + + + + + + + + Country + + + Switzerland + Germany + Italy + + + + + + + +
+
+

Autocomplete

+
+
+ + + + First Name + + + + + + + + Last Name + + + + + + + + Country + + + Switzerland + Germany + Italy + + + + + + + +
+
+
+
+ + diff --git a/packages/core/src/components/bal-combobox/test/bal-combobox.visual.html b/packages/core/src/components/bal-combobox/test/bal-combobox.visual.html new file mode 100644 index 0000000000..9e36dde17b --- /dev/null +++ b/packages/core/src/components/bal-combobox/test/bal-combobox.visual.html @@ -0,0 +1,211 @@ + + + + + + + + + + + + + +
+

Basic

+
+ + 1988 + 1989 + 1990 + 1991 + 1992 + 1993 + 1994 + 1995 + 1996 + 1997 + 1998 + 1999 + 2000 + 2001 + 2002 + 2003 + 2004 + 2005 + 2006 + 2007 + +
+

Long Content

+
+ + + Black Widow + Black Widow is a 2021 American superhero film based on Marvel Comics featuring the character of the + same name. + + + Black Panter + Wakanda + + + Iron Man + Malibu + + + Spider Man + Queens + + + Captain America + Broklyn + + + Thor God of Thunder + Asgard + + +
+

Multiple

+
+ + 1988 + 1989 + 1990 + 1991 + 1992 + 1993 + 1994 + 1995 + 1996 + 1997 + 1998 + 1999 + 2000 + 2001 + 2002 + 2003 + 2004 + 2005 + 2006 + 2007 + +
+

Multiple with Chips

+
+ + 1988 + 1989 + 1990 + 1991 + 1992 + 1993 + 1994 + 1995 + 1996 + 1997 + 1998 + 1999 + 2000 + 2001 + 2002 + 2003 + 2004 + 2005 + 2006 + 2007 + +
+

Clearable

+
+ + 1988 + 1989 + 1990 + 1991 + 1992 + +
+

Loading

+
+ + 1988 + 1989 + 1990 + 1991 + 1992 + +
+

Invalid

+
+ + 1988 + + + 1988 + + + 1988 + +
+

Disabled

+
+ + 1988 + + + 1988 + + + 1988 + +
+

Form Field

+
+ + Form Label + + + 1988 + 1989 + 1990 + 1991 + 1992 + 1993 + 1994 + 1995 + 1996 + 1997 + 1998 + 1999 + 2000 + 2001 + 2002 + 2003 + 2004 + 2005 + 2006 + 2007 + + + +
+
+
+ + diff --git a/packages/core/src/components/bal-dropdown/bal-dropdown.tsx b/packages/core/src/components/bal-dropdown/bal-dropdown.tsx index a86197f5a7..9320221558 100644 --- a/packages/core/src/components/bal-dropdown/bal-dropdown.tsx +++ b/packages/core/src/components/bal-dropdown/bal-dropdown.tsx @@ -437,6 +437,7 @@ export class Dropdown }} > this.handleKeyDown(ev)} > (this.selectEl = el)} > = ({ + blockName, name, httpFormSubmit, multiple, @@ -61,7 +63,7 @@ export const DropdownNativeSelect: FunctionalComponent { - const block = BEM.block('dropdown') + const block = BEM.block(blockName) return httpFormSubmit ? ( = ({ + blockName, inputId, isExpanded, rawOptions, @@ -30,7 +31,7 @@ export const DropdownOptionList: FunctionalComponent = refPanelEl, refListEl, }) => { - const block = BEM.block('dropdown') + const block = BEM.block(blockName) return (
= ({ + blockName, filled, chips, placeholder, @@ -127,7 +129,7 @@ export const DropdownValue: FunctionalComponent = ({ readonly, onRemoveChip, }) => { - const block = BEM.block('dropdown') + const block = BEM.block(blockName) if (filled) { if (chips) {