Skip to content

Commit

Permalink
fix(label): adjust reactivity
Browse files Browse the repository at this point in the history
  • Loading branch information
samrichardsontylertech committed Oct 26, 2023
1 parent 11f812c commit c02072b
Show file tree
Hide file tree
Showing 17 changed files with 196 additions and 54 deletions.
20 changes: 18 additions & 2 deletions src/dev/pages/label/label.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,28 @@
</div>

<div>
<h3 class="forge-typography--subtitle1">Frozen</h3>
<forge-label freeze>
<h3 class="forge-typography--subtitle1">Dynamic</h3>
<forge-label dynamic>
<span>Label</span>
<forge-checkbox></forge-checkbox>
</forge-label>
</div>

<div>
<h3 class="forge-typography--subtitle1">Aligned list</h3>
<div class="list">
<forge-label for="one">nwiuh</forge-label>
<forge-checkbox id="one"></forge-checkbox>
<forge-label for="two">wefoinqwo</forge-label>
<forge-checkbox id="two"></forge-checkbox>
<forge-label for="three">qwdc</forge-label>
<forge-checkbox id="three"></forge-checkbox>
<forge-label for="four">oerncqeoicuh</forge-label>
<forge-checkbox id="four"></forge-checkbox>
<forge-label for="five">wed</forge-label>
<forge-checkbox id="five"></forge-checkbox>
</div>
</div>
</div>

<script type="module" src="label.ts"></script>
10 changes: 10 additions & 0 deletions src/dev/pages/label/label.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,13 @@ forge-label::part(label) {
display: flex;
align-items: center;
}

.list {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;

forge-label {
width: 100%;
}
}
1 change: 0 additions & 1 deletion src/lib/checkbox/checkbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { TestHarness } from '../../test/utils/test-harness';
import { CHECKBOX_CONSTANTS, ICheckboxComponent } from '../checkbox';
import { IFocusIndicatorComponent } from '../focus-indicator';
import { IStateLayerComponent } from '../state-layer';
import exp from 'constants';

