Skip to content

Commit

Permalink
chore: refactor slider to extend BaseFormComponent and separate out…
Browse files Browse the repository at this point in the history
… validity functionality into separate base class
  • Loading branch information
DRiFTy17 committed Nov 9, 2023
1 parent d81fd31 commit e2dcc57
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 72 deletions.
4 changes: 3 additions & 1 deletion src/dev/pages/slider/slider.ejs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<forge-slider id="slider"></forge-slider>
<form>
<forge-slider id="slider"></forge-slider>
</form>

<script type="module" src="slider.ts"></script>
7 changes: 4 additions & 3 deletions src/lib/checkbox/checkbox-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getShadowElement, toggleAttribute } from '@tylertech/forge-core';
import { internals } from '../constants';
import { BaseAdapter, IBaseAdapter, INPUT_PROPERTIES, SlottedElementAdapter, cloneAttributes, cloneProperties, cloneValidationMessage, forwardAttributes } from '../core';
import { StateLayerComponent } from '../state-layer';
import { ICheckboxComponent } from './checkbox';
Expand Down Expand Up @@ -140,16 +141,16 @@ export class CheckboxAdapter extends BaseAdapter<ICheckboxComponent> implements

public syncValidity(hasCustomValidityError: boolean): void {
if (hasCustomValidityError) {
this._activeInputElement.setCustomValidity(this._component.internals.validationMessage);
this._activeInputElement.setCustomValidity(this._component[internals].validationMessage);
} else {
this._activeInputElement.setCustomValidity('');
}

this._component.internals.setValidity(this._activeInputElement.validity, this._activeInputElement.validationMessage, this._activeInputElement);
this._component[internals].setValidity(this._activeInputElement.validity, this._activeInputElement.validationMessage, this._activeInputElement);
}

public setValidity(flags?: ValidityStateFlags | undefined, message?: string | undefined): void {
this._component.internals.setValidity(flags, message, this._activeInputElement);
this._component[internals].setValidity(flags, message, this._activeInputElement);
}

private _initializeInput(): void {
Expand Down
28 changes: 15 additions & 13 deletions src/lib/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CustomElement, FoundationProperty, attachShadowTemplate, coerceBoolean, isDefined, isString, toggleAttribute } from '@tylertech/forge-core';
import { BaseFormComponent, IBaseFormComponent } from '../core';
import { internals } from '../constants';
import { BaseNullableFormComponent, IBaseNullableFormComponent } from '../core';
import { FocusIndicatorComponent } from '../focus-indicator/focus-indicator';
import { ILabelAware } from '../label/label-aware';
import { StateLayerComponent } from '../state-layer/state-layer';
Expand All @@ -10,13 +11,14 @@ import { CheckboxFoundation } from './checkbox-foundation';
import template from './checkbox.html';
import styles from './checkbox.scss';

