Skip to content

Commit

Permalink
fix(ld-checkbox): prop forwarding and indeterminate state
Browse files Browse the repository at this point in the history
  • Loading branch information
borisdiakur authored and renet committed Dec 2, 2021
1 parent cf852e0 commit e3274e6
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 51 deletions.
52 changes: 52 additions & 0 deletions screenshot/builds/master.json
Original file line number Diff line number Diff line change
Expand Up @@ -3072,6 +3072,58 @@
"isLandscape": false,
"isMobile": false
},
{
"id": "cd3e9064",
"image": "33fab876591c00fec508f54756e69220.png",
"userAgent": "default",
"desc": "ld-checkbox indeterminate css component",
"testPath": "./src/liquid/components/ld-checkbox/test/ld-checkbox.e2e.ts",
"width": 600,
"height": 600,
"deviceScaleFactor": 1,
"hasTouch": false,
"isLandscape": false,
"isMobile": false
},
{
"id": "03c7af77",
"image": "e4bc0c2e2cc14e3103af2cb9d0ab70c0.png",
"userAgent": "default",
"desc": "ld-checkbox indeterminate css component disabled",
"testPath": "./src/liquid/components/ld-checkbox/test/ld-checkbox.e2e.ts",
"width": 600,
"height": 600,
"deviceScaleFactor": 1,
"hasTouch": false,
"isLandscape": false,
"isMobile": false
},
{
"id": "030ef142",
"image": "33fab876591c00fec508f54756e69220.png",
"userAgent": "default",
"desc": "ld-checkbox indeterminate web component",
"testPath": "./src/liquid/components/ld-checkbox/test/ld-checkbox.e2e.ts",
"width": 600,
"height": 600,
"deviceScaleFactor": 1,
"hasTouch": false,
"isLandscape": false,
"isMobile": false
},
{
"id": "e241b2ef",
"image": "e4bc0c2e2cc14e3103af2cb9d0ab70c0.png",
"userAgent": "default",
"desc": "ld-checkbox indeterminate web component disabled",
"testPath": "./src/liquid/components/ld-checkbox/test/ld-checkbox.e2e.ts",
"width": 600,
"height": 600,
"deviceScaleFactor": 1,
"hasTouch": false,
"isLandscape": false,
"isMobile": false
},
{
"id": "c849bdfe",
"image": "08add4160550304134136fa7224931bb.png",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/liquid/components/ld-checkbox/ld-checkbox.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@
}
}

/* For whatever reason both selectors are required to work with both, web and css component */
&:indeterminate,
&[indeterminate] {
~ .ld-checkbox__box::before {
content: '';
position: absolute;
width: 50%;
height: var(--ld-sp-2);
border-radius: var(--ld-sp-2);
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
box-shadow: inherit;
}
}

