Skip to content

Commit

Permalink
rework
Browse files Browse the repository at this point in the history
  • Loading branch information
faselbaum committed Jun 28, 2024
1 parent b51739d commit 9336271
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 111 deletions.
2 changes: 1 addition & 1 deletion packages/js-example-app/src/index.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ function init() {
addLog('blr-radio changed: ' + e.detail.selectedValue);
});

blrRadioGroup.addEventListener('blrRadioGroupValueChange', (e) => {
blrRadioGroup.addEventListener('blrSelectedValueChange', (e) => {
addLog('blr-radio value changed blrRadioGroupValueChange: ' + e.detail.selectedValue);
});

Expand Down
2 changes: 1 addition & 1 deletion packages/js-example-app/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export function* renderIndex() {
radio-id="radioId"
name="Radio Group"
>
<blr-radio label="male" value="male"></blr-radio>
<blr-radio label="male" value="male" checked></blr-radio>
<blr-radio label="female" value="female"></blr-radio>
<blr-radio label="other" value="other"></blr-radio>
</blr-radio-group>
Expand Down
12 changes: 10 additions & 2 deletions packages/ui-library/.babelrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
// Used by Storybook
{
"sourceType": "unambiguous",
"presets": ["@babel/preset-env", "@babel/preset-typescript"],
"plugins": [["@babel/plugin-proposal-decorators", { "version": "2023-05" }]]
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-typescript",
{
"allowDeclareFields": true
}
],
["@babel/plugin-proposal-decorators", { "version": "2023-05" }]
]
}
1 change: 1 addition & 0 deletions packages/ui-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"dependencies": {
"@boiler/icons": "0.0.1",
"@floating-ui/dom": "^1.6.3",
"@lit-labs/preact-signals": "1.0.2",
"@lit-labs/react": "^1.1.1",
"lit": "^3.1.2",
"nested-css-to-flat": "^1.0.5"
Expand Down
85 changes: 40 additions & 45 deletions packages/ui-library/src/components/radio-group/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { html, nothing } from 'lit';
import { PropertyValues, html, nothing } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { property } from '../../utils/lit/decorators.js';
import { staticStyles as componentSpecificStaticStyles } from './index.css.js';
Expand All @@ -14,21 +14,22 @@ import { LitElementCustom, ElementInterface } from '../../utils/lit/element.js';
import {
BlrBlurEvent,
BlrFocusEvent,
BlrRadioGroupValueChangeEvent,
createBlrRadioGroupValueChangeEvent,
BlrSelectedValueChangeEvent,
createBlrSelectedValueChangeEvent,
} from '../../globals/events.js';
import { BlrRadio } from '../radio/index.js';
import { batch, Signal } from '@lit-labs/preact-signals';

export type BlrRadioGroupEventHandlers = {
blrFocus?: (event: BlrFocusEvent) => void;
blrBlur?: (event: BlrBlurEvent) => void;
blrRadioGroupValueChange?: (event: BlrRadioGroupValueChangeEvent) => void;
blrSelectedValueChange?: (event: BlrSelectedValueChangeEvent) => void;
};

/**
* @fires blrFocus Radio received focus
* @fires blrBlur Radio lost focus
* @fires blrRadioGroupValueChange Radio selected value changed
* @fires blrSelectedValueChange Radio selected value changed
*/

export class BlrRadioGroup extends LitElementCustom {
Expand All @@ -50,61 +51,55 @@ export class BlrRadioGroup extends LitElementCustom {
@property() accessor legend: string | undefined;
@property() accessor direction: RadioGroupDirection = 'horizontal';
@property() accessor theme: ThemeType = 'Light';
protected _radioElements: BlrRadio[] | undefined;

protected handleRadioCheckedEvent = (event: Event) => {
let currentlyCheckedRadio = (event?.target as HTMLInputElement).id;

if (event.type === 'checkedChangeEvent') {
currentlyCheckedRadio = (<CustomEvent>event).detail.currentlyCheckedRadio.getAttribute('label');
}

this._radioElements?.forEach((item: BlrRadio) => {
const label = item.getAttribute('label');
if (label === currentlyCheckedRadio) {
item.checked = true;
} else {
item.checked = false;
}
protected _radioElements: BlrRadio[] = [];
private _selectedRadio?: BlrRadio;
private _radioCheckedSignalSubscriptionDisposers: ReturnType<Signal['subscribe']>[] = [];

protected handleRadioCheckedSignal = (target: BlrRadio, value?: boolean) => {
const selectedRadio: BlrRadio | undefined = value
? target
: target === this._selectedRadio && !value
? undefined
: this._selectedRadio;

batch(() => {
this._radioElements?.forEach((radio) => {
if (radio !== selectedRadio) {
radio.checked = false;
}
});
});

this.dispatchEvent(
createBlrRadioGroupValueChangeEvent({
originalEvent: event,
selectedValue: (event?.target as HTMLInputElement).value,
})
);

event.preventDefault();
if (this._selectedRadio !== selectedRadio) {
this.dispatchEvent(createBlrSelectedValueChangeEvent({ selectedValue: (<BlrRadio>selectedRadio)?.value ?? '' }));
this._selectedRadio = selectedRadio;
}
};

firstUpdated() {
this.addEventListener('checkedChangeEvent', this.handleRadioCheckedEvent);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected firstUpdated(_changedProperties: PropertyValues): void {
this.handleSlotChange();
}

disconnectedCallback() {
this.removeEventListener('checkedChangeEvent', this.handleRadioCheckedEvent);
}
protected handleSlotChange = () => {
// Cleanup signal listeners from previously slotted elements
this._radioCheckedSignalSubscriptionDisposers.forEach((cancelSubscription) => cancelSubscription());

handleSlotChange = () => {
const slot = this.renderRoot?.querySelector('slot');
this._radioElements = slot?.assignedElements({ flatten: false }) as HTMLElement[] & BlrRadio[];
this._radioElements = slot?.assignedElements({ flatten: false }) as BlrRadio[];

this._radioElements?.forEach((item: BlrRadio) => {
// Add signal listeners to newly slotted elements
this._radioElements.forEach((item) => {
if (item instanceof BlrRadio === false) {
throw new Error('child component of blr-radio-group must be blr-radio');
}

const radioElement = <HTMLInputElement>item.shadowRoot?.querySelector('input');
const radioElementWrapper = <HTMLInputElement>item.shadowRoot?.querySelector('.blr-radio');

if (this.hasError) {
[radioElement, radioElementWrapper].forEach((element) => element.classList.add('error'));
}

if (this.sizeVariant !== item.sizeVariant) item.sizeVariant = this.sizeVariant;
item.hasError = this.hasError;

radioElement.addEventListener('click', this.handleRadioCheckedEvent);
this._radioCheckedSignalSubscriptionDisposers.push(
item.signals.checked.subscribe((value) => this.handleRadioCheckedSignal(item, value))
);
});
};

Expand Down
88 changes: 50 additions & 38 deletions packages/ui-library/src/components/radio/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,18 @@ import {
BlrFocusEvent,
BlrCheckedChangeEvent,
} from '../../globals/events.js';
import { LitElementCustom, ElementInterface } from '../../utils/lit/element.js';

export type BlrRadioEventHandlers = {
blrFocus?: (event: BlrFocusEvent) => void;
blrBlur?: (event: BlrBlurEvent) => void;
blrSelectedValueChangeEvent?: (event: BlrCheckedChangeEvent) => void;
};
import { LitElementCustom } from '../../utils/lit/element.js';
import { SignalHub } from '../../utils/lit/signals.js';

/**
* @fires blrFocus Radio received focus
* @fires blrBlur Radio lost focus
* @fires blrSelectedValueChangeEvent Radio selected value changed
*/

export class BlrRadio extends LitElementCustom {
export class BlrRadio extends LitElementCustom implements PublicReactiveProperties {
public declare signals: SignalHub<PublicReactiveProperties>;

static styles = [staticFormStyles, staticRadioStyles];

@query('input')
Expand All @@ -43,7 +40,7 @@ export class BlrRadio extends LitElementCustom {
@property() accessor label!: string;
@property() accessor disabled: boolean | undefined;
@property() accessor readonly: boolean | undefined;
@property() accessor checked: boolean | undefined;
@property({ type: Boolean }) accessor checked: boolean | undefined;
@property() accessor name: string | undefined;
@property() accessor sizeVariant: InputSizesType | undefined = 'md';
@property() accessor required: boolean | undefined;
Expand All @@ -53,6 +50,7 @@ export class BlrRadio extends LitElementCustom {
@property() accessor hasHint: boolean | undefined;
@property() accessor hintMessage: string | undefined;
@property() accessor hintMessageIcon: SizelessIconType | undefined;
@property() accessor value: string | undefined;
@property() accessor theme: ThemeType = 'Light';

protected handleFocus = (event: FocusEvent) => {
Expand All @@ -67,34 +65,21 @@ export class BlrRadio extends LitElementCustom {
}
};

protected handleChange(event: Event) {
protected handleClick(event: Event) {
event.preventDefault();

if (!this.disabled) {
this.dispatchEvent(
createBlrSelectedValueChangeEvent({ originalEvent: event, selectedValue: this._radioNode.value })
);
}
}
const changeEvent = createBlrSelectedValueChangeEvent({
originalEvent: event,
selectedValue: this._radioNode.value,
});

firstUpdated() {
/* eslint-disable @typescript-eslint/no-this-alias */
const _self = this;
Object.defineProperty(this._radioNode, 'checked', {
set: function (value) {
if (value === false) {
return;
}
_self.checked = value;
_self.dispatchEvent(
new CustomEvent('checkedChangeEvent', {
bubbles: true,
detail: { currentlyCheckedRadio: _self },
})
);
},
get: function () {
return this.hasAttribute('checked');
},
});
this.dispatchEvent(changeEvent);

if (!changeEvent.defaultPrevented) {
this.checked = true;
}
}
}

protected render() {
Expand Down Expand Up @@ -148,13 +133,13 @@ export class BlrRadio extends LitElementCustom {
class="${classes} input-control"
type="radio"
name=${this.name}
.value=${this.value}
?disabled=${this.disabled}
?readonly=${this.readonly}
?invalid=${this.hasError}
?checked=${this.checked}
.checked=${this.checked || false}
?required=${this.required}
@input=${this.handleChange}
@click=${this.handleClick}
@focus=${this.handleFocus}
@blur=${this.handleBlur}
/>
Expand All @@ -179,4 +164,31 @@ if (!customElements.get(TAG_NAME)) {
customElements.define(TAG_NAME, BlrRadio);
}

export type BlrRadioType = ElementInterface<BlrRadio & BlrRadioEventHandlers>;
export type BlrRadioType = PublicReactiveProperties & PublicMethods & BlrRadioEventHandlers;

export type PublicReactiveProperties = {
optionId: string;
label: string;
disabled?: boolean;
readonly?: boolean;
name?: string;
sizeVariant?: InputSizesType;
required?: boolean;
hasError?: boolean;
errorMessage?: string;
errorMessageIcon?: SizelessIconType;
hasHint?: boolean;
hintMessage?: string;
hintMessageIcon?: SizelessIconType;
value?: string;
theme: ThemeType;
checked?: boolean;
};

export type PublicMethods = unknown;

export type BlrRadioEventHandlers = {
blrFocus?: (event: BlrFocusEvent) => void;
blrBlur?: (event: BlrBlurEvent) => void;
blrSelectedValueChangeEvent?: (event: BlrCheckedChangeEvent) => void;
};
16 changes: 2 additions & 14 deletions packages/ui-library/src/globals/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,27 +68,15 @@ export function createBlrCheckedChangeEvent(detail: BlrCheckedChangeEventDetail)
}

export type BlrSelectedValueChangeEventDetail = {
originalEvent: Event;
originalEvent?: Event;
selectedValue: string;
};
export type BlrSelectedValueChangeEvent = CustomEvent<BlrSelectedValueChangeEventDetail>;
export const BlrSelectedValueChangeEventName = 'blrSelectedValueChange';
export function createBlrSelectedValueChangeEvent(
detail: BlrSelectedValueChangeEventDetail
): BlrSelectedValueChangeEvent {
return new CustomEvent(BlrSelectedValueChangeEventName, { bubbles: true, composed: true, detail });
}

export type BlrRadioGroupValueChangeEventDetail = {
originalEvent: Event;
selectedValue: string;
};
export type BlrRadioGroupValueChangeEvent = CustomEvent<BlrSelectedValueChangeEventDetail>;
export const BlrRadioGroupValueChangeEventName = 'blrRadioGroupValueChange';
export function createBlrRadioGroupValueChangeEvent(
detail: BlrRadioGroupValueChangeEventDetail
): BlrRadioGroupValueChangeEvent {
return new CustomEvent(BlrRadioGroupValueChangeEventName, { bubbles: true, composed: true, detail });
return new CustomEvent(BlrSelectedValueChangeEventName, { bubbles: false, composed: true, detail, cancelable: true });
}

export type BlrTextValueChangeEventDetail = {
Expand Down
Loading

0 comments on commit 9336271

Please sign in to comment.