fix(select-rich): keyboard navigation should handle scrolling
This commit is contained in:
parent
3cd6c43e9a
commit
0dad105109
2 changed files with 83 additions and 1 deletions
|
|
@ -19,6 +19,26 @@ function detectInteractionMode() {
|
||||||
return 'windows/linux';
|
return 'windows/linux';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isInView(container, element, partial = false) {
|
||||||
|
const cTop = container.scrollTop;
|
||||||
|
const cBottom = cTop + container.clientHeight;
|
||||||
|
const eTop = element.offsetTop;
|
||||||
|
const eBottom = eTop + element.clientHeight;
|
||||||
|
const isTotal = eTop >= cTop && eBottom <= cBottom;
|
||||||
|
let isPartial;
|
||||||
|
|
||||||
|
if (partial === true) {
|
||||||
|
isPartial = (eTop < cTop && eBottom > cTop) || (eBottom > cBottom && eTop < cBottom);
|
||||||
|
} else if (typeof partial === 'number') {
|
||||||
|
if (eTop < cTop && eBottom > cTop) {
|
||||||
|
isPartial = ((eBottom - cTop) * 100) / element.clientHeight > partial;
|
||||||
|
} else if (eBottom > cBottom && eTop < cBottom) {
|
||||||
|
isPartial = ((cBottom - eTop) * 100) / element.clientHeight > partial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isTotal || isPartial;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LionSelectRich: wraps the <lion-listbox> element
|
* LionSelectRich: wraps the <lion-listbox> element
|
||||||
*
|
*
|
||||||
|
|
@ -129,9 +149,18 @@ export class LionSelectRich extends OverlayMixin(
|
||||||
return this.formElements.findIndex(el => el.active === true);
|
return this.formElements.findIndex(el => el.active === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get scrollTarget() {
|
||||||
|
return this._overlayContentNode.scrollTarget || this._overlayContentNode;
|
||||||
|
}
|
||||||
|
|
||||||
set activeIndex(index) {
|
set activeIndex(index) {
|
||||||
if (this.formElements[index]) {
|
if (this.formElements[index]) {
|
||||||
this.formElements[index].active = true;
|
const el = this.formElements[index];
|
||||||
|
el.active = true;
|
||||||
|
|
||||||
|
if (!isInView(this.scrollTarget, el)) {
|
||||||
|
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -591,11 +620,30 @@ export class LionSelectRich extends OverlayMixin(
|
||||||
this._invokerNode.focus();
|
this._invokerNode.focus();
|
||||||
};
|
};
|
||||||
this._overlayCtrl.addEventListener('hide', this.__overlayOnHide);
|
this._overlayCtrl.addEventListener('hide', this.__overlayOnHide);
|
||||||
|
|
||||||
|
this.__preventScrollingWithArrowKeys = this.__preventScrollingWithArrowKeys.bind(this);
|
||||||
|
this.scrollTarget.addEventListener('keydown', this.__preventScrollingWithArrowKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
__teardownOverlay() {
|
__teardownOverlay() {
|
||||||
this._overlayCtrl.removeEventListener('show', this.__overlayOnShow);
|
this._overlayCtrl.removeEventListener('show', this.__overlayOnShow);
|
||||||
this._overlayCtrl.removeEventListener('hide', this.__overlayOnHide);
|
this._overlayCtrl.removeEventListener('hide', this.__overlayOnHide);
|
||||||
|
this.scrollTarget.removeEventListener('keydown', this.__overlayOnHide);
|
||||||
|
}
|
||||||
|
|
||||||
|
__preventScrollingWithArrowKeys(ev) {
|
||||||
|
if (this.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { key } = ev;
|
||||||
|
switch (key) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
case 'ArrowDown':
|
||||||
|
case 'Home':
|
||||||
|
case 'End':
|
||||||
|
ev.preventDefault();
|
||||||
|
/* no default */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_isEmpty() {
|
_isEmpty() {
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,40 @@ storiesOf('Forms|Select Rich', module)
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
|
.add(
|
||||||
|
'Many Options with scrolling',
|
||||||
|
() => html`
|
||||||
|
<style>
|
||||||
|
.demo-listbox {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
${selectRichDemoStyle}
|
||||||
|
</style>
|
||||||
|
<div class="demo-area">
|
||||||
|
<lion-select-rich label="Favorite color" name="color">
|
||||||
|
<lion-options slot="input" class="demo-listbox">
|
||||||
|
<lion-option .modelValue=${{ value: 'red', checked: false }}>
|
||||||
|
<p style="color: red;">I am red</p>
|
||||||
|
</lion-option>
|
||||||
|
<lion-option .modelValue=${{ value: 'hotpink', checked: true }}>
|
||||||
|
<p style="color: hotpink;">I am hotpink</p>
|
||||||
|
</lion-option>
|
||||||
|
<lion-option .modelValue=${{ value: 'teal', checked: false }}>
|
||||||
|
<p style="color: teal;">I am teal</p>
|
||||||
|
</lion-option>
|
||||||
|
<lion-option .modelValue=${{ value: 'green', checked: false }}>
|
||||||
|
<p style="color: green;">I am green</p>
|
||||||
|
</lion-option>
|
||||||
|
<lion-option .modelValue=${{ value: 'blue', checked: false }}>
|
||||||
|
<p style="color: blue;">I am blue</p>
|
||||||
|
</lion-option>
|
||||||
|
</lion-options>
|
||||||
|
</lion-select-rich>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)
|
||||||
.add(
|
.add(
|
||||||
'Read-only prefilled',
|
'Read-only prefilled',
|
||||||
() => html`
|
() => html`
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue