Skip to content

Commit

Permalink
fix(elements): link input and textarea element to its label (#1041)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sl1nd authored Sep 7, 2023
1 parent e60cd8d commit 5ad5f90
Show file tree
Hide file tree
Showing 11 changed files with 75 additions and 24 deletions.
4 changes: 2 additions & 2 deletions packages/elements-angular/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,13 +554,13 @@ export declare interface InoLabel extends Components.InoLabel {}

@ProxyCmp({
defineCustomElementFn: undefined,
inputs: ['disabled', 'outline', 'required', 'showHint', 'text']
inputs: ['disabled', 'for', 'outline', 'required', 'showHint', 'text']
})
@Component({
selector: 'ino-label',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
inputs: ['disabled', 'outline', 'required', 'showHint', 'text']
inputs: ['disabled', 'for', 'outline', 'required', 'showHint', 'text']
})
export class InoLabel {
protected el: HTMLElement;
Expand Down
3 changes: 2 additions & 1 deletion packages/elements-vue/src/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ export const InoLabel = /*@__PURE__*/ defineContainer<JSX.InoLabel>('ino-label',
'text',
'required',
'showHint',
'disabled'
'disabled',
'for'
]);


Expand Down
8 changes: 8 additions & 0 deletions packages/elements/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,10 @@ export namespace Components {
* Colors the label in an light grey to indicate the disabled status for this element
*/
"disabled": boolean;
/**
* Id of the associated form control
*/
"for": string;
/**
* Styles the label in an outlined style
*/
Expand Down Expand Up @@ -2591,6 +2595,10 @@ declare namespace LocalJSX {
* Colors the label in an light grey to indicate the disabled status for this element
*/
"disabled"?: boolean;
/**
* Id of the associated form control
*/
"for"?: string;
/**
* Styles the label in an outlined style
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const CONTENT = `
<ino-input type="text"></ino-input>
</ino-currency-input>
`;
const NATIVE_INPUT_SELECTOR = 'ino-input > label > input';
const NATIVE_INPUT_SELECTOR = 'ino-input > .mdc-text-field > input';
const HIDDEN_INPUT_SELECTOR = 'input[type="hidden"]';

describe('InoCurrencyInput', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('InoDatepicker', () => {
const inputEl = await page.find(INPUT);
expect(inputEl).toBeDefined();

const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');
expect(flatpickrInputEl).toBeDefined();

const flatpickrCalEl = await page.find('.flatpickr-calendar');
Expand All @@ -34,7 +34,7 @@ describe('InoDatepicker', () => {
const inoDatepickerEl = await page.find(DATEPICKER);
expect(inoDatepickerEl).toBeDefined();

const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');
expect(flatpickrInputEl).not.toHaveClass('mdc-text-field--focused');

inoDatepickerEl.setAttribute('autofocus', 'true');
Expand Down Expand Up @@ -161,7 +161,7 @@ describe('InoDatepicker', () => {
const page = await setupPageWithContent(INO_DATEPICKER_WITH_SIBLING);
const inoDatepickerEl = await page.find(DATEPICKER);
const buttonEl = await page.find('button');
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('required', true);
await page.waitForChanges();
Expand Down Expand Up @@ -227,7 +227,7 @@ describe('InoDatepicker', () => {
it('should be invalid if wrong date format provided', async () => {
const page = await setupPageWithContent(INO_DATEPICKER_WITH_FORMAT);
const inoDatepickerEl = await page.find(DATEPICKER);
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('value', '01-1970-01');
await page.waitForChanges();
Expand All @@ -243,7 +243,7 @@ describe('InoDatepicker', () => {
it('should be valid if not required and empty value', async () => {
const page = await setupPageWithContent(INO_DATEPICKER);
const inoDatepickerEl = await page.find(DATEPICKER);
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('required', false);
inoDatepickerEl.setAttribute('value', '');
Expand All @@ -255,7 +255,7 @@ describe('InoDatepicker', () => {
it('should be invalid if value is set before min date', async () => {
const page = await setupPageWithContent(INO_DATEPICKER);
const inoDatepickerEl = await page.find(DATEPICKER);
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('date-format', 'd-m-Y');
inoDatepickerEl.setAttribute('min', '10-10-2020');
Expand All @@ -268,7 +268,7 @@ describe('InoDatepicker', () => {
it('should be valid if value is set to min date', async () => {
const page = await setupPageWithContent(INO_DATEPICKER);
const inoDatepickerEl = await page.find(DATEPICKER);
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('date-format', 'd-m-Y');
inoDatepickerEl.setAttribute('min', '10-10-2020');
Expand All @@ -281,7 +281,7 @@ describe('InoDatepicker', () => {
it('should be validated after min date is set', async () => {
const page = await setupPageWithContent(INO_DATEPICKER);
const inoDatepickerEl = await page.find(DATEPICKER);
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('date-format', 'd-m-Y');
inoDatepickerEl.setAttribute('value', '09-10-2020');
Expand All @@ -294,7 +294,7 @@ describe('InoDatepicker', () => {
it('should be invalid if value is set after max date', async () => {
const page = await setupPageWithContent(INO_DATEPICKER);
const inoDatepickerEl = await page.find(DATEPICKER);
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('date-format', 'd-m-Y');
inoDatepickerEl.setAttribute('max', '10-10-2020');
Expand All @@ -307,7 +307,7 @@ describe('InoDatepicker', () => {
it('should be valid if value is set to max date', async () => {
const page = await setupPageWithContent(INO_DATEPICKER);
const inoDatepickerEl = await page.find(DATEPICKER);
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('date-format', 'd-m-Y');
inoDatepickerEl.setAttribute('max', '10-10-2020');
Expand All @@ -320,7 +320,7 @@ describe('InoDatepicker', () => {
it('should be validated after max date is set', async () => {
const page = await setupPageWithContent(INO_DATEPICKER);
const inoDatepickerEl = await page.find(DATEPICKER);
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('date-format', 'd-m-Y');
inoDatepickerEl.setAttribute('value', '11-10-2020');
Expand All @@ -333,7 +333,7 @@ describe('InoDatepicker', () => {
it('should be valid with min and max date set', async () => {
const page = await setupPageWithContent(INO_DATEPICKER);
const inoDatepickerEl = await page.find(DATEPICKER);
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('date-format', 'd-m-Y');
inoDatepickerEl.setAttribute('min', '09-10-2020');
Expand All @@ -347,7 +347,7 @@ describe('InoDatepicker', () => {
it('should be invalid if wrong date format is used inside range', async () => {
const page = await setupPageWithContent(INO_DATEPICKER);
const inoDatepickerEl = await page.find(DATEPICKER);
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('date-format', 'd.m.Y');
inoDatepickerEl.setAttribute('range', 'true');
Expand All @@ -360,7 +360,7 @@ describe('InoDatepicker', () => {
it('should be invalid if incorrectly formatted value is provided in month-picker', async () => {
const page = await setupPageWithContent(INO_DATEPICKER);
const inoDatepickerEl = await page.find(DATEPICKER);
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('type', 'month');
inoDatepickerEl.setAttribute('date-format', 'm.Y');
Expand All @@ -373,7 +373,7 @@ describe('InoDatepicker', () => {
it('should be valid if correctly formatted value is provided in month-picker', async () => {
const page = await setupPageWithContent(INO_DATEPICKER);
const inoDatepickerEl = await page.find(DATEPICKER);
const flatpickrInputEl = await page.find('ino-input > label.mdc-text-field');
const flatpickrInputEl = await page.find('ino-input > span.mdc-text-field');

inoDatepickerEl.setAttribute('type', 'month');
inoDatepickerEl.setAttribute('date-format', 'm.Y');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('InoInput', () => {
page = await setupPageWithContent(`<ino-input></ino-input>`);
inoInputEl = await page.find('ino-input');
nativeInputEl = await page.find('input');
labelEl = await page.find('label');
labelEl = await page.find('.mdc-text-field');
});

async function applyToNativeHtmlElement(
Expand Down
14 changes: 11 additions & 3 deletions packages/elements/src/components/ino-input/ino-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
Method,
} from '@stencil/core';
import classNames from 'classnames';
import { hasSlotContent } from '../../util/component-utils';
import { generateUniqueId, hasSlotContent } from '../../util/component-utils';
import { getPrecision } from '../../util/math-utils';
import { InputType, UserInputInterceptor } from '../types';

Expand Down Expand Up @@ -357,6 +357,12 @@ export class Input implements ComponentInterface {
*/
@Prop() resetOnChange = true;


private inputID: string;

componentWillLoad() {
this.inputID = generateUniqueId();
}
// ----
// Native input event handler
// ----
Expand Down Expand Up @@ -475,8 +481,9 @@ export class Input implements ComponentInterface {

return (
<Host>
<label class={classTextfield}>
<span class={classTextfield}>
<ino-label
for={this.inputID}
outline={this.outline}
text={this.label}
required={this.required}
Expand All @@ -489,6 +496,7 @@ export class Input implements ComponentInterface {
</span>
)}
<input
id={this.inputID}
ref={(el) => (this.nativeInputEl = el)}
class="mdc-text-field__input"
autocomplete={this.autocomplete}
Expand Down Expand Up @@ -535,7 +543,7 @@ export class Input implements ComponentInterface {
<slot name={'icon-trailing'} />
</span>
)}
</label>
</span>
<div class="mdc-text-field-helper-line">
{hasHelperText && this.helperTextTemplate()}
{hasCharacterCounter && this.characterCounterTemplate()}
Expand Down
8 changes: 7 additions & 1 deletion packages/elements/src/components/ino-label/ino-label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ export class Label {
*/
@Prop() disabled: boolean;


/**
* Id of the associated form control
*/
@Prop() for: string;

private filledTemplate = (label: HTMLElement) => [
<div class="mdc-line-ripple" />,
label,
Expand All @@ -55,7 +61,7 @@ export class Label {
});

const label = this.text ? (
<label class={'mdc-floating-label'}>{this.text}</label>
<label htmlFor={this.for} class={'mdc-floating-label'}>{this.text}</label>
) : (
''
);
Expand Down
1 change: 1 addition & 0 deletions packages/elements/src/components/ino-label/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This is an internally used component for various sorts of inputs like `ino-input
| Property | Attribute | Description | Type | Default |
| ---------- | ----------- | ---------------------------------------------------------------------------------- | --------- | ----------- |
| `disabled` | `disabled` | Colors the label in an light grey to indicate the disabled status for this element | `boolean` | `undefined` |
| `for` | `for` | Id of the associated form control | `string` | `undefined` |
| `outline` | `outline` | Styles the label in an outlined style | `boolean` | `undefined` |
| `required` | `required` | Appends * to the label to make it appear as an required input in a form | `boolean` | `undefined` |
| `showHint` | `show-hint` | Shows a "optional" message, when not `required`; Shows a * mark, when `required` | `boolean` | `undefined` |
Expand Down
11 changes: 11 additions & 0 deletions packages/elements/src/components/ino-textarea/ino-textarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from '@stencil/core';
import autosize from 'autosize';
import classNames from 'classnames';
import { generateUniqueId } from '../../util/component-utils';

@Component({
tag: 'ino-textarea',
Expand Down Expand Up @@ -200,6 +201,8 @@ export class Textarea implements ComponentInterface {
e.preventDefault();
}

private inputID: string;

private initAutogrow() {
autosize(this.nativeTextareaElement);
}
Expand All @@ -223,6 +226,12 @@ export class Textarea implements ComponentInterface {
this.valueChange.emit(value);
}



componentWillLoad() {
this.inputID = generateUniqueId();
}

render() {
const hostClasses = classNames({
'ino-textarea--outline': this.outline,
Expand All @@ -243,6 +252,7 @@ export class Textarea implements ComponentInterface {
<div class={classes}>
<textarea
ref={(el) => (this.nativeTextareaElement = el)}
id={this.inputID}
class="mdc-text-field__input"
autofocus={this.autoFocus}
cols={this.cols}
Expand All @@ -264,6 +274,7 @@ export class Textarea implements ComponentInterface {
)}
<ino-label
outline={this.outline}
for={this.inputID}
text={this.label}
required={this.required}
disabled={this.disabled}
Expand Down
16 changes: 16 additions & 0 deletions packages/storybook/elements-stencil-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -4293,6 +4293,22 @@
"optional": false,
"required": false
},
{
"name": "for",
"type": "string",
"mutable": false,
"attr": "for",
"reflectToAttr": false,
"docs": "Id of the associated form control",
"docsTags": [],
"values": [
{
"type": "string"
}
],
"optional": false,
"required": false
},
{
"name": "outline",
"type": "boolean",
Expand Down

0 comments on commit 5ad5f90

Please sign in to comment.