fix(angular): apply validation classes properly

* fix(angular): add validation classes to ion-item

* fix(inputs): focus handling

fixes #17171
fixes #16052
fixes #15572
fixes #16452
fixes #17063
This commit is contained in:
Manu MA
2019-01-19 22:56:00 +01:00
committed by GitHub
parent 7832be3f1c
commit 2b4d7b7be9
14 changed files with 241 additions and 74 deletions

View File

@ -15,6 +15,7 @@ import { createColorClasses, hostContext } from '../../utils/theme';
export class Checkbox implements ComponentInterface {
private inputId = `ion-cb-${checkboxIds++}`;
private buttonEl?: HTMLElement;
@Element() el!: HTMLElement;
@ -98,9 +99,16 @@ export class Checkbox implements ComponentInterface {
@Listen('click')
onClick() {
this.setFocus();
this.checked = !this.checked;
}
private setFocus() {
if (this.buttonEl) {
this.buttonEl.focus();
}
}
private onFocus = () => {
this.ionFocus.emit();
}
@ -146,6 +154,7 @@ export class Checkbox implements ComponentInterface {
onFocus={this.onFocus}
onBlur={this.onBlur}
disabled={this.disabled}
ref={el => this.buttonEl = el}
>
</button>
];

View File

@ -246,6 +246,7 @@ export class Datetime implements ComponentInterface {
@Listen('click')
onClick() {
this.setFocus();
this.open();
}

View File

@ -8,13 +8,13 @@
$input-md-font-size: inherit !default;
/// @prop - Margin top of the input
$input-md-padding-top: $item-md-padding-top !default;
$input-md-padding-top: 10px !default;
/// @prop - Margin end of the input
$input-md-padding-end: 0 !default;
/// @prop - Margin bottom of the input
$input-md-padding-bottom: $item-md-padding-bottom !default;
$input-md-padding-bottom: 10px !default;
/// @prop - Margin start of the input
$input-md-padding-start: ($item-md-padding-start / 2) !default;

View File

@ -284,9 +284,8 @@ button, a {
// Item Input Focused
// --------------------------------------------------
:host(.item-interactive.item-has-focus) {
--highlight-background: var(--highlight-color-focused);
:host(.item-interactive.item-has-focus),
:host(.item-interactive.ion-touched.ion-invalid) {
// If the item has a full border and highlight is enabled, show the full item highlight
--full-highlight-height: #{calc(var(--highlight-height) * var(--show-full-highlight))};
@ -294,6 +293,12 @@ button, a {
--inset-highlight-height: #{calc(var(--highlight-height) * var(--show-inset-highlight))};
}
// Item Input Focus
// --------------------------------------------------
:host(.item-interactive.item-has-focus) {
--highlight-background: var(--highlight-color-focused);
}
// Item Input Valid
// --------------------------------------------------
@ -302,7 +307,6 @@ button, a {
--highlight-background: var(--highlight-color-valid);
}
// Item Input Invalid
// --------------------------------------------------

View File

@ -1,4 +1,4 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Prop, QueueApi, State, Watch } from '@stencil/core';
import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Prop, QueueApi, State, Watch } from '@stencil/core';
import { Color, Gesture, GestureDetail, KnobName, Mode, RangeChangeEventDetail, RangeValue, StyleEventDetail } from '../../interface';
import { clamp, debounceEvent } from '../../utils/helpers';
@ -145,6 +145,24 @@ export class Range implements ComponentInterface {
*/
@Event() ionBlur!: EventEmitter<void>;
@Listen('focusout')
onBlur() {
if (this.hasFocus) {
this.hasFocus = false;
this.ionBlur.emit();
this.emitStyle();
}
}
@Listen('focusin')
onFocus() {
if (!this.hasFocus) {
this.hasFocus = true;
this.ionFocus.emit();
this.emitStyle();
}
}
componentWillLoad() {
this.updateRatio();
this.debounceChanged();
@ -165,7 +183,7 @@ export class Range implements ComponentInterface {
this.gesture.setDisabled(this.disabled);
}
private handleKeyboard = (knob: string, isIncrease: boolean) => {
private handleKeyboard = (knob: KnobName, isIncrease: boolean) => {
let step = this.step;
step = step > 0 ? step : 1;
step = step / (this.max - this.min);
@ -200,29 +218,12 @@ export class Range implements ComponentInterface {
private emitStyle() {
this.ionStyle.emit({
'interactive': true,
'interactive-disabled': this.disabled
});
}
private fireBlur() {
if (this.hasFocus) {
this.hasFocus = false;
this.ionBlur.emit();
this.emitStyle();
}
}
private fireFocus() {
if (!this.hasFocus) {
this.hasFocus = true;
this.ionFocus.emit();
this.emitStyle();
}
}
private onStart(detail: GestureDetail) {
this.fireFocus();
const rect = this.rect = this.rangeSlider!.getBoundingClientRect() as any;
const currentX = detail.currentX;
@ -234,6 +235,8 @@ export class Range implements ComponentInterface {
? 'A'
: 'B';
this.setFocus(this.pressedKnob);
// update the active knob's position
this.update(currentX);
}
@ -245,7 +248,6 @@ export class Range implements ComponentInterface {
private onEnd(detail: GestureDetail) {
this.update(detail.currentX);
this.pressedKnob = undefined;
this.fireBlur();
}
private update(currentX: number) {
@ -255,8 +257,11 @@ export class Range implements ComponentInterface {
let ratio = clamp(0, (currentX - rect.left) / rect.width, 1);
if (this.snaps) {
// snaps the ratio to the current value
const value = ratioToValue(ratio, this.min, this.max, this.step);
ratio = valueToRatio(value, this.min, this.max);
ratio = valueToRatio(
ratioToValue(ratio, this.min, this.max, this.step),
this.min,
this.max
);
}
// update which knob is pressed
@ -317,6 +322,15 @@ export class Range implements ComponentInterface {
this.noUpdate = false;
}
private setFocus(knob: KnobName) {
if (this.el.shadowRoot) {
const knobEl = this.el.shadowRoot.querySelector(knob === 'A' ? '.range-knob-a' : '.range-knob-b') as HTMLElement | undefined;
if (knobEl) {
knobEl.focus();
}
}
}
hostData() {
return {
class: {
@ -401,7 +415,7 @@ export class Range implements ComponentInterface {
}
interface RangeKnob {
knob: string;
knob: KnobName;
value: number;
ratio: number;
min: number;
@ -410,7 +424,7 @@ interface RangeKnob {
pressed: boolean;
pin: boolean;
handleKeyboard: (name: string, isIncrease: boolean) => void;
handleKeyboard: (name: KnobName, isIncrease: boolean) => void;
}
function renderKnob({ knob, value, ratio, min, max, disabled, pressed, pin, handleKeyboard }: RangeKnob) {
@ -431,6 +445,8 @@ function renderKnob({ knob, value, ratio, min, max, disabled, pressed, pin, hand
}}
class={{
'range-knob-handle': true,
'range-knob-a': knob === 'A',
'range-knob-b': knob === 'B',
'range-knob-pressed': pressed,
'range-knob-min': value === min,
'range-knob-max': value === max

View File

@ -140,6 +140,7 @@ export class Select implements ComponentInterface {
@Listen('click')
onClick(ev: UIEvent) {
this.setFocus();
this.open(ev);
}

View File

@ -16,8 +16,9 @@ import { createColorClasses, hostContext } from '../../utils/theme';
export class Toggle implements ComponentInterface {
private inputId = `ion-tg-${toggleIds++}`;
private pivotX = 0;
private gesture?: Gesture;
private buttonEl?: HTMLElement;
private lastDrag = 0;
@Element() el!: HTMLElement;
@ -108,8 +109,9 @@ export class Toggle implements ComponentInterface {
queue: this.queue,
gestureName: 'toggle',
gesturePriority: 100,
threshold: 0,
onStart: ev => this.onStart(ev),
threshold: 5,
passive: false,
onStart: () => this.onStart(),
onMove: ev => this.onMove(ev),
onEnd: ev => this.onEnd(ev),
});
@ -118,7 +120,9 @@ export class Toggle implements ComponentInterface {
@Listen('click')
onClick() {
this.checked = !this.checked;
if (this.lastDrag + 300 < Date.now()) {
this.checked = !this.checked;
}
}
private emitStyle() {
@ -127,38 +131,37 @@ export class Toggle implements ComponentInterface {
});
}
private onStart(detail: GestureDetail) {
this.pivotX = detail.currentX;
private onStart() {
this.activated = true;
// touch-action does not work in iOS
detail.event.preventDefault();
return true;
this.setFocus();
}
private onMove(detail: GestureDetail) {
const currentX = detail.currentX;
if (shouldToggle(this.checked, currentX - this.pivotX, -15)) {
if (shouldToggle(this.checked, detail.deltaX, -10)) {
this.checked = !this.checked;
this.pivotX = currentX;
hapticSelection();
}
}
private onEnd(detail: GestureDetail) {
const delta = detail.currentX - this.pivotX;
if (shouldToggle(this.checked, delta, 4)) {
this.checked = !this.checked;
hapticSelection();
}
private onEnd(ev: GestureDetail) {
this.activated = false;
this.lastDrag = Date.now();
ev.event.preventDefault();
ev.event.stopImmediatePropagation();
}
private getValue() {
return this.value || '';
}
private setFocus() {
if (this.buttonEl) {
this.buttonEl.focus();
}
}
private onFocus = () => {
this.ionFocus.emit();
}
@ -205,6 +208,7 @@ export class Toggle implements ComponentInterface {
onFocus={this.onFocus}
onBlur={this.onBlur}
disabled={this.disabled}
ref={el => this.buttonEl = el}
>
</button>
];

View File

@ -5,7 +5,6 @@ import { now, pointerCoord } from './helpers';
export function startTapClick(doc: Document, config: Config) {
let lastTouch = -MOUSE_WAIT * 10;
let lastActivated = 0;
let cancelled = false;
let scrollingEl: HTMLElement | undefined;
let activatableEle: HTMLElement | undefined;
@ -51,11 +50,9 @@ export function startTapClick(doc: Document, config: Config) {
removeActivated(false);
activatableEle = undefined;
}
cancelled = true;
}
function pointerDown(ev: any) {
cancelled = false;
if (activatableEle || isScrolling()) {
return;
}
@ -64,13 +61,7 @@ export function startTapClick(doc: Document, config: Config) {
}
function pointerUp(ev: UIEvent) {
if (isScrolling()) {
return;
}
setActivatedElement(undefined, ev);
if (cancelled && ev.cancelable) {
ev.preventDefault();
}
}
function setActivatedElement(el: HTMLElement | undefined, ev: UIEvent) {