Skip to content

Commit

Permalink
feat(textfield): add the no-actions-sync attribute (#1013)
Browse files Browse the repository at this point in the history
* Add the no-actions-sync attribute

* Fix tests for Safari

* Add safari local testing command

* Change noSync to handle disabled state only

* Delete duplication
  • Loading branch information
YonatanKra authored Aug 11, 2021
1 parent 9a9938f commit 850297f
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 96 deletions.
1 change: 1 addition & 0 deletions components/textfield/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ This component is an extension of [\<mwc-textfield\>](https://github.com/materia
| `validityTransform` | | `((value: string, nativeValidity: ValidityState) => Partial<ValidityState>) \| null` | |
| `value` | | `string` | |
| `willValidate` | readonly | `boolean` | |
| `noActionsSync` | | `boolean` | Prevents auto sync between textfield attributes and action icon buttons attributes |

#### Methods

Expand Down
208 changes: 119 additions & 89 deletions components/textfield/src/vwc-textfield.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,40 @@ const MDC_FLOAT_ABOVE_CLASS_NAME = 'mdc-floating-label--float-above';

@customElement('vwc-textfield')
export class VWCTextField extends MWCTextField {
@property({ type: Boolean, reflect: true })
@property({
type: Boolean,
reflect: true,
attribute: 'no-actions-sync'
})
noActionsSync = false;

@property({
type: Boolean,
reflect: true
})
dense = false;

@property({ type: String, reflect: true })
@property({
type: String,
reflect: true
})
shape?: TextfieldShape;

@property({ type: String, reflect: true })
@property({
type: String,
reflect: true
})
form: string | undefined;

@property({ type: String, reflect: true, converter: v => v || ' ' })
@property({
type: String,
reflect: true,
converter: v => v || ' '
})
placeholder = ' ';

@query('.mdc-text-field__input') protected inputElementWrapper!: HTMLInputElement;
@internalProperty()
private hasActionButtons = false;

@query('.mdc-text-field__input') protected inputElementWrapper!: HTMLInputElement;

@queryAssignedNodes('action', true, VALID_BUTTON_ELEMENTS.join(', '))
private actionButtons?: NodeListOf<HTMLElement>;

Expand Down Expand Up @@ -101,8 +118,8 @@ export class VWCTextField extends MWCTextField {
await super.firstUpdated();
this.shadowRoot
?.querySelector('.mdc-notched-outline')
?.shadowRoot?.querySelector('.mdc-notched-outline')
?.classList.add('vvd-notch');
?.classList
.add('vvd-notch');
this.floatLabel();
handleAutofocus(this);
this.observeInputSize();
Expand All @@ -127,16 +144,38 @@ export class VWCTextField extends MWCTextField {
}
}

@debounced(50)
private syncInputSize() {
const { width: hostWidth, left: hostLeft } = this.getBoundingClientRect();
const { width: wrapperWidth, left: wrapperLeft } = this.inputElementWrapper.getBoundingClientRect();
const paddingLeft = wrapperLeft - hostLeft;
const paddingRight = hostWidth - wrapperWidth - paddingLeft;
requestAnimationFrame(() => {
this.formElement.style.paddingLeft = `${paddingLeft}px`;
this.formElement.style.paddingRight = `${paddingRight}px`;
});
/** @soyTemplate */
render(): TemplateResult {
const shouldRenderCharCounter = this.charCounter && this.maxLength !== -1;
const shouldRenderHelperText =
!!this.helper || !!this.validationMessage || shouldRenderCharCounter;

/** @classMap */
const classes = {
'mdc-text-field--disabled': this.disabled,
'mdc-text-field--no-label': !this.label,
'mdc-text-field--filled': !this.outlined,
'mdc-text-field--outlined': this.outlined,
'mdc-text-field--with-leading-icon': this.icon,
'mdc-text-field--with-trailing-icon': this.iconTrailing,
'mdc-text-field--end-aligned': this.endAligned,
'vvd-text-field--with-action': this.hasActionButtons,
};

return html`
<label class="mdc-text-field ${classMap(classes)}">
${this.renderRipple()}
${this.outlined ? this.renderOutline() : this.renderLabel()}
${this.renderLeadingIcon()}
${this.renderPrefix()}
${this.renderInput(shouldRenderHelperText)}
${this.renderSuffix()}
${this.renderTrailingIcon()}
<slot name="action" @slotchange="${this.onActionSlotChange}"></slot>
${this.renderLineRipple()}
</label>
${this.renderHelperText(shouldRenderHelperText)}
`;
}

protected observeInputSize(): void {
Expand Down Expand Up @@ -170,9 +209,10 @@ export class VWCTextField extends MWCTextField {
protected renderOutline(): TemplateResult | string {
return !this.outlined
? ''
: html`<vwc-notched-outline class="mdc-notched-outline vvd-notch">
: html`
<vwc-notched-outline class="mdc-notched-outline vvd-notch">
${this.renderLabel()}
</vwc-notched-outline>`;
</vwc-notched-outline>`;
}

protected renderHelperText(
Expand All @@ -185,13 +225,15 @@ export class VWCTextField extends MWCTextField {
const isError = this.validationMessage && !this.isUiValid;
const text = isError ? this.validationMessage : this.helper;

return html`<vwc-helper-message
id="helper-text"
class="helper-message"
?disabled="${this.disabled}"
?is-error="${isError}"
>${text}</vwc-helper-message
>`;
return html`
<vwc-helper-message
id="helper-text"
class="helper-message"
?disabled="${this.disabled}"
?is-error="${isError}"
>${text}
</vwc-helper-message
>`;
}

protected renderRipple(): TemplateResult {
Expand All @@ -202,6 +244,49 @@ export class VWCTextField extends MWCTextField {
return html``;
}

protected onActionSlotChange(): void {
this.hasActionButtons = Boolean(this.actionButtons?.length);
this.enforcePropsOnActionNodes();
}

protected enforcePropsOnActionNodes(): void {
const buttons = Array.from(this.actionButtons || []);

buttons.forEach((button) => {
if (!this.noActionsSync) {
button.toggleAttribute('disabled', this.disabled);
}
button.toggleAttribute('dense', this.dense);

const buttonShape = this.shape == 'pill'
? 'circled'
: this.shape;
if (buttonShape) {
button.setAttribute('shape', buttonShape);
} else {
button.removeAttribute('shape');
}
});
}

@debounced(50)
private syncInputSize() {
const {
width: hostWidth,
left: hostLeft
} = this.getBoundingClientRect();
const {
width: wrapperWidth,
left: wrapperLeft
} = this.inputElementWrapper.getBoundingClientRect();
const paddingLeft = wrapperLeft - hostLeft;
const paddingRight = hostWidth - wrapperWidth - paddingLeft;
requestAnimationFrame(() => {
this.formElement.style.paddingLeft = `${paddingLeft}px`;
this.formElement.style.paddingRight = `${paddingRight}px`;
});
}

private createInputElement(): HTMLInputElement {
const element = document.createElement('input');
const defaultValue = this.getAttribute('value');
Expand Down Expand Up @@ -275,63 +360,6 @@ export class VWCTextField extends MWCTextField {
fle.classList.remove(MDC_FLOAT_ABOVE_CLASS_NAME);
}
}

protected onActionSlotChange(): void {
this.hasActionButtons = Boolean(this.actionButtons?.length);
this.enforcePropsOnActionNodes();
}

protected enforcePropsOnActionNodes(): void {
const buttons = Array.from(this.actionButtons || []);

buttons.forEach((button) => {
button.toggleAttribute('disabled', this.disabled);
button.toggleAttribute('dense', this.dense);

const buttonShape = this.shape == 'pill'
? 'circled'
: this.shape;
if (buttonShape) {
button.setAttribute('shape', buttonShape);
} else {
button.removeAttribute('shape');
}
});
}

/** @soyTemplate */
render(): TemplateResult {
const shouldRenderCharCounter = this.charCounter && this.maxLength !== -1;
const shouldRenderHelperText =
!!this.helper || !!this.validationMessage || shouldRenderCharCounter;

/** @classMap */
const classes = {
'mdc-text-field--disabled': this.disabled,
'mdc-text-field--no-label': !this.label,
'mdc-text-field--filled': !this.outlined,
'mdc-text-field--outlined': this.outlined,
'mdc-text-field--with-leading-icon': this.icon,
'mdc-text-field--with-trailing-icon': this.iconTrailing,
'mdc-text-field--end-aligned': this.endAligned,
'vvd-text-field--with-action': this.hasActionButtons,
};

return html`
<label class="mdc-text-field ${classMap(classes)}">
${this.renderRipple()}
${this.outlined ? this.renderOutline() : this.renderLabel()}
${this.renderLeadingIcon()}
${this.renderPrefix()}
${this.renderInput(shouldRenderHelperText)}
${this.renderSuffix()}
${this.renderTrailingIcon()}
<slot name="action" @slotchange="${this.onActionSlotChange}"></slot>
${this.renderLineRipple()}
</label>
${this.renderHelperText(shouldRenderHelperText)}
`;
}
}

const setAttributeByValue = (function () {
Expand All @@ -342,11 +370,13 @@ const setAttributeByValue = (function () {
target: HTMLInputElement,
asEmpty = false
): void {
const newValue:unknown = value
? (() => { return asEmpty ? '' : String(value); })()
const newValue: unknown = value
? (() => {
return asEmpty ? '' : String(value);
})()
: NOT_ASSIGNED;

const currentValue:unknown = target.hasAttribute(attributeName)
const currentValue: unknown = target.hasAttribute(attributeName)
? target.getAttribute(attributeName)
: NOT_ASSIGNED;

Expand Down
Loading

0 comments on commit 850297f

Please sign in to comment.