class CheckboxHarness extends TestHarness<ICheckboxComponent> {
public rootElement: HTMLElement;
Expand Down
Empty file removed src/lib/label/_configuration.scss
Empty file.
6 changes: 0 additions & 6 deletions src/lib/label/_core.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
@use '../core/styles/typography';
@use '../core/styles/utils';
@use '../core/styles/tokens/label/tokens';

@mixin provide-theme($theme) {
@include utils.provide(tokens.$tokens, $theme, label);
}

@mixin host {
display: inline-block;
Expand Down
1 change: 0 additions & 1 deletion src/lib/label/index.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
@forward './configuration';
@forward './core';
4 changes: 3 additions & 1 deletion src/lib/label/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { defineCustomElement } from '@tylertech/forge-core';

import { LabelComponent } from './label';

export * from './label-adapter';
export * from './label-constants';
export * from './label-foundation';
export * from './label';
export * from './label-component-delegate';
export * from './label-aware';

export function defineLabelComponent(): void {
defineCustomElement(LabelComponent);
Expand Down
8 changes: 8 additions & 0 deletions src/lib/label/label-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getShadowElement } from '@tylertech/forge-core';
import { BaseAdapter, IBaseAdapter } from '../core';
import { ILabelComponent } from './label';
import { ILabelAware, isLabelAware } from './label-aware';
Expand All @@ -11,16 +12,19 @@ export interface ILabelAdapter extends IBaseAdapter {
trySetTarget(value: string | null): void;
clickTarget(): void;
updateTargetLabel(): void;
addSlotChangeListener(callback: EventListener): void;
addMutationObserver(callback: MutationCallback): void;
removeMutationObserver(): void;
}

export class LabelAdapter extends BaseAdapter<ILabelComponent> implements ILabelAdapter {
private _slotElement: HTMLElement;
private _targetElement: ILabelAware & HTMLElement | null = null;
private _mutationObserver?: MutationObserver;

constructor(component: ILabelComponent) {
super(component);
this._slotElement = getShadowElement(component, LABEL_CONSTANTS.selectors.SLOT);
}

public destroy(): void {
Expand Down Expand Up @@ -63,6 +67,10 @@ export class LabelAdapter extends BaseAdapter<ILabelComponent> implements ILabel
this._targetElement?.labelChangedCallback(value);
}

public addSlotChangeListener(callback: EventListener): void {
this._slotElement.addEventListener('slotchange', callback);
}

public addMutationObserver(callback: MutationCallback): void {
this._mutationObserver = new MutationObserver(callback);
this._mutationObserver.observe(this._component, {
Expand Down
5 changes: 1 addition & 4 deletions src/lib/label/label-aware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,5 @@ export interface ILabelAware {
* @returns True if the object is label aware, false otherwise.
*/
export const isLabelAware = (obj: any): obj is ILabelAware => {
return obj.labelClickedCallback &&
typeof obj.labelClickedCallback === 'function' &&
obj.labelChangedCallback &&
typeof obj.labelChangedCallback === 'function';
return typeof obj.labelClickedCallback === 'function' && typeof obj.labelChangedCallback === 'function';
};
18 changes: 0 additions & 18 deletions src/lib/label/label-component-delegate.ts

This file was deleted.

5 changes: 3 additions & 2 deletions src/lib/label/label-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ const elementName: keyof HTMLElementTagNameMap = `${COMPONENT_NAME_PREFIX}label`

const attributes = {
FOR: 'for',
FREEZE: 'freeze'
DYNAMIC: 'dynamic'
};

const selectors = {
ROOT: '.forge-label'
ROOT: '.forge-label',
SLOT: 'slot'
};

const labelableChildSelectors = [
Expand Down
32 changes: 21 additions & 11 deletions src/lib/label/label-foundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { LABEL_CONSTANTS } from './label-constants';
export interface ILabelFoundation extends ICustomElementFoundation {
for: string | null | undefined;
forElement: HTMLElement | null | undefined;
freeze: boolean;
dynamic: boolean;
disconnect(): void;
update(): void;
}
Expand All @@ -14,19 +14,22 @@ export class LabelFoundation implements ILabelFoundation {
// State
private _for: string | null | undefined;
private _forElement: HTMLElement | null | undefined;
private _freeze = false;
private _dynamic = false;
private _isConnected = false;

// Listeners
private readonly _clickListener: EventListener;
private readonly _slotChangeListener: EventListener;
private readonly _mutationCallback: MutationCallback;

constructor(private _adapter: ILabelAdapter) {
this._clickListener = (evt: PointerEvent) => this._handleClick(evt);
this._slotChangeListener = () => this._handleSlotChange();
this._mutationCallback = () => this._handleMutation();
}

public initialize(): void {
this._adapter.addSlotChangeListener(this._slotChangeListener);
this._adapter.trySetTarget(null);
if (this._adapter.hasTargetElement()) {
this._connect();
Expand All @@ -50,13 +53,20 @@ export class LabelFoundation implements ILabelFoundation {
this._adapter.clickTarget();
}

private _handleSlotChange(): void {
if (!this._for && !this._forElement) {
this._adapter.trySetTarget(null);
this._tryConnect();
}
}

private _handleMutation(): void {
this._adapter.updateTargetLabel();
}

private _connect(): void {
this._adapter.addHostListener('click', this._clickListener);
if (!this._freeze) {
if (this._dynamic) {
this._adapter.addMutationObserver(this._mutationCallback);
}
this._isConnected = true;
Expand Down Expand Up @@ -99,16 +109,16 @@ export class LabelFoundation implements ILabelFoundation {
}
}

public get freeze(): boolean {
return this._freeze;
public get dynamic(): boolean {
return this._dynamic;
}
public set freeze(value: boolean) {
if (this._freeze !== value) {
this._freeze = value;
this._adapter.toggleHostAttribute(LABEL_CONSTANTS.attributes.FREEZE, this._freeze);
if (this._freeze) {
public set dynamic(value: boolean) {
if (this._dynamic !== value) {
this._dynamic = value;
this._adapter.toggleHostAttribute(LABEL_CONSTANTS.attributes.DYNAMIC, this._dynamic);

if (!this._dynamic) {
this._adapter.removeMutationObserver();
this._adapter.updateTargetLabel();
} else if (this._adapter.hasTargetElement()) {
this._adapter.addMutationObserver(this._mutationCallback);
}
Expand Down
77 changes: 77 additions & 0 deletions src/lib/label/label.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { expect } from '@esm-bundle/chai';
import { fixture, html } from '@open-wc/testing';
import { getShadowElement } from '@tylertech/forge-core';
import { sendMouse } from '@web/test-runner-commands';
import { TestHarness } from '../../test/utils/test-harness';
import { ILabelComponent } from './label';
import { ILabelAware } from './label-aware';
import { LABEL_CONSTANTS } from './label-constants';

import './label';

class LabelHarness extends TestHarness<ILabelComponent> {
public rootElement: HTMLElement;
public labelAwareElement: HTMLElement & ILabelAware;

constructor(el: ILabelComponent) {
super(el);
}

public initElementRefs(): void {
this.rootElement = getShadowElement(this.element, LABEL_CONSTANTS.selectors.ROOT);
this.labelAwareElement = Object.create(document.createElement('div'), {
id: {
value: 'test'
},
labelClickedCallback: {
value: () => { },
},
labelChangedCallback: {
value: () => { }
}
}) as HTMLElement & ILabelAware;
}

public async clickElement(el: HTMLElement): Promise<void> {
const { x, y, width, height } = el.getBoundingClientRect();

await sendMouse({ type: 'click', position: [
Math.floor(x + window.scrollX + width / 2),
Math.floor(y + window.scrollY + height / 2)
]})
}
}

describe('Label', () => {
it('should contain shadow root', async () => {
const el = await fixture<ILabelComponent>(html`<forge-label></forge-label>`);
expect(el.shadowRoot).to.not.be.null;
});

it('should be accessible', async () => {
const el = await fixture<ILabelComponent>(html`<forge-label></forge-label>`);
await expect(el).to.be.accessible();
});

it('should render with correct default values', async () => {
const el = await fixture<ILabelComponent>(html`<forge-label></forge-label>`);

expect(el.for).to.be.undefined;
expect(el.forElement).to.be.undefined;
expect(el.dynamic).to.be.false;
});

it('should accept for', async () => {
const el = await fixture<ILabelComponent>(html`<forge-label for="test"></forge-label>`);
expect(el.for).to.equal('test');
});

it('should accept forElement', async () => {
const el = await fixture<ILabelComponent>(html`<forge-label></forge-label>`);
const ctx = new LabelHarness(el);



expect(el.forElement).to.not.be.null;
});
})
14 changes: 7 additions & 7 deletions src/lib/label/label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import style from './label.scss';
export interface ILabelComponent extends IBaseComponent {
for: string | null | undefined;
forElement: HTMLElement | null | undefined;
freeze: boolean;
dynamic: boolean;
update(): void;
}

Expand All @@ -28,10 +28,10 @@ declare global {
*
* @property {string | null | undefined} for - The id of the associated element.
* @property {HTMLElement | null | undefined} forElement - The associated element.
* @property {boolean} freeze - Prevents the label from monitoring changes to its content. Set this if the label's content will never change.
* @property {boolean} dynamic - Propagates changes in the label's text content to the associated element.
*
* @attribute {string} for - The id of the associated form component.
* @attribute {boolean} freeze - Prevents the label from monitoring changes to its content.
* @attribute {boolean} dynamic - Propagates changes in the label's text content to the associated element.
*
* @method update - Updates the targetted element with the label's current text content.
*
Expand All @@ -44,7 +44,7 @@ export class LabelComponent extends BaseComponent implements ILabelComponent {
public static get observedAttributes(): string[] {
return [
LABEL_CONSTANTS.attributes.FOR,
LABEL_CONSTANTS.attributes.FREEZE
LABEL_CONSTANTS.attributes.DYNAMIC
];
}

Expand All @@ -69,8 +69,8 @@ export class LabelComponent extends BaseComponent implements ILabelComponent {
case LABEL_CONSTANTS.attributes.FOR:
this.for = newValue;
break;
case LABEL_CONSTANTS.attributes.FREEZE:
this.freeze = coerceBoolean(newValue);
case LABEL_CONSTANTS.attributes.DYNAMIC:
this.dynamic = coerceBoolean(newValue);
break;
}
}
Expand All @@ -82,7 +82,7 @@ export class LabelComponent extends BaseComponent implements ILabelComponent {
public forElement: HTMLElement | null | undefined;

@FoundationProperty()
public freeze: boolean;
public dynamic: boolean;

/**
* Updates the targetted element with the label's current text content.
Expand Down
Loading

0 comments on commit c02072b

Please sign in to comment.