export interface ICheckboxComponent extends IBaseFormComponent, ILabelAware {
export interface ICheckboxComponent extends IBaseNullableFormComponent, ILabelAware {
checked: boolean;
defaultChecked: boolean;
indeterminate: boolean;
dense: boolean;
labelPosition: CheckboxLabelPosition;
toggle(force?: boolean): void;
setFormValue(value: string | File | FormData | null, state?: string | File | FormData | null | undefined): void;
}

declare global {
Expand Down Expand Up @@ -112,7 +114,7 @@ declare global {
StateLayerComponent
]
})
export class CheckboxComponent extends BaseFormComponent implements ICheckboxComponent {
export class CheckboxComponent extends BaseNullableFormComponent implements ICheckboxComponent {
public static get observedAttributes(): string[] {
return [
CHECKBOX_CONSTANTS.attributes.CHECKED,
Expand All @@ -135,34 +137,34 @@ export class CheckboxComponent extends BaseFormComponent implements ICheckboxCom
}

public get form(): HTMLFormElement | null {
return this.internals.form;
return this[internals].form;
}

public get labels(): NodeList {
return this.internals.labels;
return this[internals].labels;
}

public get validity(): ValidityState {
this._foundation.syncValidity(this._hasCustomValidityError);
return this.internals.validity;
return this[internals].validity;
}

public get validationMessage(): string {
this._foundation.syncValidity(this._hasCustomValidityError);
return this.internals.validationMessage;
return this[internals].validationMessage;
}

public get willValidate(): boolean {
return this.internals.willValidate;
return this[internals].willValidate;
}

public readonly internals: ElementInternals;
public readonly [internals]: ElementInternals;
private _foundation: CheckboxFoundation;

constructor() {
super();
attachShadowTemplate(this, template, styles, true);
this.internals = this.attachInternals();
this[internals] = this.attachInternals();
this._foundation = new CheckboxFoundation(new CheckboxAdapter(this));
}

Expand Down Expand Up @@ -203,7 +205,7 @@ export class CheckboxComponent extends BaseFormComponent implements ICheckboxCom
}

public setFormValue(value: string | File | FormData | null, state?: string | File | FormData | null | undefined): void {
this.internals.setFormValue(value, state);
this[internals].setFormValue(value, state);

if (state) {
const stateValue = isString(state) ? state : state[this.name];
Expand All @@ -223,12 +225,12 @@ export class CheckboxComponent extends BaseFormComponent implements ICheckboxCom

public checkValidity(): boolean {
this._foundation.syncValidity(this._hasCustomValidityError);
return this.internals.checkValidity();
return this[internals].checkValidity();
}

public reportValidity(): boolean {
this._foundation.syncValidity(this._hasCustomValidityError);
return this.internals.reportValidity();
return this[internals].reportValidity();
}

public setCustomValidity(error: string): void {
Expand Down
46 changes: 29 additions & 17 deletions src/lib/core/base/base-component.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
import { internals } from '../../constants';

export interface IBaseComponent extends HTMLElement {}

/** Any Custom HTML element. Some elements directly implement this interface, while others implement it via an interface that inherits it. */
export abstract class BaseComponent extends HTMLElement implements IBaseComponent {}

export interface IBaseFormComponent<T = string> extends IBaseComponent {
export interface IBaseFormComponent<T = string, K = T> extends IBaseComponent {
value: T;
disabled: boolean;
required: boolean;
readonly: boolean;
name: string;

readonly form: HTMLFormElement | null;
readonly labels: NodeList;
readonly validity: ValidityState;
readonly validationMessage: string;
readonly willValidate: boolean;
readonly internals: ElementInternals;
setFormValue(value: string | File | FormData | null, state?: string | File | FormData | null | undefined): void;
checkValidity(): boolean;
reportValidity(): boolean;
setCustomValidity(error: string): void;
readonly [internals]: ElementInternals;

formResetCallback(): void;
formStateRestoreCallback(state: T, reason: 'restore' | 'autocomplete'): void;
formStateRestoreCallback(state: unknown, reason: 'restore' | 'autocomplete'): void;
formDisabledCallback(isDisabled: boolean): void;
}

Expand All @@ -30,26 +26,42 @@ export abstract class BaseFormComponent<T = string> extends BaseComponent implem

public abstract value: T;
public abstract disabled: boolean;
public abstract required: boolean;
public abstract readonly: boolean;
public abstract name: string;

public abstract get form(): HTMLFormElement | null;
public abstract get labels(): NodeList;
public abstract get [internals](): ElementInternals;

public abstract formResetCallback(): void;
public abstract formStateRestoreCallback(state: unknown, reason: 'restore' | 'autocomplete'): void;
public abstract formDisabledCallback(isDisabled: boolean): void;
}

export interface IBaseNullableFormComponent<T = string> extends IBaseFormComponent<T> {
required: boolean;

readonly validity: ValidityState;
readonly validationMessage: string;
readonly willValidate: boolean;

checkValidity(): boolean;
reportValidity(): boolean;
setCustomValidity(error: string): void;
}

export abstract class BaseNullableFormComponent<T = string> extends BaseFormComponent<T> {
public abstract required: boolean;

public abstract get validity(): ValidityState;
public abstract get validationMessage(): string;
public abstract get willValidate(): boolean;
public abstract get internals(): ElementInternals;

// Needed for Safari, see https://bugs.webkit.org/show_bug.cgi?id=261432
// Replace with this.internals.validity.customError when resolved.
protected _hasCustomValidityError = false;

public abstract setFormValue(value: string | File | FormData | null, state?: string | File | FormData | null | undefined): void;
public abstract checkValidity(): boolean;
public abstract reportValidity(): boolean;
public abstract setCustomValidity(error: string): void;
public abstract formResetCallback(): void;
public abstract formStateRestoreCallback(state: T, reason: 'restore' | 'autocomplete'): void;
public abstract formDisabledCallback(isDisabled: boolean): void;
}
5 changes: 3 additions & 2 deletions src/lib/slider/slider-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getShadowElement, toggleAttribute, toggleClass } from '@tylertech/forge-core';
import { internals } from '../constants';
import { BaseAdapter, IBaseAdapter } from '../core/base/base-adapter';
import { elementsOverlapping, isPointerOverElement } from '../core/utils/utils';
import { ISliderComponent } from '../slider';
Expand Down Expand Up @@ -133,9 +134,9 @@ export class SliderAdapter extends BaseAdapter<ISliderComponent> {
const data = new FormData();
data.append(this._component.nameStart, String(valueStart));
data.append(this._component.nameEnd, String(valueEnd));
this._component.internals.setFormValue(data);
this._component[internals].setFormValue(data);
} else {
this._component.internals.setFormValue(String(valueEnd));
this._component[internals].setFormValue(String(valueEnd));
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/lib/slider/slider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TestHarness } from '../../test/utils/test-harness';
import { ISliderComponent } from '../slider';
import { SLIDER_CONSTANTS } from './slider-constants';
import type { IStateLayerComponent } from '../state-layer/state-layer';
import { internals } from '../constants';

import './slider';

Expand Down Expand Up @@ -550,7 +551,7 @@ describe('Slider', () => {
const form = await fixture<HTMLFormElement>(html`<form name="test-form"></form>`);

const slider = document.createElement('forge-slider');
const setFormValueSpy = spy(slider.internals, 'setFormValue');
const setFormValueSpy = spy(slider[internals], 'setFormValue');
slider.name = 'test-slider';
slider.setAttribute('value', '75');
form.appendChild(slider);
Expand All @@ -574,7 +575,7 @@ describe('Slider', () => {
const form = await fixture<HTMLFormElement>(html`<form name="test-form"></form>`);

const slider = document.createElement('forge-slider');
const setFormValueSpy = spy(slider.internals, 'setFormValue');
const setFormValueSpy = spy(slider[internals], 'setFormValue');
slider.range = true;
slider.nameStart = 'test-slider-start';
slider.nameEnd = 'test-slider-end';
Expand Down
26 changes: 12 additions & 14 deletions src/lib/slider/slider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { attachShadowTemplate, coerceBoolean, coerceNumber, CustomElement, FoundationProperty, toggleAttribute } from '@tylertech/forge-core';
import { BaseComponent, IBaseComponent } from '../core/base/base-component';
import { internals } from '../constants';
import { BaseFormComponent, IBaseFormComponent } from '../core/base/base-component';
import { FocusIndicatorComponent } from '../focus-indicator/focus-indicator';
import { StateLayerComponent } from '../state-layer/state-layer';
import { SliderAdapter } from './slider-adapter';
Expand All @@ -9,8 +10,7 @@ import { SliderFoundation } from './slider-foundation';
import template from './slider.html';
import styles from './slider.scss';

export interface ISliderComponent extends IBaseComponent {
value: number;
export interface ISliderComponent extends IBaseFormComponent<number, string | Array<[string, string]> | null> {
valueStart: number;
valueEnd: number;
label: string;
Expand All @@ -23,14 +23,8 @@ export interface ISliderComponent extends IBaseComponent {
range: boolean;
tickmarks: boolean;
labeled: boolean;
disabled: boolean;
readonly: boolean;
form: HTMLFormElement | null;
name: string;
nameStart: string;
nameEnd: string;
labels: NodeList;
internals: ElementInternals;
}

declare global {
Expand Down Expand Up @@ -143,7 +137,7 @@ declare global {
StateLayerComponent
]
})
export class SliderComponent extends BaseComponent implements ISliderComponent {
export class SliderComponent extends BaseFormComponent<number> implements ISliderComponent {
public static get observedAttributes(): string[] {
return [
SLIDER_CONSTANTS.attributes.ARIA_LABEL,
Expand All @@ -169,11 +163,11 @@ export class SliderComponent extends BaseComponent implements ISliderComponent {
public static formAssociated = true;

public get form(): HTMLFormElement | null {
return this.internals.form;
return this[internals].form;
}

public get labels(): NodeList {
return this.internals.labels;
return this[internals].labels;
}

public get name(): string {
Expand All @@ -197,13 +191,13 @@ export class SliderComponent extends BaseComponent implements ISliderComponent {
toggleAttribute(this, !!value, 'name-end', value ?? '');
}

public readonly internals: ElementInternals;
public readonly [internals]: ElementInternals;
private readonly _foundation: SliderFoundation;

constructor() {
super();
attachShadowTemplate(this, template, styles);
this.internals = this.attachInternals();
this[internals] = this.attachInternals();
this._foundation = new SliderFoundation(new SliderAdapter(this));
}

Expand Down Expand Up @@ -293,6 +287,10 @@ export class SliderComponent extends BaseComponent implements ISliderComponent {
this.range = false;
}

public formDisabledCallback(isDisabled: boolean): void {
this.disabled = isDisabled;
}

@FoundationProperty()
public declare value: number;

Expand Down
11 changes: 6 additions & 5 deletions src/lib/switch/switch-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getShadowElement, toggleAttribute, toggleClass } from '@tylertech/forge-core';
import { internals } from '../constants';
import { BaseAdapter, IBaseAdapter, INPUT_PROPERTIES, SlottedElementAdapter, cloneAttributes, cloneProperties, cloneValidationMessage, forwardAttributes } from '../core';
import { StateLayerComponent } from '../state-layer';
import { ISwitchComponent } from './switch';
Expand Down Expand Up @@ -138,27 +139,27 @@ export class SwitchAdapter extends BaseAdapter<ISwitchComponent> implements ISwi

public syncValue(value: string | null): void {
if (value === null) {
this._component.internals.setFormValue(null, SWITCH_CONSTANTS.state.OFF);
this._component[internals].setFormValue(null, SWITCH_CONSTANTS.state.OFF);
return;
}

const data = new FormData();
data.append(this._component.name, value);
this._component.internals.setFormValue(data, SWITCH_CONSTANTS.state.ON);
this._component[internals].setFormValue(data, SWITCH_CONSTANTS.state.ON);
}

public syncValidity(hasCustomValidityError: boolean): void {
if (hasCustomValidityError) {
this._activeInputElement.setCustomValidity(this._component.internals.validationMessage);
this._activeInputElement.setCustomValidity(this._component[internals].validationMessage);
} else {
this._activeInputElement.setCustomValidity('');
}

this._component.internals.setValidity(this._activeInputElement.validity, this._activeInputElement.validationMessage, this._activeInputElement);
this._component[internals].setValidity(this._activeInputElement.validity, this._activeInputElement.validationMessage, this._activeInputElement);
}

public setValidity(flags?: ValidityStateFlags | undefined, message?: string | undefined): void {
this._component.internals.setValidity(flags, message, this._activeInputElement);
this._component[internals].setValidity(flags, message, this._activeInputElement);
}

private _initializeInput(): void {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/switch/switch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TestHarness } from '../../test/utils/test-harness';
import { IFocusIndicatorComponent } from '../focus-indicator';
import { IStateLayerComponent } from '../state-layer';
import { ISwitchComponent, SWITCH_CONSTANTS } from '../switch';
import { internals } from '../constants';

class SwitchHarness extends TestHarness<ISwitchComponent> {
public rootElement: HTMLElement;
Expand Down Expand Up @@ -340,7 +341,7 @@ describe('Switch', () => {
const form = await fixture<HTMLFormElement>(html`<form name="test-form"></form>`);

const switchEl = document.createElement('forge-switch');
const setFormValueSpy = spy(switchEl.internals, 'setFormValue');
const setFormValueSpy = spy(switchEl[internals], 'setFormValue');
switchEl.name = 'test-switch';
switchEl.toggleAttribute('on', true)
form.appendChild(switchEl);
Expand Down
Loading

0 comments on commit e2dcc57

Please sign in to comment.