&:where(:disabled),
&:where([aria-disabled='true']) {
~ .ld-checkbox__check {
Expand Down
135 changes: 102 additions & 33 deletions src/liquid/components/ld-checkbox/ld-checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component, Element, h, Host, Method, Prop, Watch } from '@stencil/core'
import { cloneAttributes } from '../../utils/cloneAttributes'
import { getClassNames } from '../../utils/getClassNames'

/**
* @virtualProp ref - reference to component
Expand All @@ -17,30 +18,51 @@ export class LdCheckbox implements InnerFocusable {
private input: HTMLInputElement
private hiddenInput: HTMLInputElement

/** Display mode. */
@Prop() mode?: 'highlight' | 'danger'
/** Hint for form autofill feature. */
@Prop({ mutable: true, reflect: true }) autocomplete?: string

/** Used to specify the name of the control. */
@Prop() name: string
/** Automatically focus the form control when the page is loaded. */
@Prop() autofocus?: boolean

/** The input value. */
@Prop() value: string

/** Checkbox tone. Use `'dark'` on white backgrounds. Default is a light tone. */
@Prop() tone: 'dark'
@Prop({ mutable: true, reflect: true }) checked: boolean

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

/** The input value. */
@Prop({ mutable: true, reflect: true }) checked: boolean
/** Associates the control with a form element. */
@Prop() form?: string

/**
* Set this property to `true` to indicate that the checkbox's value is neither true nor false.
* The prop is removed automatically as soon as the checkbox is clicked (if not disabled).
*/
@Prop({ mutable: true }) indeterminate?: boolean

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

/** Value of the id attribute of the `<datalist>` of autocomplete options. */
@Prop() list?: string

/** Display mode. */
@Prop() mode?: 'highlight' | 'danger'

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

/** The value is not editable. */
@Prop() readonly?: boolean

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

/** Checkbox tone. Use `'dark'` on white backgrounds. Default is a light tone. */
@Prop() tone: 'dark'

/** The input value. */
@Prop() value: string

/**
* Sets focus on the checkbox
*/
Expand All @@ -52,25 +74,51 @@ export class LdCheckbox implements InnerFocusable {
}

@Watch('checked')
updateIndeterminate() {
this.indeterminate = undefined
}

@Watch('checked')
@Watch('form')
@Watch('indeterminate')
@Watch('name')
@Watch('required')
@Watch('value')
updateHiddenInput() {
const outerForm = this.el.closest('form')
if (!this.hiddenInput && this.name && (outerForm || this.form)) {
this.hiddenInput = document.createElement('input')
this.el.appendChild(this.hiddenInput)
}

if (this.hiddenInput) {
if (!this.name) {
this.hiddenInput.remove()
this.hiddenInput = undefined
return
}

this.hiddenInput.name = this.name
this.hiddenInput.checked = this.checked
this.hiddenInput.required = this.required

if (this.name) {
this.hiddenInput.name = this.name
} else {
this.hiddenInput.removeAttribute('name')
}
this.hiddenInput.indeterminate = this.indeterminate

if (this.value) {
this.hiddenInput.value = this.value
} else {
this.hiddenInput.removeAttribute('value')
}

if (this.form) {
this.hiddenInput.setAttribute('form', this.form)
} else if (this.hiddenInput.getAttribute('form')) {
if (outerForm) {
this.hiddenInput.removeAttribute('form')
} else {
this.hiddenInput.remove()
this.hiddenInput = undefined
}
}
}
}

Expand Down Expand Up @@ -104,35 +152,56 @@ export class LdCheckbox implements InnerFocusable {
}

componentWillLoad() {
if (this.el.closest('form')) {
this.hiddenInput = document.createElement('input')
this.hiddenInput.required = this.required
this.hiddenInput.type = 'checkbox'
this.hiddenInput.style.visibility = 'hidden'
this.hiddenInput.style.position = 'absolute'
this.hiddenInput.style.pointerEvents = 'none'
this.hiddenInput.checked = this.checked
const outerForm = this.el.closest('form')

if (outerForm && !this.autocomplete) {
this.autocomplete = outerForm.getAttribute('autocomplete')
}

if (outerForm || this.form) {
if (this.name) {
this.hiddenInput = document.createElement('input')
this.hiddenInput.required = this.required
this.hiddenInput.type = 'checkbox'
this.hiddenInput.style.visibility = 'hidden'
this.hiddenInput.style.position = 'absolute'
this.hiddenInput.style.pointerEvents = 'none'
this.hiddenInput.checked = this.checked
this.hiddenInput.name = this.name
}

if (this.value) {
this.hiddenInput.value = this.value
if (this.form) {
this.hiddenInput.setAttribute('form', this.form)
}

if (this.indeterminate) {
this.hiddenInput.indeterminate = this.indeterminate
}

if (this.value) {
this.hiddenInput.value = this.value
}

this.el.appendChild(this.hiddenInput)
}
}
}

this.el.appendChild(this.hiddenInput)
componentDidLoad() {
if (this.autofocus) {
this.focusInner()
}
}

render() {
let cl = 'ld-checkbox'
if (this.mode) cl += ` ld-checkbox--${this.mode}`
if (this.tone) cl += ` ld-checkbox--${this.tone}`
if (this.invalid) cl += ' ld-checkbox--invalid'
const cl = [
'ld-checkbox',
this.mode && `ld-checkbox--${this.mode}`,
this.tone && `ld-checkbox--${this.tone}`,
this.invalid && 'ld-checkbox--invalid',
]

return (
<Host part="root" class={cl} onClick={this.handleClick}>
<Host part="root" class={getClassNames(cl)} onClick={this.handleClick}>
<input
part="input focusable"
onBlur={this.handleBlur.bind(this)}
Expand Down
93 changes: 81 additions & 12 deletions src/liquid/components/ld-checkbox/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,69 @@ This component can be used in conjunction with the [`ld-label`](components/ld-la

> **Note:** When `aria-disabled` is applied on the checkbox, the component will try to prevent user interaction using an internal click event handler, calling `preventDefault()` on the click event. With the CSS component version on the other hand, you will need to take care of preventing the default behaviour of the checkbox yourself.
### Indeterminate

If the `indeterminate` attribute is present on the `ld-checkbox` component, the checkbox's value is neither `true` nor `false`, but is instead _indeterminate_, meaning that its state cannot be determined or stated in pure binary terms. This may happen, for instance, if the state of the checkbox depends on multiple other checkboxes, and those checkboxes have different values.

> **Note**: When using the CSS Component you need to take care of removing the indeterminate prop yourself.
{% example 'html', false, false, 'light' %}
<ld-checkbox indeterminate></ld-checkbox>

<ld-checkbox indeterminate disabled></ld-checkbox>

<!-- CSS component -->

<script>
document.addEventListener('input', ev => {
ev.target.removeAttribute('indeterminate')
})
</script>

<div class="ld-checkbox">
<input type="checkbox" indeterminate>
<svg
class="ld-checkbox__check"
width="14"
height="14"
fill="none"
viewBox="0 0 14 14"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 4L5.40795 10L2 6.63964"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<div class="ld-checkbox__box"></div>
</div>

<div class="ld-checkbox">
<input type="checkbox" indeterminate disabled>
<svg
class="ld-checkbox__check"
width="14"
height="14"
fill="none"
viewBox="0 0 14 14"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 4L5.40795 10L2 6.63964"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<div class="ld-checkbox__box"></div>
</div>

{% endexample %}

### Dark

> **Note**: Dark tone checkboxes should only be used on white backgrounds.
Expand Down Expand Up @@ -586,18 +649,24 @@ The `ld-checkbox` Web Component provides a low level API for integrating the com

## Properties

| Property | Attribute | Description | Type | Default |
| ---------- | ---------- | ------------------------------------------------------------------------------ | ------------------------- | ----------- |
| `checked` | `checked` | The input value. | `boolean` | `undefined` |
| `disabled` | `disabled` | Disabled state of the checkbox. | `boolean` | `undefined` |
| `invalid` | `invalid` | Set this property to `true` in order to mark the checkbox visually as invalid. | `boolean` | `undefined` |
| `key` | `key` | for tracking the node's identity when working with lists | `string \| number` | `undefined` |
| `mode` | `mode` | Display mode. | `"danger" \| "highlight"` | `undefined` |
| `name` | `name` | Used to specify the name of the control. | `string` | `undefined` |
| `ref` | `ref` | reference to component | `any` | `undefined` |
| `required` | `required` | Set this property to `true` in order to mark the checkbox as required. | `boolean` | `undefined` |
| `tone` | `tone` | Checkbox tone. Use `'dark'` on white backgrounds. Default is a light tone. | `"dark"` | `undefined` |
| `value` | `value` | The input value. | `string` | `undefined` |
| Property | Attribute | Description | Type | Default |
| --------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------- | ----------- |
| `autocomplete` | `autocomplete` | Hint for form autofill feature. | `string` | `undefined` |
| `autofocus` | `autofocus` | Automatically focus the form control when the page is loaded. | `boolean` | `undefined` |
| `checked` | `checked` | The input value. | `boolean` | `undefined` |
| `disabled` | `disabled` | Disabled state of the checkbox. | `boolean` | `undefined` |
| `form` | `form` | Associates the control with a form element. | `string` | `undefined` |
| `indeterminate` | `indeterminate` | Set this property to `true` to indicate that the checkbox's value is neither true nor false. The prop is removed automatically as soon as the checkbox is clicked (if not disabled). | `boolean` | `undefined` |
| `invalid` | `invalid` | Set this property to `true` in order to mark the checkbox visually as invalid. | `boolean` | `undefined` |
| `key` | `key` | for tracking the node's identity when working with lists | `string \| number` | `undefined` |
| `list` | `list` | Value of the id attribute of the `<datalist>` of autocomplete options. | `string` | `undefined` |
| `mode` | `mode` | Display mode. | `"danger" \| "highlight"` | `undefined` |
| `name` | `name` | Used to specify the name of the control. | `string` | `undefined` |
| `readonly` | `readonly` | The value is not editable. | `boolean` | `undefined` |
| `ref` | `ref` | reference to component | `any` | `undefined` |
| `required` | `required` | Set this property to `true` in order to mark the checkbox as required. | `boolean` | `undefined` |
| `tone` | `tone` | Checkbox tone. Use `'dark'` on white backgrounds. Default is a light tone. | `"dark"` | `undefined` |
| `value` | `value` | The input value. | `string` | `undefined` |


## Methods
Expand Down
Loading

0 comments on commit e3274e6

Please sign in to comment.