mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(inputs): keyboard focus improvements (#16838)
fixes #16815 fixes #16872 fixes #13978 fixes #16610
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
"validate": "npm i && npm run lint && npm run test && npm run build"
|
||||
},
|
||||
"module": "dist/fesm5.js",
|
||||
"main": "dist/fesm5.js",
|
||||
"types": "dist/core.d.ts",
|
||||
"files": [
|
||||
"dist/",
|
||||
|
||||
@@ -190,6 +190,8 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
'role': 'dialog',
|
||||
'aria-modal': 'true',
|
||||
style: {
|
||||
zIndex: 20000 + this.overlayIndex,
|
||||
},
|
||||
|
||||
@@ -117,11 +117,7 @@
|
||||
}
|
||||
|
||||
.alert-tappable {
|
||||
display: flex;
|
||||
|
||||
height: $alert-ios-tappable-height;
|
||||
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -110,12 +110,10 @@
|
||||
}
|
||||
|
||||
.alert-tappable {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
height: $alert-md-tappable-height;
|
||||
|
||||
contain: strict;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,11 @@
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.alert-button.ion-focused,
|
||||
.alert-tappable.ion-focused {
|
||||
background: $background-color-step-100;
|
||||
}
|
||||
|
||||
.alert-button-inner {
|
||||
display: flex;
|
||||
|
||||
@@ -147,6 +152,8 @@
|
||||
@include margin(0);
|
||||
@include padding(0);
|
||||
|
||||
display: flex;
|
||||
|
||||
width: 100%;
|
||||
|
||||
border: 0;
|
||||
@@ -159,16 +166,15 @@
|
||||
|
||||
text-align: start;
|
||||
appearance: none;
|
||||
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.alert-button,
|
||||
.alert-checkbox,
|
||||
.alert-input,
|
||||
.alert-radio {
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.alert-radio-icon,
|
||||
|
||||
@@ -309,7 +309,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
disabled={i.disabled}
|
||||
tabIndex={0}
|
||||
role="checkbox"
|
||||
class="alert-tappable alert-checkbox alert-checkbox-button"
|
||||
class="alert-tappable alert-checkbox alert-checkbox-button ion-focusable"
|
||||
>
|
||||
<div class="alert-button-inner">
|
||||
<div class="alert-checkbox-icon">
|
||||
@@ -341,7 +341,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
disabled={i.disabled}
|
||||
id={i.id}
|
||||
tabIndex={0}
|
||||
class="alert-radio-button alert-tappable alert-radio"
|
||||
class="alert-radio-button alert-tappable alert-radio ion-focusable"
|
||||
role="radio"
|
||||
>
|
||||
<div class="alert-button-inner">
|
||||
@@ -385,7 +385,8 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
role: 'alertdialog',
|
||||
'role': 'dialog',
|
||||
'aria-modal': 'true',
|
||||
style: {
|
||||
zIndex: 20000 + this.overlayIndex,
|
||||
},
|
||||
@@ -451,6 +452,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
function buttonClass(button: AlertButton): CssClassMap {
|
||||
return {
|
||||
'alert-button': true,
|
||||
'ion-focusable': true,
|
||||
'ion-activatable': true,
|
||||
...getClassMap(button.cssClass)
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ComponentInterface, Prop } from '@stencil/core';
|
||||
import { Component, ComponentInterface, Listen, Prop } from '@stencil/core';
|
||||
|
||||
import { Color, RouterDirection } from '../../interface';
|
||||
import { createColorClasses, openURL } from '../../utils/theme';
|
||||
@@ -31,6 +31,11 @@ export class Anchor implements ComponentInterface {
|
||||
*/
|
||||
@Prop() routerDirection: RouterDirection = 'forward';
|
||||
|
||||
@Listen('click')
|
||||
onClick(ev: Event) {
|
||||
openURL(this.win, this.href, ev, this.routerDirection);
|
||||
}
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
class: {
|
||||
@@ -42,10 +47,7 @@ export class Anchor implements ComponentInterface {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<a
|
||||
href={this.href}
|
||||
onClick={(ev: Event) => openURL(this.win, this.href, ev, this.routerDirection)}
|
||||
>
|
||||
<a href={this.href}>
|
||||
<slot></slot>
|
||||
</a>
|
||||
);
|
||||
|
||||
@@ -27,6 +27,7 @@ export class App implements ComponentInterface {
|
||||
importInputShims(win, config);
|
||||
importStatusTap(win, config, queue);
|
||||
importHardwareBackButton(win, config);
|
||||
importFocusVisible(win);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,6 +55,10 @@ function importStatusTap(win: Window, config: Config, queue: QueueApi) {
|
||||
}
|
||||
}
|
||||
|
||||
function importFocusVisible(win: Window) {
|
||||
import('../../utils/focus-visible').then(module => module.startFocusVisible(win.document));
|
||||
}
|
||||
|
||||
function importTapClick(win: Window, config: Config) {
|
||||
import('../../utils/tap-click').then(module => module.startTapClick(win.document, config));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ComponentInterface, Element, Prop } from '@stencil/core';
|
||||
import { Component, ComponentInterface, Element, Listen, Prop } from '@stencil/core';
|
||||
|
||||
import { Color, Config, Mode } from '../../interface';
|
||||
import { createColorClasses, openURL } from '../../utils/theme';
|
||||
@@ -45,6 +45,7 @@ export class BackButton implements ComponentInterface {
|
||||
*/
|
||||
@Prop() text?: string | null;
|
||||
|
||||
@Listen('click')
|
||||
async onClick(ev: Event) {
|
||||
const nav = this.el.closest('ion-nav');
|
||||
ev.preventDefault();
|
||||
@@ -78,7 +79,6 @@ export class BackButton implements ComponentInterface {
|
||||
<button
|
||||
type="button"
|
||||
class="button-native"
|
||||
onClick={(ev: Event) => this.onClick(ev)}
|
||||
>
|
||||
<span class="button-inner">
|
||||
{backButtonIcon && <ion-icon icon={backButtonIcon} lazy={false}></ion-icon>}
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
}
|
||||
|
||||
// Focused/Activated Solid Button with Color
|
||||
:host(.button-solid.ion-color.focused) .button-native {
|
||||
:host(.button-solid.ion-color.ion-focused) .button-native {
|
||||
background: #{current-color(shade)};
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
color: current-color(base);
|
||||
}
|
||||
|
||||
:host(.button-outline.focused.ion-color) .button-native {
|
||||
:host(.button-outline.ion-focused.ion-color) .button-native {
|
||||
background: current-color(base, .1);
|
||||
color: current-color(base);
|
||||
}
|
||||
@@ -130,7 +130,7 @@
|
||||
}
|
||||
|
||||
// Focused Clear Button with Color
|
||||
:host(.button-clear.focused.ion-color) .button-native {
|
||||
:host(.button-clear.ion-focused.ion-color) .button-native {
|
||||
background: current-color(base, .1);
|
||||
color: current-color(base);
|
||||
}
|
||||
@@ -231,7 +231,7 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:host(.focused) .button-native {
|
||||
:host(.ion-focused) .button-native {
|
||||
background: var(--background-focused);
|
||||
color: var(--color-focused);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, State } from '@stencil/core';
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Prop } from '@stencil/core';
|
||||
|
||||
import { Color, Mode, RouterDirection } from '../../interface';
|
||||
import { hasShadowDom } from '../../utils/helpers';
|
||||
@@ -20,8 +20,6 @@ export class Button implements ComponentInterface {
|
||||
|
||||
@Prop({ context: 'window' }) win!: Window;
|
||||
|
||||
@State() keyFocus = false;
|
||||
|
||||
/**
|
||||
* The color to use from your application's color palette.
|
||||
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
|
||||
@@ -103,20 +101,8 @@ export class Button implements ComponentInterface {
|
||||
this.inToolbar = !!this.el.closest('ion-buttons');
|
||||
}
|
||||
|
||||
private onFocus = () => {
|
||||
this.ionFocus.emit();
|
||||
}
|
||||
|
||||
private onKeyUp = () => {
|
||||
this.keyFocus = true;
|
||||
}
|
||||
|
||||
private onBlur = () => {
|
||||
this.keyFocus = false;
|
||||
this.ionBlur.emit();
|
||||
}
|
||||
|
||||
private onClick = (ev: Event) => {
|
||||
@Listen('click')
|
||||
onClick(ev: Event) {
|
||||
if (this.type === 'button') {
|
||||
return openURL(this.win, this.href, ev, this.routerDirection);
|
||||
|
||||
@@ -139,14 +125,22 @@ export class Button implements ComponentInterface {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
private onFocus = () => {
|
||||
this.ionFocus.emit();
|
||||
}
|
||||
|
||||
private onBlur = () => {
|
||||
this.ionBlur.emit();
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const { buttonType, keyFocus, disabled, color, expand, shape, size, strong } = this;
|
||||
const { buttonType, disabled, color, expand, shape, size, strong } = this;
|
||||
let fill = this.fill;
|
||||
if (fill === undefined) {
|
||||
fill = this.inToolbar ? 'clear' : 'solid';
|
||||
}
|
||||
return {
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
'aria-disabled': disabled ? 'true' : null,
|
||||
class: {
|
||||
...createColorClasses(color),
|
||||
[buttonType]: true,
|
||||
@@ -156,9 +150,9 @@ export class Button implements ComponentInterface {
|
||||
[`${buttonType}-${fill}`]: true,
|
||||
[`${buttonType}-strong`]: strong,
|
||||
|
||||
'focused': keyFocus,
|
||||
'button-disabled': disabled,
|
||||
'ion-activatable': true,
|
||||
'ion-focusable': true,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -175,9 +169,7 @@ export class Button implements ComponentInterface {
|
||||
class="button-native"
|
||||
disabled={this.disabled}
|
||||
onFocus={this.onFocus}
|
||||
onKeyUp={this.onKeyUp}
|
||||
onBlur={this.onBlur}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
<span class="button-inner">
|
||||
<slot name="icon-only"></slot>
|
||||
|
||||
@@ -31,26 +31,6 @@
|
||||
}
|
||||
|
||||
|
||||
// iOS Checkbox Keyboard Focus
|
||||
// -----------------------------------------
|
||||
|
||||
:host(.checkbox-key) .checkbox-icon::after {
|
||||
@include border-radius(50%);
|
||||
@include position(-9px, null, null, -9px);
|
||||
|
||||
display: block;
|
||||
position: absolute;
|
||||
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
background: $checkbox-ios-background-color-focused;
|
||||
|
||||
content: "";
|
||||
opacity: .2;
|
||||
}
|
||||
|
||||
|
||||
// iOS Checkbox Within An Item
|
||||
// -----------------------------------------
|
||||
|
||||
|
||||
@@ -44,27 +44,6 @@
|
||||
opacity: $checkbox-md-disabled-opacity;
|
||||
}
|
||||
|
||||
|
||||
// Material Design Checkbox Keyboard Focus
|
||||
// -----------------------------------------
|
||||
|
||||
:host(.checkbox-key) .checkbox-icon::after {
|
||||
@include border-radius(50%);
|
||||
@include position(-12px, null, null, -12px);
|
||||
|
||||
display: block;
|
||||
position: absolute;
|
||||
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
background: $checkbox-md-background-color-focused;
|
||||
|
||||
content: "";
|
||||
opacity: .2;
|
||||
}
|
||||
|
||||
|
||||
// Material Design Checkbox Within An Item
|
||||
// -----------------------------------------
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Prop, Watch } from '@stencil/core';
|
||||
|
||||
import { CheckboxChangeEventDetail, Color, Mode, StyleEventDetail } from '../../interface';
|
||||
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
@@ -18,8 +18,6 @@ export class Checkbox implements ComponentInterface {
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@State() keyFocus = false;
|
||||
|
||||
/**
|
||||
* The color to use from your application's color palette.
|
||||
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
|
||||
@@ -98,40 +96,36 @@ export class Checkbox implements ComponentInterface {
|
||||
});
|
||||
}
|
||||
|
||||
private onClick = () => {
|
||||
@Listen('click')
|
||||
onClick() {
|
||||
this.checked = !this.checked;
|
||||
}
|
||||
|
||||
private onKeyUp = () => {
|
||||
this.keyFocus = true;
|
||||
}
|
||||
|
||||
private onFocus = () => {
|
||||
this.ionFocus.emit();
|
||||
}
|
||||
|
||||
private onBlur = () => {
|
||||
this.keyFocus = false;
|
||||
this.ionBlur.emit();
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const labelId = this.inputId + '-lbl';
|
||||
const label = findItemLabel(this.el);
|
||||
const { inputId, disabled, checked, color, el } = this;
|
||||
const labelId = inputId + '-lbl';
|
||||
const label = findItemLabel(el);
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
return {
|
||||
'role': 'checkbox',
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
'aria-checked': `${this.checked}`,
|
||||
'aria-disabled': disabled ? 'true' : null,
|
||||
'aria-checked': `${checked}`,
|
||||
'aria-labelledby': labelId,
|
||||
class: {
|
||||
...createColorClasses(this.color),
|
||||
'in-item': hostContext('ion-item', this.el),
|
||||
'checkbox-checked': this.checked,
|
||||
'checkbox-disabled': this.disabled,
|
||||
'checkbox-key': this.keyFocus,
|
||||
...createColorClasses(color),
|
||||
'in-item': hostContext('ion-item', el),
|
||||
'checkbox-checked': checked,
|
||||
'checkbox-disabled': disabled,
|
||||
'interactive': true
|
||||
}
|
||||
};
|
||||
@@ -149,10 +143,9 @@ export class Checkbox implements ComponentInterface {
|
||||
</svg>,
|
||||
<button
|
||||
type="button"
|
||||
onClick={this.onClick}
|
||||
onKeyUp={this.onKeyUp}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
disabled={this.disabled}
|
||||
>
|
||||
</button>
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Prop, State, Watch } from '@stencil/core';
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Method, Prop, State, Watch } from '@stencil/core';
|
||||
|
||||
import { DatetimeChangeEventDetail, DatetimeOptions, Mode, PickerColumn, PickerColumnOption, PickerOptions, StyleEventDetail } from '../../interface';
|
||||
import { clamp, findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
@@ -15,16 +15,17 @@ import { DatetimeData, LocaleData, convertDataToISO, convertFormatToKey, convert
|
||||
shadow: true
|
||||
})
|
||||
export class Datetime implements ComponentInterface {
|
||||
|
||||
private inputId = `ion-dt-${datetimeIds++}`;
|
||||
private locale: LocaleData = {};
|
||||
private datetimeMin: DatetimeData = {};
|
||||
private datetimeMax: DatetimeData = {};
|
||||
private datetimeValue: DatetimeData = {};
|
||||
private buttonEl?: HTMLButtonElement;
|
||||
|
||||
@Element() el!: HTMLIonDatetimeElement;
|
||||
|
||||
@State() isExpanded = false;
|
||||
@State() keyFocus = false;
|
||||
|
||||
@Prop({ connect: 'ion-picker-controller' }) pickerCtrl!: HTMLIonPickerControllerElement;
|
||||
|
||||
@@ -238,6 +239,11 @@ export class Datetime implements ComponentInterface {
|
||||
this.emitStyle();
|
||||
}
|
||||
|
||||
@Listen('click')
|
||||
onClick() {
|
||||
this.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the datetime overlay.
|
||||
*/
|
||||
@@ -252,6 +258,7 @@ export class Datetime implements ComponentInterface {
|
||||
this.isExpanded = true;
|
||||
picker.onDidDismiss().then(() => {
|
||||
this.isExpanded = false;
|
||||
this.setFocus();
|
||||
});
|
||||
await this.validate(picker);
|
||||
await picker.present();
|
||||
@@ -522,12 +529,10 @@ export class Datetime implements ComponentInterface {
|
||||
return Object.keys(val).length > 0;
|
||||
}
|
||||
|
||||
private onClick = () => {
|
||||
this.open();
|
||||
}
|
||||
|
||||
private onKeyUp = () => {
|
||||
this.keyFocus = true;
|
||||
private setFocus() {
|
||||
if (this.buttonEl) {
|
||||
this.buttonEl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private onFocus = () => {
|
||||
@@ -535,30 +540,31 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
|
||||
private onBlur = () => {
|
||||
this.keyFocus = false;
|
||||
this.ionBlur.emit();
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const addPlaceholderClass =
|
||||
(this.getText() === undefined && this.placeholder != null) ? true : false;
|
||||
const { inputId, disabled, isExpanded, el, placeholder } = this;
|
||||
|
||||
const labelId = this.inputId + '-lbl';
|
||||
const label = findItemLabel(this.el);
|
||||
const addPlaceholderClass =
|
||||
(this.getText() === undefined && placeholder != null) ? true : false;
|
||||
|
||||
const labelId = inputId + '-lbl';
|
||||
const label = findItemLabel(el);
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
|
||||
return {
|
||||
'role': 'combobox',
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
'aria-expanded': `${this.isExpanded}`,
|
||||
'aria-disabled': disabled ? 'true' : null,
|
||||
'aria-expanded': `${isExpanded}`,
|
||||
'aria-haspopup': 'true',
|
||||
'aria-labelledby': labelId,
|
||||
class: {
|
||||
'datetime-disabled': this.disabled,
|
||||
'datetime-disabled': disabled,
|
||||
'datetime-placeholder': addPlaceholderClass,
|
||||
'in-item': hostContext('ion-item', this.el)
|
||||
'in-item': hostContext('ion-item', el)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -576,10 +582,10 @@ export class Datetime implements ComponentInterface {
|
||||
<div class="datetime-text">{datetimeText}</div>,
|
||||
<button
|
||||
type="button"
|
||||
onClick={this.onClick}
|
||||
onKeyUp={this.onKeyUp}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
disabled={this.disabled}
|
||||
ref={el => this.buttonEl = el}
|
||||
>
|
||||
</button>
|
||||
];
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
background: #{current-color(base, $fab-ios-translucent-background-color-alpha)};
|
||||
}
|
||||
|
||||
:host(.ion-color.focused.fab-button-translucent) .button-native,
|
||||
:host(.ion-color.ion-focused.fab-button-translucent) .button-native,
|
||||
:host(.ion-color.activated.fab-button-translucent) .button-native {
|
||||
background: #{current-color(base)};
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
}
|
||||
|
||||
// Focused/Activated Button with Color
|
||||
:host(.ion-color.focused) .button-native,
|
||||
:host(.ion-color.ion-focused) .button-native,
|
||||
:host(.ion-color.activated) .button-native {
|
||||
background: #{current-color(shade)};
|
||||
color: #{current-color(contrast)};
|
||||
@@ -147,7 +147,7 @@
|
||||
}
|
||||
|
||||
// Focused Button
|
||||
:host(.focused) .button-native {
|
||||
:host(.ion-focused) .button-native {
|
||||
background: var(--background-focused);
|
||||
color: var(--color-focused);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, State } from '@stencil/core';
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop } from '@stencil/core';
|
||||
|
||||
import { Color, Mode, RouterDirection } from '../../interface';
|
||||
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
|
||||
@@ -14,8 +14,6 @@ import { createColorClasses, hostContext, openURL } from '../../utils/theme';
|
||||
export class FabButton implements ComponentInterface {
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@State() keyFocus = false;
|
||||
|
||||
@Prop({ context: 'window' }) win!: Window;
|
||||
|
||||
/**
|
||||
@@ -86,17 +84,12 @@ export class FabButton implements ComponentInterface {
|
||||
this.ionFocus.emit();
|
||||
}
|
||||
|
||||
private onKeyUp = () => {
|
||||
this.keyFocus = true;
|
||||
}
|
||||
|
||||
private onBlur = () => {
|
||||
this.keyFocus = false;
|
||||
this.ionBlur.emit();
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const { el, disabled, color, activated, show, translucent, size, keyFocus } = this;
|
||||
const { el, disabled, color, activated, show, translucent, size } = this;
|
||||
const inList = hostContext('ion-fab-list', el);
|
||||
return {
|
||||
'aria-disabled': disabled ? 'true' : null,
|
||||
@@ -109,7 +102,7 @@ export class FabButton implements ComponentInterface {
|
||||
'fab-button-disabled': disabled,
|
||||
'fab-button-translucent': translucent,
|
||||
'ion-activatable': true,
|
||||
'focused': keyFocus,
|
||||
'ion-focusable': true,
|
||||
[`fab-button-${size}`]: size !== undefined,
|
||||
}
|
||||
};
|
||||
@@ -127,7 +120,6 @@ export class FabButton implements ComponentInterface {
|
||||
class="button-native"
|
||||
disabled={this.disabled}
|
||||
onFocus={this.onFocus}
|
||||
onKeyUp={this.onKeyUp}
|
||||
onBlur={this.onBlur}
|
||||
onClick={(ev: Event) => openURL(this.win, this.href, ev, this.routerDirection)}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ComponentInterface, Element, Prop } from '@stencil/core';
|
||||
import { Component, ComponentInterface, Element, Listen, Prop } from '@stencil/core';
|
||||
|
||||
import { Color, Mode } from '../../interface';
|
||||
import { createColorClasses } from '../../utils/theme';
|
||||
@@ -43,9 +43,12 @@ export class ItemOption implements ComponentInterface {
|
||||
*/
|
||||
@Prop() href?: string;
|
||||
|
||||
private clickedOptionButton(ev: Event): boolean {
|
||||
@Listen('click')
|
||||
onClick(ev: Event) {
|
||||
const el = (ev.target as HTMLElement).closest('ion-item-option');
|
||||
return !!el;
|
||||
if (el) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
hostData() {
|
||||
@@ -67,7 +70,6 @@ export class ItemOption implements ComponentInterface {
|
||||
class="button-native"
|
||||
disabled={this.disabled}
|
||||
href={this.href}
|
||||
onClick={this.clickedOptionButton.bind(this)}
|
||||
>
|
||||
<span class="button-inner">
|
||||
<slot name="start"></slot>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
--inner-border-width: #{0px 0px $item-ios-border-bottom-width 0px};
|
||||
--background: #{$item-ios-background};
|
||||
--background-activated: #{$item-ios-background-activated};
|
||||
--background-focused: #{$item-ios-background-activated};
|
||||
--border-color: #{$item-ios-border-bottom-color};
|
||||
--color: #{$item-ios-color};
|
||||
--highlight-height: 0;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
--min-height: #{$item-md-min-height};
|
||||
--background: #{$item-md-background};
|
||||
--background-activated: var(--background);
|
||||
--background-focused: #{$item-md-background-activated};
|
||||
--border-color: #{$item-md-border-bottom-color};
|
||||
--color: #{$item-md-color};
|
||||
--transition: background-color 300ms cubic-bezier(.4, 0, .2, 1);
|
||||
|
||||
@@ -91,6 +91,10 @@
|
||||
// Activated Item
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.ion-focused) .item-native {
|
||||
background: var(--background-focused);
|
||||
}
|
||||
|
||||
:host(.activated) .item-native {
|
||||
background: var(--background-activated);
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ export class Item implements ComponentInterface {
|
||||
'item': true,
|
||||
'item-multiple-inputs': this.multipleInputs,
|
||||
'ion-activatable': this.isClickable(),
|
||||
'ion-focusable': true,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -158,6 +159,7 @@ export class Item implements ComponentInterface {
|
||||
<TagType
|
||||
{...attrs}
|
||||
class="item-native"
|
||||
disabled={this.disabled}
|
||||
onClick={(ev: Event) => openURL(win, href, ev, routerDirection)}
|
||||
>
|
||||
<slot name="start"></slot>
|
||||
|
||||
@@ -191,6 +191,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
hostData() {
|
||||
return {
|
||||
'no-router': true,
|
||||
'aria-modal': 'true',
|
||||
class: {
|
||||
...createThemedClasses(this.mode, 'modal'),
|
||||
...getClassMap(this.cssClass)
|
||||
|
||||
@@ -202,6 +202,7 @@ export class Picker implements ComponentInterface, OverlayInterface {
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
'aria-modal': 'true',
|
||||
class: {
|
||||
...createThemedClasses(this.mode, 'picker'),
|
||||
...getClassMap(this.cssClass)
|
||||
|
||||
@@ -198,10 +198,11 @@ export class Popover implements ComponentInterface, OverlayInterface {
|
||||
|
||||
hostData() {
|
||||
return {
|
||||
'aria-modal': 'true',
|
||||
'no-router': true,
|
||||
style: {
|
||||
zIndex: 20000 + this.overlayIndex,
|
||||
},
|
||||
'no-router': true,
|
||||
class: {
|
||||
...getClassMap(this.cssClass),
|
||||
'popover-translucent': this.translucent
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
// iOS Radio: Keyboard Focus
|
||||
// -----------------------------------------
|
||||
|
||||
:host(.radio-key) .radio-icon::after {
|
||||
:host(.ion-focused) .radio-icon::after {
|
||||
@include border-radius(50%);
|
||||
@include position(-8px, null, null, -9px);
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
// Material Design Radio: Keyboard Focus
|
||||
// -----------------------------------------
|
||||
|
||||
:host(.radio-key) .radio-icon::after {
|
||||
:host(.ion-focused) .radio-icon::after {
|
||||
@include border-radius(50%);
|
||||
@include position(-12px, null, null, -12px);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Prop, Watch } from '@stencil/core';
|
||||
|
||||
import { Color, Mode, RadioChangeEventDetail, StyleEventDetail } from '../../interface';
|
||||
import { findItemLabel } from '../../utils/helpers';
|
||||
@@ -16,8 +16,6 @@ export class Radio implements ComponentInterface {
|
||||
|
||||
private inputId = `ion-rb-${radioButtonIds++}`;
|
||||
|
||||
@State() keyFocus = false;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
/**
|
||||
@@ -127,14 +125,8 @@ export class Radio implements ComponentInterface {
|
||||
this.ionRadioDidUnload.emit();
|
||||
}
|
||||
|
||||
private emitStyle() {
|
||||
this.ionStyle.emit({
|
||||
'radio-checked': this.checked,
|
||||
'interactive-disabled': this.disabled,
|
||||
});
|
||||
}
|
||||
|
||||
private onClick = () => {
|
||||
@Listen('click')
|
||||
onClick() {
|
||||
if (this.checked) {
|
||||
this.ionDeselect.emit();
|
||||
} else {
|
||||
@@ -142,8 +134,11 @@ export class Radio implements ComponentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private onKeyUp = () => {
|
||||
this.keyFocus = true;
|
||||
private emitStyle() {
|
||||
this.ionStyle.emit({
|
||||
'radio-checked': this.checked,
|
||||
'interactive-disabled': this.disabled,
|
||||
});
|
||||
}
|
||||
|
||||
private onFocus = () => {
|
||||
@@ -151,28 +146,27 @@ export class Radio implements ComponentInterface {
|
||||
}
|
||||
|
||||
private onBlur = () => {
|
||||
this.keyFocus = false;
|
||||
this.ionBlur.emit();
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const labelId = this.inputId + '-lbl';
|
||||
const label = findItemLabel(this.el);
|
||||
const { inputId, disabled, checked, color, el } = this;
|
||||
const labelId = inputId + '-lbl';
|
||||
const label = findItemLabel(el);
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
return {
|
||||
'role': 'radio',
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
'aria-checked': `${this.checked}`,
|
||||
'aria-disabled': disabled ? 'true' : null,
|
||||
'aria-checked': `${checked}`,
|
||||
'aria-labelledby': labelId,
|
||||
class: {
|
||||
...createColorClasses(this.color),
|
||||
'in-item': hostContext('ion-item', this.el),
|
||||
...createColorClasses(color),
|
||||
'in-item': hostContext('ion-item', el),
|
||||
'interactive': true,
|
||||
'radio-checked': this.checked,
|
||||
'radio-disabled': this.disabled,
|
||||
'radio-key': this.keyFocus
|
||||
'radio-checked': checked,
|
||||
'radio-disabled': disabled,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -184,10 +178,9 @@ export class Radio implements ComponentInterface {
|
||||
</div>,
|
||||
<button
|
||||
type="button"
|
||||
onClick={this.onClick}
|
||||
onKeyUp={this.onKeyUp}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
disabled={this.disabled}
|
||||
>
|
||||
</button>,
|
||||
];
|
||||
|
||||
@@ -165,7 +165,7 @@ export class Range implements ComponentInterface {
|
||||
this.gesture.setDisabled(this.disabled);
|
||||
}
|
||||
|
||||
private handleKeyboard(knob: string, isIncrease: boolean) {
|
||||
private handleKeyboard = (knob: string, isIncrease: boolean) => {
|
||||
let step = this.step;
|
||||
step = step > 0 ? step : 1;
|
||||
step = step / (this.max - this.min);
|
||||
@@ -173,9 +173,9 @@ export class Range implements ComponentInterface {
|
||||
step *= -1;
|
||||
}
|
||||
if (knob === 'A') {
|
||||
this.ratioA += step;
|
||||
this.ratioA = clamp(0, this.ratioA + step, 1);
|
||||
} else {
|
||||
this.ratioB += step;
|
||||
this.ratioB = clamp(0, this.ratioB + step, 1);
|
||||
}
|
||||
this.updateValue();
|
||||
}
|
||||
@@ -378,7 +378,7 @@ export class Range implements ComponentInterface {
|
||||
ratio: this.ratioA,
|
||||
pin: this.pin,
|
||||
disabled: this.disabled,
|
||||
handleKeyboard: this.handleKeyboard.bind(this),
|
||||
handleKeyboard: this.handleKeyboard,
|
||||
min,
|
||||
max
|
||||
})}
|
||||
@@ -390,7 +390,7 @@ export class Range implements ComponentInterface {
|
||||
ratio: this.ratioB,
|
||||
pin: this.pin,
|
||||
disabled: this.disabled,
|
||||
handleKeyboard: this.handleKeyboard.bind(this),
|
||||
handleKeyboard: this.handleKeyboard,
|
||||
min,
|
||||
max
|
||||
})}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, Watch } from '@stencil/core';
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Prop, Watch } from '@stencil/core';
|
||||
|
||||
import { Mode, SegmentButtonLayout } from '../../interface';
|
||||
|
||||
@@ -53,7 +53,8 @@ export class SegmentButton implements ComponentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private onClick = () => {
|
||||
@Listen('click')
|
||||
onClick() {
|
||||
this.checked = true;
|
||||
}
|
||||
|
||||
@@ -90,7 +91,6 @@ export class SegmentButton implements ComponentInterface {
|
||||
aria-pressed={this.checked ? 'true' : null}
|
||||
class="button-native"
|
||||
disabled={this.disabled}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
<slot></slot>
|
||||
{this.mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:host(.select-key) button {
|
||||
:host(.ion-focused) button {
|
||||
border: 2px solid #5e9ed6;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export class Select implements ComponentInterface {
|
||||
private inputId = `ion-sel-${selectIds++}`;
|
||||
private overlay?: OverlaySelect;
|
||||
private didInit = false;
|
||||
private buttonEl?: HTMLButtonElement;
|
||||
|
||||
@Element() el!: HTMLIonSelectElement;
|
||||
|
||||
@@ -26,7 +27,6 @@ export class Select implements ComponentInterface {
|
||||
@Prop({ connect: 'ion-popover-controller' }) popoverCtrl!: HTMLIonPopoverControllerElement;
|
||||
|
||||
@State() isExpanded = false;
|
||||
@State() keyFocus = false;
|
||||
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
@@ -138,6 +138,11 @@ export class Select implements ComponentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('click')
|
||||
onClick() {
|
||||
this.open();
|
||||
}
|
||||
|
||||
async componentDidLoad() {
|
||||
await this.loadOptions();
|
||||
|
||||
@@ -174,6 +179,7 @@ export class Select implements ComponentInterface {
|
||||
overlay.onDidDismiss().then(() => {
|
||||
this.overlay = undefined;
|
||||
this.isExpanded = false;
|
||||
this.setFocus();
|
||||
});
|
||||
await overlay.present();
|
||||
return overlay;
|
||||
@@ -353,6 +359,12 @@ export class Select implements ComponentInterface {
|
||||
return generateText(this.childOpts, this.value);
|
||||
}
|
||||
|
||||
private setFocus() {
|
||||
if (this.buttonEl) {
|
||||
this.buttonEl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private emitStyle() {
|
||||
this.ionStyle.emit({
|
||||
'interactive': true,
|
||||
@@ -364,20 +376,11 @@ export class Select implements ComponentInterface {
|
||||
});
|
||||
}
|
||||
|
||||
private onClick = (ev: UIEvent) => {
|
||||
this.open(ev);
|
||||
}
|
||||
|
||||
private onKeyUp = () => {
|
||||
this.keyFocus = true;
|
||||
}
|
||||
|
||||
private onFocus = () => {
|
||||
this.ionFocus.emit();
|
||||
}
|
||||
|
||||
private onBlur = () => {
|
||||
this.keyFocus = false;
|
||||
this.ionBlur.emit();
|
||||
}
|
||||
|
||||
@@ -397,7 +400,6 @@ export class Select implements ComponentInterface {
|
||||
class: {
|
||||
'in-item': hostContext('ion-item', this.el),
|
||||
'select-disabled': this.disabled,
|
||||
'select-key': this.keyFocus
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -432,10 +434,10 @@ export class Select implements ComponentInterface {
|
||||
</div>,
|
||||
<button
|
||||
type="button"
|
||||
onClick={this.onClick}
|
||||
onKeyUp={this.onKeyUp}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
disabled={this.disabled}
|
||||
ref={(el => this.buttonEl = el)}
|
||||
>
|
||||
</button>
|
||||
];
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
z-index: $z-index-item-input;
|
||||
}
|
||||
|
||||
:host(.toggle-key) input {
|
||||
:host(.ion-focused) input {
|
||||
border: 2px solid #5e9ed6;
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input {
|
||||
button {
|
||||
@include input-cover();
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ export class Toggle implements ComponentInterface {
|
||||
@Prop({ context: 'queue' }) queue!: QueueApi;
|
||||
|
||||
@State() activated = false;
|
||||
@State() keyFocus = false;
|
||||
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
@@ -99,27 +98,6 @@ export class Toggle implements ComponentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('click')
|
||||
onClick() {
|
||||
this.checked = !this.checked;
|
||||
}
|
||||
|
||||
@Listen('keyup')
|
||||
onKeyUp() {
|
||||
this.keyFocus = true;
|
||||
}
|
||||
|
||||
@Listen('focus')
|
||||
onFocus() {
|
||||
this.ionFocus.emit();
|
||||
}
|
||||
|
||||
@Listen('blur')
|
||||
onBlur() {
|
||||
this.keyFocus = false;
|
||||
this.ionBlur.emit();
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
this.emitStyle();
|
||||
}
|
||||
@@ -138,6 +116,11 @@ export class Toggle implements ComponentInterface {
|
||||
this.disabledChanged();
|
||||
}
|
||||
|
||||
@Listen('click')
|
||||
onClick() {
|
||||
this.checked = !this.checked;
|
||||
}
|
||||
|
||||
private emitStyle() {
|
||||
this.ionStyle.emit({
|
||||
'interactive-disabled': this.disabled,
|
||||
@@ -176,27 +159,34 @@ export class Toggle implements ComponentInterface {
|
||||
return this.value || '';
|
||||
}
|
||||
|
||||
private onFocus = () => {
|
||||
this.ionFocus.emit();
|
||||
}
|
||||
|
||||
private onBlur = () => {
|
||||
this.ionBlur.emit();
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const labelId = this.inputId + '-lbl';
|
||||
const label = findItemLabel(this.el);
|
||||
const { inputId, disabled, checked, activated, color, el } = this;
|
||||
const labelId = inputId + '-lbl';
|
||||
const label = findItemLabel(el);
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
|
||||
return {
|
||||
'role': 'checkbox',
|
||||
'tabindex': '0',
|
||||
'aria-disabled': this.disabled ? 'true' : null,
|
||||
'aria-checked': `${this.checked}`,
|
||||
'aria-disabled': disabled ? 'true' : null,
|
||||
'aria-checked': `${checked}`,
|
||||
'aria-labelledby': labelId,
|
||||
|
||||
class: {
|
||||
...createColorClasses(this.color),
|
||||
'in-item': hostContext('ion-item', this.el),
|
||||
'toggle-activated': this.activated,
|
||||
'toggle-checked': this.checked,
|
||||
'toggle-disabled': this.disabled,
|
||||
'toggle-key': this.keyFocus,
|
||||
...createColorClasses(color),
|
||||
'in-item': hostContext('ion-item', el),
|
||||
'toggle-activated': activated,
|
||||
'toggle-checked': checked,
|
||||
'toggle-disabled': disabled,
|
||||
'interactive': true
|
||||
}
|
||||
};
|
||||
@@ -206,11 +196,18 @@ export class Toggle implements ComponentInterface {
|
||||
const value = this.getValue();
|
||||
renderHiddenInput(true, this.el, this.name, (this.checked ? value : ''), this.disabled);
|
||||
|
||||
return (
|
||||
return [
|
||||
<div class="toggle-icon">
|
||||
<div class="toggle-inner"/>
|
||||
</div>
|
||||
);
|
||||
</div>,
|
||||
<button
|
||||
type="button"
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
disabled={this.disabled}
|
||||
>
|
||||
</button>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
46
core/src/utils/focus-visible.ts
Normal file
46
core/src/utils/focus-visible.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
const ION_FOCUSED = 'ion-focused';
|
||||
const ION_FOCUSABLE = 'ion-focusable';
|
||||
const FOCUS_KEYS = ['Tab', 'ArrowDown', 'Space', 'Escape', ' ', 'Shift', 'Enter', 'ArrowLeft', 'ArrowRight', 'ArrowUp'];
|
||||
|
||||
export function startFocusVisible(doc: Document) {
|
||||
|
||||
let currentFocus: Element[] = [];
|
||||
let keyboardMode = true;
|
||||
|
||||
function setFocus(elements: Element[]) {
|
||||
currentFocus.forEach(el => el.classList.remove(ION_FOCUSED));
|
||||
elements.forEach(el => el.classList.add(ION_FOCUSED));
|
||||
currentFocus = elements;
|
||||
}
|
||||
|
||||
doc.addEventListener('keydown', ev => {
|
||||
keyboardMode = FOCUS_KEYS.includes(ev.key);
|
||||
if (!keyboardMode) {
|
||||
setFocus([]);
|
||||
}
|
||||
});
|
||||
|
||||
const pointerDown = () => {
|
||||
keyboardMode = false;
|
||||
setFocus([]);
|
||||
};
|
||||
doc.addEventListener('focusin', ev => {
|
||||
if (keyboardMode && ev.composedPath) {
|
||||
const toFocus = ev.composedPath().filter((el: any) => {
|
||||
if (el.classList) {
|
||||
return el.classList.contains(ION_FOCUSABLE);
|
||||
}
|
||||
return false;
|
||||
}) as Element[];
|
||||
setFocus(toFocus);
|
||||
}
|
||||
});
|
||||
doc.addEventListener('focusout', () => {
|
||||
if (doc.activeElement === doc.body) {
|
||||
setFocus([]);
|
||||
}
|
||||
});
|
||||
doc.addEventListener('touchstart', pointerDown);
|
||||
doc.addEventListener('mousedown', pointerDown);
|
||||
}
|
||||
@@ -25,6 +25,18 @@ export function createOverlay<T extends HTMLIonOverlayElement>(element: T, opts:
|
||||
export function connectListeners(doc: Document) {
|
||||
if (lastId === 0) {
|
||||
lastId = 1;
|
||||
// trap focus inside overlays
|
||||
doc.addEventListener('focusin', ev => {
|
||||
const lastOverlay = getOverlay(doc);
|
||||
if (lastOverlay && lastOverlay.backdropDismiss && !isDescendant(lastOverlay, ev.target as HTMLElement)) {
|
||||
const firstInput = lastOverlay.querySelector('input,button') as HTMLElement | null;
|
||||
if (firstInput) {
|
||||
firstInput.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// handle back-button click
|
||||
doc.addEventListener('ionBackButton', ev => {
|
||||
const lastOverlay = getOverlay(doc);
|
||||
if (lastOverlay && lastOverlay.backdropDismiss) {
|
||||
@@ -34,6 +46,7 @@ export function connectListeners(doc: Document) {
|
||||
}
|
||||
});
|
||||
|
||||
// handle ESC to close overlay
|
||||
doc.addEventListener('keyup', ev => {
|
||||
if (ev.key === 'Escape') {
|
||||
const lastOverlay = getOverlay(doc);
|
||||
@@ -195,4 +208,14 @@ export function isCancel(role: string | undefined): boolean {
|
||||
return role === 'cancel' || role === BACKDROP;
|
||||
}
|
||||
|
||||
function isDescendant(parent: HTMLElement, child: HTMLElement | null) {
|
||||
while (child) {
|
||||
if (child === parent) {
|
||||
return true;
|
||||
}
|
||||
child = child.parentElement;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const BACKDROP = 'backdrop';
|
||||
|
||||
@@ -19,6 +19,7 @@ export function startTapClick(doc: Document, config: Config) {
|
||||
if (cancelled || scrolling) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
cancelled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +49,7 @@ export function startTapClick(doc: Document, config: Config) {
|
||||
}
|
||||
|
||||
function cancelActive() {
|
||||
console.log('cancelActive()');
|
||||
clearTimeout(activeDefer);
|
||||
activeDefer = undefined;
|
||||
if (activatableEle) {
|
||||
|
||||
Reference in New Issue
Block a user