Skip to content

Commit

Permalink
fix(ld-select): events
Browse files Browse the repository at this point in the history
  • Loading branch information
borisdiakur committed Jul 20, 2021
1 parent bb091ef commit 730e755
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 13 deletions.
3 changes: 1 addition & 2 deletions src/liquid/components/ld-checkbox/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,6 @@ The `ld-checkbox` Web Component provides a low level API for integrating the com
<ld-button>Submit</ld-button>
</form>
<script>
const form = document.getElementById('example-form')
const termsConfirmation = document.querySelector('#example-form ld-label:first-of-type ld-checkbox')
const termsConfirmationErrorMessage = document.querySelector('#example-form ld-label:first-of-type ld-input-message')
const submitButton = document.querySelector('#example-form ld-button')
Expand Down Expand Up @@ -577,7 +576,7 @@ The `ld-checkbox` Web Component provides a low level API for integrating the com
} else {
window.alert('Form is invalid.')
}
}, 10)
}, 100)
})
</script>
{% endexample %}
Expand Down
3 changes: 1 addition & 2 deletions src/liquid/components/ld-input/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,6 @@ The `ld-input` Web Component does not provide any properties or methods for vali
<ld-button>Submit</ld-button>
</form>
<script>
const form = document.getElementById('example-form')
const username = document.querySelector('#example-form ld-label:first-of-type ld-input')
const usernameErrorMessage = document.querySelector('#example-form ld-label:first-of-type ld-input-message')
const password = document.querySelector('#example-form ld-label:last-of-type ld-input')
Expand Down Expand Up @@ -1048,7 +1047,7 @@ The `ld-input` Web Component does not provide any properties or methods for vali
} else {
window.alert('Form is invalid.')
}
}, 10)
}, 100)
})
</script>
{% endexample %}
Expand Down
3 changes: 1 addition & 2 deletions src/liquid/components/ld-radio/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,6 @@ The `ld-radio` Web Component provides a low level API for integrating the compon
<ld-button>Submit</ld-button>
</form>
<script>
const form = document.getElementById('example-form')
const orangeRadio = document.querySelector('#example-form ld-label:first-of-type ld-radio')
const bananaRadio = document.querySelector('#example-form ld-label:last-of-type ld-radio')
const orangeMessage = document.querySelector('#example-form ld-label:first-of-type ld-input-message')
Expand Down Expand Up @@ -350,7 +349,7 @@ The `ld-radio` Web Component provides a low level API for integrating the compon
} else {
window.alert('Form is invalid.')
}
}, 10)
}, 100)
})
</script>
{% endexample %}
Expand Down
29 changes: 22 additions & 7 deletions src/liquid/components/ld-select/ld-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ export class LdSelect {
*/
@Event() input: EventEmitter<string[]>

/**
* Emitted with an array of selected values when the select component gets focus.
*/
@Event({ bubbles: false }) focus: EventEmitter<string[]>

/**
* Emitted with an array of selected values when the select component looses focus.
*/
Expand Down Expand Up @@ -694,7 +699,6 @@ export class LdSelect {

@Listen('click', {
target: 'window',
passive: true,
})
handleClickOutside(ev) {
if (
Expand All @@ -703,6 +707,11 @@ export class LdSelect {
) {
this.expanded = false
}

if (ev.target.closest('ld-label')?.querySelector('ld-select') === this.el) {
ev.preventDefault()
this.triggerRef.focus()
}
}

// Mobile Safari in some cases does not react to click events on elements
Expand Down Expand Up @@ -742,12 +751,8 @@ export class LdSelect {
(ev.relatedTarget as HTMLElement).closest('ld-select') === this.el)
) {
ev.stopImmediatePropagation()
}

// Re-dispatch blur events emitted within popper on the select component.
if (target.closest('[role="listbox"]') === this.popperRef) {
const evRedispatched = new FocusEvent(ev.type, ev)
this.el.dispatchEvent(evRedispatched)
} else if (ev instanceof FocusEvent) {
this.blur.emit(this.selected.map((option) => option.value))
}
}

Expand All @@ -761,6 +766,15 @@ export class LdSelect {
this.togglePopper()
}

private handleTriggerFocus(ev: FocusEvent) {
if (
(ev.relatedTarget as HTMLElement)?.closest('[role="listbox"]') !==
this.popperRef
) {
this.focus.emit(this.selected.map((option) => option.value))
}
}

private handleClearClick(ev: MouseEvent) {
ev.preventDefault()
ev.stopImmediatePropagation()
Expand Down Expand Up @@ -888,6 +902,7 @@ export class LdSelect {
aria-haspopup="listbox"
aria-expanded={this.expanded ? 'true' : 'false'}
onClick={this.handleTriggerClick.bind(this)}
onFocus={this.handleTriggerFocus.bind(this)}
ref={(el) => (this.triggerRef = el as HTMLElement)}
>
{this.multiple && this.selected.length ? (
Expand Down
235 changes: 235 additions & 0 deletions src/liquid/components/ld-select/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,240 @@ The feature set of the `ld-select` Web Component differs from its CSS Component
</div>
{% endexample %}

### With label

{% example %}
<ld-label>
Favorite fruit
<ld-select placeholder="Pick a fruit" name="fruit">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
<ld-option value="watermelon" disabled>Watermelon</ld-option>
<ld-option value="honeymelon">Honeymelon</ld-option>
<ld-option value="rasberry">Rasberry</ld-option>
<ld-option value="cherry">Cherry</ld-option>
<ld-option value="blueberry">Blueberry</ld-option>
<ld-option value="peach">Peach</ld-option>
<ld-option value="grape">Grape</ld-option>
<ld-option value="fuyu persimmon">Fuyu Persimmon</ld-option>
<ld-option value="monstera deliciosa">Monstera Deliciosa</ld-option>
<ld-option value="pear">Pear</ld-option>
<ld-option value="pineapple">Pineapple</ld-option>
<ld-option value="plum">Plum</ld-option>
</ld-select>
</ld-label>

<!-- CSS component -->

<label class="ld-label">
Favorite fruit
<div class='ld-select'>
<select name="fruits">
<option value="">Pick a fruit</option>
<option value="apple">Apple</option>
<option value="banana">Banana</option>
<option value="strawberry">Strawberry</option>
<option value="watermelon" disabled>Watermelon</option>
<option value="honeymelon">Honeymelon</option>
<option value="rasberry">Rasberry</option>
<option value="cherry">Cherry</option>
<option value="blueberry">Blueberry</option>
<option value="peach">Peach</option>
<option value="grape">Grape</option>
<option value="fuyu persimmon">Fuyu Persimmon</option>
<option value="monstera deliciosa">Monstera Deliciosa</option>
<option value="pear">Pear</option>
<option value="pineapple">Pineapple</option>
<option value="plum">Plum</option>
</select>
<svg
role="presentation"
class="ld-select__icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="3"
d="M3 6l5 4 5-4"
/>
</svg>
</div>
</label>
{% endexample %}

### With label and input message

{% example %}
<ld-label>
Favorite fruit
<ld-select placeholder="Pick a fruit" name="fruit">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
<ld-option value="watermelon" disabled>Watermelon</ld-option>
<ld-option value="honeymelon">Honeymelon</ld-option>
<ld-option value="rasberry">Rasberry</ld-option>
<ld-option value="cherry">Cherry</ld-option>
<ld-option value="blueberry">Blueberry</ld-option>
<ld-option value="peach">Peach</ld-option>
<ld-option value="grape">Grape</ld-option>
<ld-option value="fuyu persimmon">Fuyu Persimmon</ld-option>
<ld-option value="monstera deliciosa" selected>Monstera Deliciosa</ld-option>
<ld-option value="pear">Pear</ld-option>
<ld-option value="pineapple">Pineapple</ld-option>
<ld-option value="plum">Plum</ld-option>
</ld-select>
<ld-input-message>Not available today.</ld-input-message>
</ld-label>

<!-- CSS component -->

<label class="ld-label">
Favorite fruit
<div class='ld-select'>
<select name="fruits">
<option value="">Pick a fruit</option>
<option value="apple">Apple</option>
<option value="banana">Banana</option>
<option value="strawberry">Strawberry</option>
<option value="watermelon" disabled>Watermelon</option>
<option value="honeymelon">Honeymelon</option>
<option value="rasberry">Rasberry</option>
<option value="cherry">Cherry</option>
<option value="blueberry">Blueberry</option>
<option value="peach">Peach</option>
<option value="grape">Grape</option>
<option value="fuyu persimmon">Fuyu Persimmon</option>
<option value="monstera deliciosa" selected>Monstera Deliciosa</option>
<option value="pear">Pear</option>
<option value="pineapple">Pineapple</option>
<option value="plum">Plum</option>
</select>
<svg
role="presentation"
class="ld-select__icon"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="3"
d="M3 6l5 4 5-4"
/>
</svg>
</div>
<span class="ld-input-message ld-input-message--error">
<!-- Note that you can use an img element with the class ld-input-message__icon here as well. -->
<svg class="ld-input-message__icon" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 14C10.866 14 14 10.866 14 7C14 3.13401 10.866 0 7 0C3.13401 0 0 3.13401 0 7C0 10.866 3.13401 14 7 14Z" fill="#E61E50"/>
<path d="M4.66675 4.66699L9.33341 9.33366" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.66675 9.33301L9.33341 4.66634" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Not available today.
</span>
</label>
{% endexample %}

### Input validation

The `ld-select` Web Component provides a low level API for integrating it with the form validation solution of your choice. It allows you to listen for `focus`, `change`, `input` and `blur` events and setting error / info messages via the [`ld-input-message`](/components/ld-input-message/) component. The following is an example on how you could implement input validation with vanilla JS:

{% example %}
<style>
#example-form {
display: grid;
gap: 1rem;
width: 100%;
}
#example-form > * {
align-self: flex-end;
flex: 1 0 auto;
}
#example-form ld-button {
margin-bottom: 1.7rem;
}
@media (min-width: 52rem) {
#example-form {
grid-template-columns: 1fr auto;
}
}
#example-form ld-input-message {
visibility: hidden;
}
#example-form ld-input-message[covert="false"] {
visibility: inherit;
}
</style>
<form id="example-form" novalidate>
<ld-label>
Fruits*
<ld-select placeholder="Pick some fruits" name="fruits" multiple max-rows="1">
<ld-option value="apple">Apple</ld-option>
<ld-option value="banana">Banana</ld-option>
<ld-option value="strawberry">Strawberry</ld-option>
<ld-option value="watermelon" disabled>Watermelon</ld-option>
<ld-option value="honeymelon">Honeymelon</ld-option>
<ld-option value="rasberry">Rasberry</ld-option>
<ld-option value="cherry">Cherry</ld-option>
<ld-option value="blueberry">Blueberry</ld-option>
<ld-option value="peach">Peach</ld-option>
<ld-option value="grape">Grape</ld-option>
<ld-option value="fuyu persimmon">Fuyu Persimmon</ld-option>
<ld-option value="monstera deliciosa">Monstera Deliciosa</ld-option>
<ld-option value="pear">Pear</ld-option>
<ld-option value="pineapple">Pineapple</ld-option>
<ld-option value="plum">Plum</ld-option>
</ld-select>
<ld-input-message visible="false">Pick at least 3 fruits.</ld-input-message>
</ld-label>
<ld-button>Submit</ld-button>
</form>
<script>
const select = document.querySelector('#example-form ld-select')
const errorMessage = document.querySelector('#example-form ld-input-message')
const submitButton = document.querySelector('#example-form ld-button')
let selected = []
let selectDirty = false
function validateInput() {
if (selectDirty && selected.length < 3) {
select.setAttribute('invalid', 'true')
errorMessage.setAttribute('covert', 'false')
return false
}
select.removeAttribute('invalid')
errorMessage.setAttribute('covert', 'true')
return true
}
select.addEventListener('change', ev => {
selected = ev.detail
validateInput()
})
select.addEventListener('blur', ev => {
selected = ev.detail
selectDirty = true
validateInput()
})
submitButton.addEventListener('click', ev => {
ev.preventDefault()
selectDirty = true
const isValid = validateInput()
setTimeout(() => {
if (isValid) {
window.alert('Form submitted.')
} else {
window.alert('Form is invalid.')
}
}, 100)
})
</script>
{% endexample %}

<!-- Auto Generated Below -->


Expand Down Expand Up @@ -739,6 +973,7 @@ The feature set of the `ld-select` Web Component differs from its CSS Component
| -------- | ------------------------------------------------------------------------------------------------------ | ----------------------- |
| `blur` | Emitted with an array of selected values when the select component looses focus. | `CustomEvent<string[]>` |
| `change` | Emitted with an array of selected values when an alteration to the selection is committed by the user. | `CustomEvent<string[]>` |
| `focus` | Emitted with an array of selected values when the select component gets focus. | `CustomEvent<string[]>` |
| `input` | Emitted with an array of selected values when an alteration to the selection is committed by the user. | `CustomEvent<string[]>` |


Expand Down

0 comments on commit 730e755

Please sign in to comment.