@lion/button
Advanced tools
Comparing version 0.7.15 to 0.8.0
# Change Log | ||
## 0.8.0 | ||
### Minor Changes | ||
- 26b60593: Several button improvements | ||
- remove click-area --> move styles to host::before | ||
- reduce css so that extending styles makes sense. Merge .btn with host. | ||
- reduce the template and remove the if else construction inside the template. | ||
- hide focus styles if they're not needed, for example, when an element receives focus via the mouse. | ||
- improve \_\_clickDelegationHandler. Use current slotted native button instead of create new one. | ||
- fix vertical alignment when 2 buttons are inline and one has icon. Example included. | ||
### Patch Changes | ||
- f7ab5391: Set button host to inline-flex as a better default for when button content contains a before or after icon | ||
- Updated dependencies [e92b98a4] | ||
- @lion/core@0.13.1 | ||
## 0.7.15 | ||
@@ -4,0 +23,0 @@ |
{ | ||
"name": "@lion/button", | ||
"version": "0.7.15", | ||
"version": "0.8.0", | ||
"description": "A button that is easily styleable and accessible in all contexts", | ||
@@ -26,6 +26,7 @@ "license": "MIT", | ||
"scripts": { | ||
"debug": "cd ../../ && yarn debug --group button", | ||
"debug:firefox": "cd ../../ && yarn debug:firefox --group button", | ||
"debug:webkit": "cd ../../ && yarn debug:webkit --group button", | ||
"prepublishOnly": "../../scripts/npm-prepublish.js", | ||
"start": "cd ../../ && yarn dev-server --open packages/button/README.md", | ||
"test": "cd ../../ && yarn test:browser --grep \"packages/button/test/**/*.test.js\"", | ||
"test:watch": "cd ../../ && yarn test:browser:watch --grep \"packages/button/test/**/*.test.js\"" | ||
"test": "cd ../../ && yarn test:browser --group button" | ||
}, | ||
@@ -36,3 +37,3 @@ "sideEffects": [ | ||
"dependencies": { | ||
"@lion/core": "0.13.0" | ||
"@lion/core": "0.13.1" | ||
}, | ||
@@ -39,0 +40,0 @@ "keywords": [ |
@@ -19,3 +19,3 @@ [//]: # 'AUTO INSERT HEADER PREPUBLISH' | ||
```js preview-story | ||
export const main = () => html` <lion-button>Default</lion-button> `; | ||
export const main = () => html`<lion-button>Default</lion-button>`; | ||
``` | ||
@@ -64,3 +64,3 @@ | ||
```js preview-story | ||
export const iconButton = () => html` <lion-button>${iconSvg(html)} Bug</lion-button> `; | ||
export const iconButton = () => html`<lion-button>${iconSvg(html)}Bug</lion-button>`; | ||
``` | ||
@@ -71,4 +71,3 @@ | ||
```js preview-story | ||
export const iconOnly = () => | ||
html` <lion-button aria-label="Bug"> ${iconSvg(html)} </lion-button> `; | ||
export const iconOnly = () => html`<lion-button aria-label="Bug">${iconSvg(html)}</lion-button>`; | ||
``` | ||
@@ -79,5 +78,29 @@ | ||
```js preview-story | ||
export const disabled = () => html` <lion-button disabled>Disabled</lion-button> `; | ||
export const disabled = () => html`<lion-button disabled>Disabled</lion-button>`; | ||
``` | ||
### Multiple buttons inline | ||
```js preview-story | ||
export const mainAndIconButton = () => html` | ||
<lion-button>Default</lion-button> | ||
<lion-button>${iconSvg(html)} Bug</lion-button> | ||
`; | ||
``` | ||
### Small button (minimum click area showed) | ||
```js preview-story | ||
export const smallButton = () => html` <style> | ||
.small { | ||
padding: 4px; | ||
line-height: 1em; | ||
} | ||
.small::before { | ||
border: 1px dashed #000; | ||
} | ||
</style> | ||
<lion-button class="small">xs</lion-button>`; | ||
``` | ||
### Usage with native form | ||
@@ -97,3 +120,3 @@ | ||
ev.preventDefault(); | ||
console.log('submit handler'); | ||
console.log('submit handler', ev.target); | ||
}} | ||
@@ -105,3 +128,3 @@ > | ||
<input id="lastNameId" name="lastName" /> | ||
<lion-button @click=${() => console.log('click handler')}>Submit</lion-button> | ||
<lion-button @click=${ev => console.log('click handler', ev.target)}>Submit</lion-button> | ||
</form> | ||
@@ -108,0 +131,0 @@ `; |
@@ -11,12 +11,2 @@ import { | ||
// TODO: several improvements: | ||
// [1] remove click-area | ||
// [2] remove the native _button slot. We can detect and submit parent form without the slot. | ||
// [3] reduce css so that extending styles makes sense. Merge .btn with host | ||
// [4] reduce the template and remove the if else construction inside the template (an extra | ||
// div by default to support IE is fine) => <div id="${this._buttonId}"><slot></slot></div> | ||
// should be all needed | ||
// [5] do we need the before and after templates? Could be added by subclasser | ||
// [6] extract all functionality (except for form submission) into LionButtonMixin | ||
const isKeyboardClickEvent = (/** @type {KeyboardEvent} */ e) => e.key === ' ' || e.key === 'Enter'; | ||
@@ -45,11 +35,6 @@ const isSpaceKeyboardClickEvent = (/** @type {KeyboardEvent} */ e) => e.key === ' '; | ||
return html` | ||
<div class="btn"> | ||
<div class="click-area"></div> | ||
${this._beforeTemplate()} | ||
${browserDetection.isIE11 | ||
? html`<div id="${this._buttonId}"><slot></slot></div>` | ||
: html`<slot></slot>`} | ||
${this._afterTemplate()} | ||
<slot name="_button"></slot> | ||
</div> | ||
${this._beforeTemplate()} | ||
<div class="button-content" id="${this._buttonId}"><slot></slot></div> | ||
${this._afterTemplate()} | ||
<slot name="_button"></slot> | ||
`; | ||
@@ -72,20 +57,35 @@ } | ||
:host { | ||
display: inline-block; | ||
min-height: 40px; /* src = https://www.smashingmagazine.com/2012/02/finger-friendly-design-ideal-mobile-touchscreen-target-sizes/ */ | ||
outline: 0; | ||
background-color: transparent; | ||
position: relative; | ||
display: inline-flex; | ||
box-sizing: border-box; | ||
vertical-align: middle; | ||
line-height: 24px; | ||
background: #eee; /* minimal styling to make it recognizable as btn */ | ||
padding: 8px; /* padding to fix with min-height */ | ||
outline: none; /* focus style handled below */ | ||
cursor: default; /* /* we should always see the default arrow, never a caret */ | ||
} | ||
.btn { | ||
min-height: 24px; | ||
:host::before { | ||
content: ''; | ||
/* center vertically and horizontally */ | ||
position: absolute; | ||
top: 50%; | ||
left: 50%; | ||
transform: translate(-50%, -50%); | ||
/* touch area (comes into play when button height goes below this one) */ | ||
/* src = https://www.smashingmagazine.com/2012/02/finger-friendly-design-ideal-mobile-touchscreen-target-sizes/ */ | ||
min-height: 40px; | ||
min-width: 40px; | ||
} | ||
.button-content { | ||
display: flex; | ||
align-items: center; | ||
position: relative; | ||
background: #eee; /* minimal styling to make it recognizable as btn */ | ||
padding: 8px; /* vertical padding to fix with host min-height */ | ||
outline: none; /* focus style handled below, else it follows boundaries of click-area */ | ||
justify-content: center; | ||
} | ||
:host .btn ::slotted(button) { | ||
:host ::slotted(button) { | ||
position: absolute; | ||
@@ -104,13 +104,5 @@ top: 0; | ||
.click-area { | ||
position: absolute; | ||
top: 0; | ||
right: 0; | ||
bottom: 0; | ||
left: 0; | ||
margin: 0; | ||
padding: 0; | ||
} | ||
:host(:focus:not([disabled])) .btn { | ||
/* Show focus styles on keyboard focus. */ | ||
:host(:focus:not([disabled])), | ||
:host(:focus-visible) { | ||
/* if you extend, please overwrite */ | ||
@@ -120,3 +112,9 @@ outline: 2px solid #bde4ff; | ||
:host(:hover) .btn { | ||
/* Hide focus styles if they're not needed, for example, | ||
when an element receives focus via the mouse. */ | ||
:host(:focus:not(:focus-visible)) { | ||
outline: 0; | ||
} | ||
:host(:hover) { | ||
/* if you extend, please overwrite */ | ||
@@ -126,4 +124,4 @@ background: #f4f6f7; | ||
:host(:active) .btn, /* keep native :active to render quickly where possible */ | ||
:host([active]) .btn /* use custom [active] to fix IE11 */ { | ||
:host(:active), /* keep native :active to render quickly where possible */ | ||
:host([active]) /* use custom [active] to fix IE11 */ { | ||
/* if you extend, please overwrite */ | ||
@@ -133,6 +131,2 @@ background: gray; | ||
:host([disabled]) { | ||
pointer-events: none; | ||
} | ||
:host([hidden]) { | ||
@@ -142,3 +136,4 @@ display: none; | ||
:host([disabled]) .btn { | ||
:host([disabled]) { | ||
pointer-events: none; | ||
/* if you extend, please overwrite */ | ||
@@ -224,10 +219,5 @@ background: lightgray; | ||
__clickDelegationHandler(e) { | ||
if ((this.type === 'submit' || this.type === 'reset') && e.target === this) { | ||
if (this._form) { | ||
const nativeButton = document.createElement('button'); | ||
nativeButton.type = this.type; | ||
this._form.appendChild(nativeButton); | ||
nativeButton.click(); | ||
this._form.removeChild(nativeButton); | ||
} | ||
if ((this.type === 'submit' || this.type === 'reset') && e.target === this && this._form) { | ||
e.stopImmediatePropagation(); | ||
this._nativeButtonNode.click(); | ||
} | ||
@@ -252,2 +242,3 @@ } | ||
this.removeEventListener('keyup', this.__keyupHandler); | ||
this.removeEventListener('click', this.__clickDelegationHandler); | ||
} | ||
@@ -254,0 +245,0 @@ |
import { browserDetection } from '@lion/core'; | ||
import { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing'; | ||
import { aTimeout, expect, fixture, html, oneEvent, unsafeStatic } from '@open-wc/testing'; | ||
import sinon from 'sinon'; | ||
@@ -209,3 +209,3 @@ import '@lion/core/src/differentKeyEventNamesShimIE.js'; | ||
expect(/** @type {ShadowRoot} */ (el.shadowRoot).querySelector(`#${wrapperId}`)).dom.to.equal( | ||
`<div id="${wrapperId}"><slot></slot></div>`, | ||
`<div class="button-content" id="${wrapperId}"><slot></slot></div>`, | ||
); | ||
@@ -401,2 +401,12 @@ browserDetectionStub.restore(); | ||
describe('click event', () => { | ||
/** | ||
* @param {HTMLButtonElement | LionButton} el | ||
*/ | ||
async function prepareClickEvent(el) { | ||
setTimeout(() => { | ||
el.click(); | ||
}); | ||
return oneEvent(el, 'click'); | ||
} | ||
it('is fired once', async () => { | ||
@@ -417,12 +427,21 @@ const clickSpy = /** @type {EventListener} */ (sinon.spy()); | ||
it('is fired one inside a form', async () => { | ||
const formClickSpy = /** @type {EventListener} */ (sinon.spy(e => e.preventDefault())); | ||
const el = /** @type {HTMLFormElement} */ (await fixture( | ||
html`<form @click="${formClickSpy}"> | ||
<lion-button>foo</lion-button> | ||
</form>`, | ||
)); | ||
// @ts-ignore | ||
el.querySelector('lion-button').click(); | ||
// trying to wait for other possible redispatched events | ||
await aTimeout(0); | ||
await aTimeout(0); | ||
expect(formClickSpy).to.have.been.calledOnce; | ||
}); | ||
describe('native button behavior', async () => { | ||
/** | ||
* @param {HTMLButtonElement | LionButton} el | ||
*/ | ||
async function prepareClickEvent(el) { | ||
setTimeout(() => { | ||
el.click(); | ||
}); | ||
return oneEvent(el, 'click'); | ||
} | ||
/** @type {Event} */ | ||
@@ -456,4 +475,6 @@ let nativeButtonEvent; | ||
}); | ||
}); | ||
it('has host in the target property', async () => { | ||
describe('event target', async () => { | ||
it('is host by default', async () => { | ||
const el = /** @type {LionButton} */ (await fixture('<lion-button>foo</lion-button>')); | ||
@@ -463,4 +484,33 @@ const event = await prepareClickEvent(el); | ||
}); | ||
const useCases = [ | ||
{ container: 'div', type: 'submit', targetHost: true }, | ||
{ container: 'div', type: 'reset', targetHost: true }, | ||
{ container: 'div', type: 'button', targetHost: true }, | ||
{ container: 'form', type: 'submit', targetHost: false }, | ||
{ container: 'form', type: 'reset', targetHost: false }, | ||
{ container: 'form', type: 'button', targetHost: true }, | ||
]; | ||
useCases.forEach(useCase => { | ||
const { container, type, targetHost } = useCase; | ||
const targetName = targetHost ? 'host' : 'native button'; | ||
it(`is ${targetName} with type ${type} and it is inside a ${container}`, async () => { | ||
const clickSpy = /** @type {EventListener} */ (sinon.spy(e => e.preventDefault())); | ||
const el = /** @type {LionButton} */ (await fixture( | ||
`<lion-button type="${type}">foo</lion-button>`, | ||
)); | ||
const tag = unsafeStatic(container); | ||
await fixture(html`<${tag} @click="${clickSpy}">${el}</${tag}>`); | ||
const event = await prepareClickEvent(el); | ||
if (targetHost) { | ||
expect(event.target).to.equal(el); | ||
} else { | ||
expect(event.target).to.equal(el._nativeButtonNode); | ||
} | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
69264
819
163
+ Added@lion/core@0.13.1(transitive)
- Removed@lion/core@0.13.0(transitive)
Updated@lion/core@0.13.1