fix(inputs): inputs work inside <form>

This commit is contained in:
Manu Mtz.-Almeida
2018-07-23 23:57:29 +02:00
parent 073f45c94b
commit 8324bd1f7f
10 changed files with 65 additions and 30 deletions

View File

@ -1,6 +1,6 @@
import { Component, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
import { CheckboxInput, CheckedInputChangeEvent, Color, Mode, StyleEvent } from '../../interface'; import { CheckboxInput, CheckedInputChangeEvent, Color, Mode, StyleEvent } from '../../interface';
import { deferEvent } from '../../utils/helpers'; import { deferEvent, renderHiddenInput } from '../../utils/helpers';
import { createColorClasses, hostContext } from '../../utils/theme'; import { createColorClasses, hostContext } from '../../utils/theme';
@ -129,6 +129,8 @@ export class Checkbox implements CheckboxInput {
} }
render() { render() {
renderHiddenInput(this.el, this.name, this.value, this.disabled);
return [ return [
<div class="checkbox-icon"> <div class="checkbox-icon">
<div class="checkbox-inner"></div> <div class="checkbox-inner"></div>

View File

@ -21,6 +21,7 @@
height: 100%; height: 100%;
/* stylelint-disable */ /* stylelint-disable */
/* TODO: find a better solution in padding.css, that does not require !important, */
margin: 0 !important; margin: 0 !important;
padding: 0 !important; padding: 0 !important;

View File

@ -18,6 +18,11 @@
align-items: center; align-items: center;
width: 100%; width: 100%;
/* stylelint-disable */
/* TODO: find a better solution in padding.css, that does not require !important, */
padding: 0 !important;
/* stylelint-enable */
} }
@ -55,15 +60,16 @@
font-family: inherit; font-family: inherit;
font-weight: var(--placeholder-weight); font-weight: var(--placeholder-weight);
} }
&:-webkit-autofill {
background-color: transparent;
}
} }
.native-input[disabled] { .native-input[disabled] {
opacity: .4; opacity: .4;
} }
.native-input:-webkit-autofill {
background-color: transparent;
}
// Input Cover: Unfocused // Input Cover: Unfocused
@ -99,16 +105,13 @@
border: 0; border: 0;
outline: none;
background-color: transparent; background-color: transparent;
background-repeat: no-repeat; background-repeat: no-repeat;
visibility: hidden; visibility: hidden;
appearance: none; appearance: none;
&:active,
&:focus {
outline: none;
}
} }
:host(.has-focus.has-value) .input-clear-icon { :host(.has-focus.has-value) .input-clear-icon {

View File

@ -1,6 +1,6 @@
import { Component, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
import { Color, InputChangeEvent, Mode, StyleEvent } from '../../interface'; import { Color, InputChangeEvent, Mode, StyleEvent } from '../../interface';
import { debounceEvent, deferEvent } from '../../utils/helpers'; import { debounceEvent, deferEvent, renderHiddenInput } from '../../utils/helpers';
import { hostContext } from '../../utils/theme'; import { hostContext } from '../../utils/theme';
import { InputComponent } from './input-base'; import { InputComponent } from './input-base';
@ -16,6 +16,7 @@ import { InputComponent } from './input-base';
export class Input implements InputComponent { export class Input implements InputComponent {
private nativeInput?: HTMLInputElement; private nativeInput?: HTMLInputElement;
private inputId = `ion-input-${inputIds++}`;
didBlurAfterEdit = false; didBlurAfterEdit = false;
mode!: Mode; mode!: Mode;
@ -148,7 +149,7 @@ export class Input implements InputComponent {
/** /**
* The name of the control, which is submitted with the form data. * The name of the control, which is submitted with the form data.
*/ */
@Prop() name?: string; @Prop() name: string = this.inputId;
/** /**
* A regular expression that the value is checked against. The pattern must match the entire value, not just some subset. Use the title attribute to describe the pattern to help the user. This attribute applies when the value of the type attribute is `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored. * A regular expression that the value is checked against. The pattern must match the entire value, not just some subset. Use the title attribute to describe the pattern to help the user. This attribute applies when the value of the type attribute is `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored.
@ -276,14 +277,10 @@ export class Input implements InputComponent {
} }
} }
private inputKeydown() {
this.checkClearOnEdit();
}
/** /**
* Check if we need to clear the text input if clearOnEdit is enabled * Check if we need to clear the text input if clearOnEdit is enabled
*/ */
private checkClearOnEdit() { private onKeydown() {
if (!this.clearOnEdit) { if (!this.clearOnEdit) {
return; return;
} }
@ -317,7 +314,7 @@ export class Input implements InputComponent {
} }
render() { render() {
// TODO aria-labelledby={this.item.labelId} renderHiddenInput(this.el, this.name, this.value, this.disabled);
return [ return [
<input <input
@ -350,8 +347,9 @@ export class Input implements InputComponent {
onInput={this.onInput.bind(this)} onInput={this.onInput.bind(this)}
onBlur={this.onBlur.bind(this)} onBlur={this.onBlur.bind(this)}
onFocus={this.onFocus.bind(this)} onFocus={this.onFocus.bind(this)}
onKeyDown={this.inputKeydown.bind(this)} onKeyDown={this.onKeydown.bind(this)}
/>, />,
<slot></slot>,
this.clearInput && <button this.clearInput && <button
type="button" type="button"
class="input-clear-icon" class="input-clear-icon"
@ -360,3 +358,5 @@ export class Input implements InputComponent {
]; ];
} }
} }
let inputIds = 0;

View File

@ -7,11 +7,17 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<script src="/dist/ionic.js"></script> <script src="/dist/ionic.js"></script>
<link rel="stylesheet" type="text/css" href="/css/ionic.min.css"> <link rel="stylesheet" type="text/css" href="/css/ionic.min.css">
<style>
ion-label {
color: red;
}
</style>
</head> </head>
<body> <body>
<ion-label>Default</ion-label> <ion-label>Default</ion-label>
<ion-label>Wrap label this label just goes on and on and on</ion-label> <ion-label color="primary">Wrap label this label just goes on and on and on</ion-label>
<ion-label position="fixed">Fixed</ion-label> <ion-label position="fixed">Fixed</ion-label>
<ion-label position="floating">Floating</ion-label> <ion-label position="floating">Floating</ion-label>
<ion-label position="stacked">Stacked</ion-label> <ion-label position="stacked">Stacked</ion-label>

View File

@ -1,6 +1,6 @@
import { Component, Element, Event, EventEmitter, Listen, Prop, State, Watch } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Listen, Prop, State, Watch } from '@stencil/core';
import { ActionSheetButton, ActionSheetOptions, AlertInput, AlertOptions, CssClassMap, Mode, PopoverOptions, SelectInputChangeEvent, SelectInterface, SelectPopoverOption, StyleEvent} from '../../interface'; import { ActionSheetButton, ActionSheetOptions, AlertInput, AlertOptions, CssClassMap, Mode, PopoverOptions, SelectInputChangeEvent, SelectInterface, SelectPopoverOption, StyleEvent} from '../../interface';
import { deferEvent } from '../../utils/helpers'; import { deferEvent, renderHiddenInput } from '../../utils/helpers';
import { createThemedClasses, hostContext } from '../../utils/theme'; import { createThemedClasses, hostContext } from '../../utils/theme';
@ -15,7 +15,7 @@ import { createThemedClasses, hostContext } from '../../utils/theme';
export class Select { export class Select {
private childOpts: HTMLIonSelectOptionElement[] = []; private childOpts: HTMLIonSelectOptionElement[] = [];
private selectId = `ion-sel-${selectIds++}`; private inputId = `ion-sel-${selectIds++}`;
private labelId?: string; private labelId?: string;
private overlay?: HTMLIonActionSheetElement | HTMLIonAlertElement | HTMLIonPopoverElement; private overlay?: HTMLIonActionSheetElement | HTMLIonAlertElement | HTMLIonPopoverElement;
@ -54,7 +54,7 @@ export class Select {
/** /**
* The name of the control, which is submitted with the form data. * The name of the control, which is submitted with the form data.
*/ */
@Prop({ mutable: true }) name?: string; @Prop() name: string = this.inputId;
/** /**
* The text to display instead of the selected option's value. * The text to display instead of the selected option's value.
@ -225,7 +225,6 @@ export class Select {
if (!this.value) { if (!this.value) {
this.value = this.multiple ? [] : undefined; this.value = this.multiple ? [] : undefined;
} }
this.name = this.name || this.selectId;
} }
componentDidLoad() { componentDidLoad() {
@ -460,6 +459,8 @@ export class Select {
} }
render() { render() {
renderHiddenInput(this.el, this.name, parseValue(this.value), this.disabled);
let addPlaceholderClass = false; let addPlaceholderClass = false;
let selectText = this.selectedText || this.text; let selectText = this.selectedText || this.text;
@ -496,8 +497,7 @@ export class Select {
class="select-cover"> class="select-cover">
<slot></slot> <slot></slot>
{ this.mode === 'md' && <ion-ripple-effect tapClick={true}/> } { this.mode === 'md' && <ion-ripple-effect tapClick={true}/> }
</button>, </button>
<input type="hidden" name={this.name} value={parseValue(this.value)}/>
]; ];
} }
} }

View File

@ -1,6 +1,6 @@
import { Component, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
import { Color, InputChangeEvent, Mode, StyleEvent } from '../../interface'; import { Color, InputChangeEvent, Mode, StyleEvent } from '../../interface';
import { debounceEvent, deferEvent } from '../../utils/helpers'; import { debounceEvent, deferEvent, renderHiddenInput } from '../../utils/helpers';
import { TextareaComponent } from '../input/input-base'; import { TextareaComponent } from '../input/input-base';
@ -15,6 +15,7 @@ import { TextareaComponent } from '../input/input-base';
export class Textarea implements TextareaComponent { export class Textarea implements TextareaComponent {
private inputEl?: HTMLTextAreaElement; private inputEl?: HTMLTextAreaElement;
private inputId = `ion-input-${textareaIds++}`;
mode!: Mode; mode!: Mode;
color?: Color; color?: Color;
@ -103,7 +104,7 @@ export class Textarea implements TextareaComponent {
/** /**
* The name of the control, which is submitted with the form data. * The name of the control, which is submitted with the form data.
*/ */
@Prop() name?: string; @Prop() name: string = this.inputId;
/** /**
* Instructional text that shows before the input has a value. * Instructional text that shows before the input has a value.
@ -229,6 +230,8 @@ export class Textarea implements TextareaComponent {
} }
render() { render() {
renderHiddenInput(this.el, this.name, this.value, this.disabled);
return ( return (
<textarea <textarea
class="native-textarea" class="native-textarea"
@ -257,3 +260,5 @@ export class Textarea implements TextareaComponent {
); );
} }
} }
let textareaIds = 0;

View File

@ -1,7 +1,7 @@
import { Component, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Prop, State, Watch } from '@stencil/core';
import { CheckboxInput, CheckedInputChangeEvent, Color, GestureDetail, Mode, StyleEvent } from '../../interface'; import { CheckboxInput, CheckedInputChangeEvent, Color, GestureDetail, Mode, StyleEvent } from '../../interface';
import { hapticSelection } from '../../utils/haptic'; import { hapticSelection } from '../../utils/haptic';
import { deferEvent } from '../../utils/helpers'; import { deferEvent, renderHiddenInput } from '../../utils/helpers';
import { createColorClasses, hostContext } from '../../utils/theme'; import { createColorClasses, hostContext } from '../../utils/theme';
@ -169,6 +169,8 @@ export class Toggle implements CheckboxInput {
} }
render() { render() {
renderHiddenInput(this.el, this.name, this.value, this.disabled);
return [ return [
<ion-gesture <ion-gesture
onStart={this.onDragStart.bind(this)} onStart={this.onDragStart.bind(this)}
@ -197,7 +199,8 @@ export class Toggle implements CheckboxInput {
name={this.name} name={this.name}
value={this.value} value={this.value}
disabled={this.disabled} disabled={this.disabled}
ref={r => this.nativeInput = (r as any)}/> ref={r => this.nativeInput = (r as any)}/>,
<slot></slot>
]; ];
} }
} }

View File

@ -8,6 +8,21 @@ export function reorderArray(array: any[], indexes: {from: number, to: number}):
return array; return array;
} }
export function renderHiddenInput(container: HTMLElement, name: string, value: string, disabled: boolean) {
if (container.shadowRoot) {
let input = container.querySelector('input.aux-input') as HTMLInputElement;
if (!input) {
input = container.ownerDocument.createElement('input');
input.type = 'hidden';
input.classList.add('aux-input');
container.appendChild(input);
}
input.disabled = disabled;
input.name = name;
input.value = value;
}
}
export function clamp(min: number, n: number, max: number) { export function clamp(min: number, n: number, max: number) {
return Math.max(min, Math.min(n, max)); return Math.max(min, Math.min(n, max));
} }

View File

@ -24,7 +24,7 @@ export function startInputShims(
const scrollAssistMap = new WeakMap<HTMLElement, Function>(); const scrollAssistMap = new WeakMap<HTMLElement, Function>();
function registerInput(componentEl: HTMLElement) { function registerInput(componentEl: HTMLElement) {
const inputEl = componentEl.querySelector('input'); const inputEl = (componentEl.shadowRoot || componentEl).querySelector('input');
const contentEl = componentEl.closest('ion-content'); const contentEl = componentEl.closest('ion-content');
const scrollEl = contentEl && contentEl.getScrollElement(); const scrollEl = contentEl && contentEl.getScrollElement();