Skip to content

Commit

Permalink
fix(ld-select): prop forwarding
Browse files Browse the repository at this point in the history
  • Loading branch information
borisdiakur authored Nov 30, 2021
1 parent bfb5bb6 commit 9273538
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 58 deletions.
114 changes: 72 additions & 42 deletions src/liquid/components/ld-select/ld-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import Tether from 'tether'
import { LdSelectPopper } from './ld-select-popper/ld-select-popper'
import { LdOptionInternal } from './ld-option-internal/ld-option-internal'
import { getClassNames } from '../../utils/getClassNames'

type SelectOption = { value: string; text: string }

Expand All @@ -41,29 +42,31 @@ export class LdSelect implements InnerFocusable {
private popper: Tether
private observer: MutationObserver

/** Hint for form autofill feature. */
@Prop() autocomplete?: string

/**
* Used as trigger button label in multiselect mode
* and in single select mode if nothing is selected.
* This Boolean attribute lets you specify that a form control should have input focus when the page loads.
* Only one form element in a document can have the autofocus attribute.
*/
@Prop() placeholder: string

/** Used to specify the name of the control. */
@Prop() name: string

/** Multiselect mode. */
@Prop() multiple: boolean
@Prop() autofocus?: boolean

/** Disabled state of the component. */
@Prop() disabled: boolean

/**
* The form element to associate the select with (its form owner).
*/
@Prop() form?: string

/** Set this property to `true` in order to mark the select visually as invalid. */
@Prop() invalid: boolean

/**
* Prevents a state with no options selected after
* initial selection in single select mode.
* Constrains the height of the trigger button by replacing overflowing selection
* with a "+X more" indicator.
*/
@Prop() preventDeselection: boolean
@Prop({ mutable: true }) maxRows?: number

// prettier-ignore
/**
Expand All @@ -75,34 +78,51 @@ export class LdSelect implements InnerFocusable {
| 'inline' // = detached + minumum trigger button width
| 'ghost' // = inline + transparent background and borders

/** Size of the select trigger button. */
@Prop() size?: 'sm' | 'lg'
/** Multiselect mode. */
@Prop() multiple: boolean

/** Used to specify the name of the control. */
@Prop() name: string

/**
* Constrains the height of the trigger button by replacing overflowing selection
* with a "+X more" indicator.
* Used as trigger button label in multiselect mode
* and in single select mode if nothing is selected.
*/
@Prop({ mutable: true }) maxRows?: number
@Prop() placeholder: string

/** Attached as CSS class to the select popper element. */
@Prop() popperClass?: string

/**
* Prevents a state with no options selected after
* initial selection in single select mode.
*/
@Prop() preventDeselection: boolean

/** Size of the select trigger button. */
@Prop() size?: 'sm' | 'lg'

/**
* Stringified tether options object to be merged with the default options.
*/
@Prop({ mutable: true }) tetherOptions = '{}'

@State() initialized = false
/**
* A Boolean attribute indicating that an option with a non-empty string value must be selected.
*/
@Prop() required?: boolean

@State() ariaDisabled = false
@State() expanded = false
@State() hasCustomIcon = false
@State() hasMore = false
@State() initialized = false
@State() internalOptionsHTML: string
@State() renderHiddenInput = false
@State() selected: SelectOption[] = []
@State() theme: string
@State() ariaDisabled = false
@State() typeAheadQuery: string
@State() typeAheadTimeout: number
@State() internalOptionsHTML: string
@State() hasMore = false
@State() hasCustomIcon = false
@State() renderHiddenInput = false

@Watch('selected')
emitEventsAndUpdateHidden(
Expand Down Expand Up @@ -864,6 +884,10 @@ export class LdSelect implements InnerFocusable {
setTimeout(() => {
this.initObserver()
this.initialized = true

if (this.autofocus) {
this.focusInner()
}
})
}

Expand All @@ -889,22 +913,28 @@ export class LdSelect implements InnerFocusable {
// Disallow ghost in combination with multiple select mode.
const ghost = !this.multiple && this.mode === 'ghost'

let cl = 'ld-select'
if (this.size) cl += ` ld-select--${this.size}`
if (this.invalid) cl += ' ld-select--invalid'
if (this.expanded) cl += ' ld-select--expanded'
if (detached) cl += ' ld-select--detached'
if (inline) cl += ' ld-select--inline'
if (ghost) cl += ' ld-select--ghost'

let triggerCl = 'ld-select__btn-trigger'
if (this.invalid) triggerCl += ' ld-select__btn-trigger--invalid'
if (detached) triggerCl += ' ld-select__btn-trigger--detached'
if (inline) triggerCl += ' ld-select__btn-trigger--inline'
if (ghost) triggerCl += ' ld-select__btn-trigger--ghost'

let triggerIconCl = 'ld-select__icon'
if (this.expanded) triggerIconCl += ' ld-select__icon--rotated'
const cl = [
'ld-select',
this.size && `ld-select--${this.size}`,
this.invalid && 'ld-select--invalid',
this.expanded && 'ld-select--expanded',
detached && 'ld-select--detached',
inline && 'ld-select--inline',
ghost && 'ld-select--ghost',
]

const triggerCl = [
'ld-select__btn-trigger',
this.invalid && 'ld-select__btn-trigger--invalid',
detached && 'ld-select__btn-trigger--detached',
inline && 'ld-select__btn-trigger--inline',
ghost && 'ld-select__btn-trigger--ghost',
]

const triggerIconCl = [
'ld-select__icon',
this.expanded && 'ld-select__icon--rotated',
]

const triggerText = this.multiple
? this.placeholder
Expand All @@ -913,7 +943,7 @@ export class LdSelect implements InnerFocusable {
return (
<Host>
<div
class={cl}
class={getClassNames(cl)}
aria-disabled={this.disabled || this.ariaDisabled}
part="root"
onFocusout={this.handleFocusout.bind(this)}
Expand All @@ -935,7 +965,7 @@ export class LdSelect implements InnerFocusable {
ref={(el) => (this.selectRef = el as HTMLElement)}
>
<div
class={triggerCl}
class={getClassNames(triggerCl)}
role="button"
part="btn-trigger focusable"
tabindex={this.disabled && !this.ariaDisabled ? undefined : '0'}
Expand Down Expand Up @@ -1074,7 +1104,7 @@ export class LdSelect implements InnerFocusable {
<slot name="icon"></slot>
{!this.hasCustomIcon && (
<svg
class={triggerIconCl}
class={getClassNames(triggerIconCl)}
role={'presentation'}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
Expand Down
34 changes: 19 additions & 15 deletions src/liquid/components/ld-select/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -1226,21 +1226,25 @@ The `ld-select` Web Component provides a low level API for integrating it with t

## Properties

| Property | Attribute | Description | Type | Default |
| -------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------- | ----------- |
| `disabled` | `disabled` | Disabled state of the component. | `boolean` | `undefined` |
| `invalid` | `invalid` | Set this property to `true` in order to mark the select visually as invalid. | `boolean` | `undefined` |
| `key` | `key` | for tracking the node's identity when working with lists | `string \| number` | `undefined` |
| `maxRows` | `max-rows` | Constrains the height of the trigger button by replacing overflowing selection with a "+X more" indicator. | `number` | `undefined` |
| `mode` | `mode` | Display mode. | `"detached" \| "ghost" \| "inline"` | `undefined` |
| `multiple` | `multiple` | Multiselect mode. | `boolean` | `undefined` |
| `name` | `name` | Used to specify the name of the control. | `string` | `undefined` |
| `placeholder` | `placeholder` | Used as trigger button label in multiselect mode and in single select mode if nothing is selected. | `string` | `undefined` |
| `popperClass` | `popper-class` | Attached as CSS class to the select popper element. | `string` | `undefined` |
| `preventDeselection` | `prevent-deselection` | Prevents a state with no options selected after initial selection in single select mode. | `boolean` | `undefined` |
| `ref` | `ref` | reference to component | `any` | `undefined` |
| `size` | `size` | Size of the select trigger button. | `"lg" \| "sm"` | `undefined` |
| `tetherOptions` | `tether-options` | Stringified tether options object to be merged with the default options. | `string` | `'{}'` |
| Property | Attribute | Description | Type | Default |
| -------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------- | ----------- |
| `autocomplete` | `autocomplete` | Hint for form autofill feature. | `string` | `undefined` |
| `autofocus` | `autofocus` | This Boolean attribute lets you specify that a form control should have input focus when the page loads. Only one form element in a document can have the autofocus attribute. | `boolean` | `undefined` |
| `disabled` | `disabled` | Disabled state of the component. | `boolean` | `undefined` |
| `form` | `form` | The form element to associate the select with (its form owner). | `string` | `undefined` |
| `invalid` | `invalid` | Set this property to `true` in order to mark the select visually as invalid. | `boolean` | `undefined` |
| `key` | `key` | for tracking the node's identity when working with lists | `string \| number` | `undefined` |
| `maxRows` | `max-rows` | Constrains the height of the trigger button by replacing overflowing selection with a "+X more" indicator. | `number` | `undefined` |
| `mode` | `mode` | Display mode. | `"detached" \| "ghost" \| "inline"` | `undefined` |
| `multiple` | `multiple` | Multiselect mode. | `boolean` | `undefined` |
| `name` | `name` | Used to specify the name of the control. | `string` | `undefined` |
| `placeholder` | `placeholder` | Used as trigger button label in multiselect mode and in single select mode if nothing is selected. | `string` | `undefined` |
| `popperClass` | `popper-class` | Attached as CSS class to the select popper element. | `string` | `undefined` |
| `preventDeselection` | `prevent-deselection` | Prevents a state with no options selected after initial selection in single select mode. | `boolean` | `undefined` |
| `ref` | `ref` | reference to component | `any` | `undefined` |
| `required` | `required` | A Boolean attribute indicating that an option with a non-empty string value must be selected. | `boolean` | `undefined` |
| `size` | `size` | Size of the select trigger button. | `"lg" \| "sm"` | `undefined` |
| `tetherOptions` | `tether-options` | Stringified tether options object to be merged with the default options. | `string` | `'{}'` |


## Events
Expand Down
24 changes: 23 additions & 1 deletion src/liquid/components/ld-select/test/ld-select.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2003,9 +2003,31 @@ describe('ld-select', () => {
const btnTrigger = ldSelect.shadowRoot.querySelector(
'.ld-select__btn-trigger'
)
;(btnTrigger as HTMLElement).focus = jest.fn(focusManager.focus)
;(btnTrigger as HTMLElement).focus = jest.fn()

await page.root.focusInner()
expect((btnTrigger as HTMLElement).focus).toHaveBeenCalledTimes(1)
})

it('auto-focuses', async () => {
const page = await newSpecPage({
components,
html: `
<ld-select autofocus placeholder="Pick a fruit" name="fruit" popper-class="ld-select__popper--fruits">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
</ld-select>
`,
})

const ldSelect = page.root
const btnTrigger = ldSelect.shadowRoot.querySelector(
'.ld-select__btn-trigger'
)
;(btnTrigger as HTMLElement).focus = jest.fn()

await page.waitForChanges()
jest.advanceTimersByTime(0)
expect((btnTrigger as HTMLElement).focus).toHaveBeenCalledTimes(1)
})
})

0 comments on commit 9273538

Please sign in to comment.