Skip to content

Commit

Permalink
[button-area] refactor to use tokens, state-layer, and focus-indicator (
Browse files Browse the repository at this point in the history
  • Loading branch information
derekmoss authored Mar 6, 2024
1 parent 56c5141 commit 9df42ed
Show file tree
Hide file tree
Showing 15 changed files with 503 additions and 260 deletions.
20 changes: 20 additions & 0 deletions src/lib/button-area/_core.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@use './token-utils' as *;

@forward './token-utils';

@mixin host {
display: block;
position: relative;
}

@mixin base {
position: relative;
overflow: hidden;
}

@mixin enabled {
cursor: #{token(cursor)};
}
@mixin disabled {
cursor: #{token(disabled-cursor)};
}
42 changes: 0 additions & 42 deletions src/lib/button-area/_mixins.scss

This file was deleted.

25 changes: 25 additions & 0 deletions src/lib/button-area/_token-utils.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@use '../core/styles/tokens/button-area/tokens';
@use '../core/styles/tokens/token-utils';

$_module: button-area;
$_tokens: tokens.$tokens;

@mixin provide-theme($theme) {
@include token-utils.provide-theme($_module, $_tokens, $theme);
}

@function token($name, $type: token) {
@return token-utils.token($_module, $_tokens, $name, $type);
}

@function declare($token) {
@return token-utils.declare($_module, $token);
}

@mixin override($token, $token-or-value, $type: token) {
@include token-utils.override($_module, $_tokens, $token, $token-or-value, $type);
}

@mixin tokens($includes: null, $excludes: null) {
@include token-utils.tokens($_module, $_tokens, $includes, $excludes);
}
146 changes: 44 additions & 102 deletions src/lib/button-area/button-area-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,85 @@
import { addClass, getShadowElement, removeClass, toggleClass } from '@tylertech/forge-core';
import { getShadowElement } from '@tylertech/forge-core';

import { BaseAdapter, IBaseAdapter, createUserInteractionListener } from '../core';
import { ForgeRipple, ForgeRippleAdapter, ForgeRippleCapableSurface, ForgeRippleFoundation } from '../ripple';
import { BaseAdapter, IBaseAdapter } from '../core';
import { FOCUS_INDICATOR_CONSTANTS, IFocusIndicatorComponent } from '../focus-indicator';
import { IStateLayerComponent, STATE_LAYER_CONSTANTS } from '../state-layer';
import { IButtonAreaComponent } from './button-area';
import { BUTTON_AREA_CONSTANTS } from './button-area-constants';

export interface IButtonAreaAdapter extends IBaseAdapter {
destroy(): void;
setDisabled(value: boolean): void;
addListener(type: string, listener: (event: Event) => void): void;
removeListener(type: string, listener: (event: Event) => void): void;
addSlotChangeListener(listener: () => void): void;
removeSlotChangeListener(listener: () => void): void;
addListener(type: string, listener: EventListener, capture?: boolean): void;
removeListener(type: string, listener: EventListener, capture?: boolean): void;
addButtonSlotListener(type: string, listener: EventListener): void;
removeButtonSlotListener(type: string, listener: EventListener): void;
addContentSlotListener(type: string, listener: EventListener): void;
removeContentSlotListener(type: string, listener: EventListener): void;
animateStateLayer(): void;
startButtonObserver(callback: MutationCallback): void;
stopButtonObserver(): void;
detectSlottedButton(): void;
buttonIsDisabled(): boolean;
isButtonDisabled(): boolean;
requestDisabledButtonFrame(): void;
createRipple(): Promise<void>;
}

export class ButtonAreaAdapter extends BaseAdapter<IButtonAreaComponent> implements IButtonAreaAdapter, ForgeRippleCapableSurface {
export class ButtonAreaAdapter extends BaseAdapter<IButtonAreaComponent> implements IButtonAreaAdapter {
private _rootElement: HTMLElement;
private _buttonSlotElement: HTMLSlotElement;
private _contentSlotElement: HTMLSlotElement;
private _buttonElement?: HTMLButtonElement;
private _rippleInstance?: ForgeRipple;
private _buttonObserver?: MutationObserver;
private _destroyUserInteractionListener: (() => void) | undefined;
private _focusIndicatorElement: IFocusIndicatorComponent;
private _stateLayerElement: IStateLayerComponent;


constructor(component: IButtonAreaComponent) {
super(component);
this._rootElement = getShadowElement(component, BUTTON_AREA_CONSTANTS.selectors.ROOT);
this._buttonSlotElement = getShadowElement(component, BUTTON_AREA_CONSTANTS.selectors.BUTTON_SLOT) as HTMLSlotElement;
this._contentSlotElement = getShadowElement(component, BUTTON_AREA_CONSTANTS.selectors.CONTENT_SLOT) as HTMLSlotElement;
this._focusIndicatorElement = getShadowElement(component, FOCUS_INDICATOR_CONSTANTS.elementName) as IFocusIndicatorComponent;
this._stateLayerElement = getShadowElement(component, STATE_LAYER_CONSTANTS.elementName) as IStateLayerComponent;
}

public destroy(): void {
if (typeof this._destroyUserInteractionListener === 'function') {
this._destroyUserInteractionListener();
this._destroyUserInteractionListener = undefined;
}
}
public destroy(): void {}

public get root(): HTMLElement {
return this._rootElement;
public setDisabled(value: boolean): void {
this._buttonElement?.toggleAttribute(BUTTON_AREA_CONSTANTS.attributes.DISABLED, value);
if (value) {
this._focusIndicatorElement.remove();
this._stateLayerElement.remove();
} else {
this._rootElement.append(this._focusIndicatorElement, this._stateLayerElement);
}
}

public get unbounded(): boolean | undefined {
return false;
public addListener(type: string, listener: EventListener, capture?: boolean): void {
this._rootElement.addEventListener(type, listener, { capture });
}

public get disabled(): boolean | undefined {
return this.buttonIsDisabled();
public removeListener(type: string, listener: EventListener, capture?: boolean): void {
this._rootElement.removeEventListener(type, listener, { capture });
}

public setDisabled(value: boolean): void {
toggleClass(this._rootElement, value, BUTTON_AREA_CONSTANTS.classes.DISABLED);
this._buttonElement?.toggleAttribute(BUTTON_AREA_CONSTANTS.attributes.DISABLED, value);
public addButtonSlotListener(type: string, listener: EventListener): void {
this._buttonSlotElement.addEventListener(type, listener);
}

public addListener(type: string, listener: (event: Event) => void): void {
this._rootElement.addEventListener(type, listener);
public removeButtonSlotListener(type: string, listener: EventListener): void {
this._buttonSlotElement.removeEventListener(type, listener);
}

public removeListener(type: string, listener: (event: Event) => void): void {
this._rootElement.removeEventListener(type, listener);
public addContentSlotListener(type: string, listener: EventListener): void {
this._contentSlotElement.addEventListener(type, listener);
}

public addSlotChangeListener(listener: () => void): void {
this._buttonSlotElement.addEventListener('slotchange', listener);
public removeContentSlotListener(type: string, listener: EventListener): void {
this._contentSlotElement.removeEventListener(type, listener);
}

public removeSlotChangeListener(listener: () => void): void {
this._buttonSlotElement.removeEventListener('slotchange', listener);
public animateStateLayer(): void {
this._stateLayerElement.playAnimation();
}

public startButtonObserver(callback: MutationCallback): void {
Expand All @@ -92,7 +100,7 @@ export class ButtonAreaAdapter extends BaseAdapter<IButtonAreaComponent> impleme
this._buttonElement = this._buttonSlotElement.assignedElements()[0] as HTMLButtonElement | undefined;
}

public buttonIsDisabled(): boolean {
public isButtonDisabled(): boolean {
return this._buttonElement?.disabled ?? true;
}

Expand All @@ -103,70 +111,4 @@ export class ButtonAreaAdapter extends BaseAdapter<IButtonAreaComponent> impleme
requestAnimationFrame(() => this._buttonElement!.disabled = false);
}
}

public async createRipple(): Promise<void> {
if (!this._rippleInstance) {
const { userInteraction, destroy } = await createUserInteractionListener(this._rootElement);
this._destroyUserInteractionListener = destroy;
const { type } = await userInteraction;
this._destroyUserInteractionListener = undefined;

if (!this.isConnected) {
return;
}

if (!this._rippleInstance) {
const adapter: ForgeRippleAdapter = {
...ForgeRipple.createAdapter(this),
isSurfaceActive: () => this._rootElement.matches(':active') || (this._buttonElement?.matches(':active') ?? false),
isSurfaceDisabled: () => this.disabled ?? true,
isUnbounded: () => !!this.unbounded,
registerInteractionHandler: (evtType, handler) => {
if (this._isRootEvent(evtType)) {
this._rootElement.addEventListener(evtType, handler, { passive: true });
} else {
this._buttonElement?.addEventListener(evtType, handler, { passive: true});
}
},
deregisterInteractionHandler: (evtType, handler) => {
if (this._isRootEvent(evtType)) {
this._rootElement.removeEventListener(evtType, handler, { passive: true } as AddEventListenerOptions);
} else {
this._buttonElement?.removeEventListener(evtType, handler, { passive: true } as AddEventListenerOptions);
}
},
addClass: (className) => addClass(className, this._rootElement),
removeClass: (className) => removeClass(className, this._rootElement),
updateCssVariable: (varName, value) => this._rootElement.style.setProperty(varName, value)
};

this._rippleInstance = new ForgeRipple(this._rootElement, new ForgeRippleFoundation(adapter));
if (type === 'focusin') {
this._rippleInstance.handleFocus();
}
}
} else {
if (typeof this._destroyUserInteractionListener === 'function') {
this._destroyUserInteractionListener();
}

this._rippleInstance.destroy();
this._rippleInstance = undefined;
}
}

private async _userInteractionListener(): Promise<string> {
if (typeof this._destroyUserInteractionListener === 'function') {
this._destroyUserInteractionListener();
}
const { userInteraction, destroy } = createUserInteractionListener(this._rootElement);
this._destroyUserInteractionListener = destroy;
const { type } = await userInteraction;
this._destroyUserInteractionListener = undefined;
return type;
}

private _isRootEvent(evtType: string): boolean {
return ['touchstart', 'pointerdown', 'mousedown'].includes(evtType);
}
}
8 changes: 5 additions & 3 deletions src/lib/button-area/button-area-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ const attributes = {

const ids = {
ROOT: 'root',
BUTTON_SLOT: 'button'
BUTTON_SLOT: 'button',
CONTENT_SLOT: 'content'
};

const classes = {
DISABLED: `forge-button-area--disabled`
ROOT: `forge-button-area`
};

const selectors = {
ROOT: `#${ids.ROOT}`,
BUTTON_SLOT: `slot[name=button]`
BUTTON_SLOT: `slot[name=button]`,
CONTENT_SLOT: `#${ids.CONTENT_SLOT}`
};

export const BUTTON_AREA_CONSTANTS = {
Expand Down
Loading

0 comments on commit 9df42ed

Please sign in to comment.