Skip to content

Commit

Permalink
refactor: switch to sd-option to allow for CheckBoxList support
Browse files Browse the repository at this point in the history
  • Loading branch information
GeekyEggo committed Nov 4, 2024
1 parent 909ba46 commit 743c563
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 197 deletions.
8 changes: 8 additions & 0 deletions src/ui/components/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ export class SDOptionElement extends LitElement {
this.htmlValue = value?.toString();
}

/**
* @inheritdoc
*/
protected override update(changedProperties: Map<PropertyKey, unknown>): void {
super.update(changedProperties);
this.dispatchEvent(new Event("update"));
}

/**
* @inheritdoc
*/
Expand Down
67 changes: 22 additions & 45 deletions src/ui/components/radio-group.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,53 @@
import { css, html, LitElement, type TemplateResult } from "lit";
import { customElement, queryAssignedElements } from "lit/decorators.js";
import { customElement } from "lit/decorators.js";
import { repeat } from "lit/directives/repeat.js";

import { Input } from "../mixins/input";
import { List } from "../mixins/list";
import { SDRadioElement } from "./radio";

/**
* Element that offers persisting a value via a list of radio options.
*/
@customElement("sd-radiogroup")
export class SDRadioGroupElement extends Input<boolean | number | string>(LitElement) {
export class SDRadioGroupElement extends List(Input<boolean | number | string>(LitElement)) {
/**
* @inheritdoc
*/
public static styles = [
super.styles ?? [],
...SDRadioElement.styles,
css`
::slotted(sd-radio) {
sd-radio {
display: flex;
}
`,
];

/**
* Radio buttons associated with this group.
*/
@queryAssignedElements({ selector: "sd-radio" })
accessor #radioButtons!: Array<SDRadioElement>;

/**
* @inheritdoc
*/
public override render(): TemplateResult {
return html`
<slot
@change=${(ev: Event): void => {
if (ev.target instanceof SDRadioElement) {
this.value = ev.target.value;
}
}}
@slotchange=${(): void => this.#setRadioButtonProperties()}
></slot>
${repeat(
this.items,
(opt) => opt,
(opt) => {
return html`<sd-radio
name="__radio"
.checked=${this.value === opt.value}
.disabled=${opt.disabled}
.label=${opt.label}
.value=${opt.value}
@change=${() => {

Check warning on line 42 in src/ui/components/radio-group.ts

View workflow job for this annotation

GitHub Actions / Build (20.18.0, macos-latest)

Missing return type on function
this.value = opt.value;
}}
>${opt.innerText}</sd-radio
>`;
},
)}
`;
}

/**
* @inheritdoc
*/
protected override willUpdate(_changedProperties: Map<PropertyKey, unknown>): void {
super.willUpdate(_changedProperties);

// Reflect the necessary properties of the radio group, to its radio buttons.
if (_changedProperties.has("disabled") || _changedProperties.has("value")) {
const disabled = _changedProperties.get("disabled") !== undefined ? this.disabled : undefined;
this.#setRadioButtonProperties(disabled);
}
}

/**
* Sets the properties of the child radio buttons, based on this parent group.
* @param disabled Determines the disabled state of all radio buttons; ignored when `undefined`.
*/
#setRadioButtonProperties(disabled?: boolean): void {
for (const radioButton of this.#radioButtons) {
radioButton.name = "__radio"; // Radio buttons are scoped to this shadow root.
radioButton.checked = this.value === radioButton.value;

if (disabled !== undefined) {
radioButton.disabled = disabled;
}
}
}
}

declare global {
Expand Down
152 changes: 0 additions & 152 deletions src/ui/controllers/mutation-controller.ts

This file was deleted.

105 changes: 105 additions & 0 deletions src/ui/controllers/option-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ReactiveController, ReactiveControllerHost } from "lit";

import { SDOptionElement } from "../components";

/**
* Controller for tracking {@link SDOptionElement} within a host.
*/
export class OptionController implements ReactiveController {
/**
* Current options within the host.
*/
public readonly options: SDOptionElement[] = [];

/**
* Host this controller is attached to.
*/
readonly #host: HTMLElement & ReactiveControllerHost;

/**
* Underlying mutation observer monitoring changes to the shadow DOM.
*/
readonly #observer: MutationObserver;

/**
* Requests a host update which is processed asynchronously.
*/
readonly #requestUpdate: () => void;

/**
* Initializes a new instance of the {@link OptionController} class.
* @param host Host to attach to.
*/
constructor(host: HTMLElement & ReactiveControllerHost) {
this.#host = host;
this.#host.addController(this);
this.#requestUpdate = () => this.#host.requestUpdate();

Check warning on line 36 in src/ui/controllers/option-controller.ts

View workflow job for this annotation

GitHub Actions / Build (20.18.0, macos-latest)

Missing return type on function

this.#observer = new MutationObserver((mutations: MutationRecord[]) => this.#onMutation(mutations));
this.#setOptionsByQueryingHost();
}

/**
* @inheritdoc
*/
public hostConnected(): void {
this.#observer.observe(this.#host, { childList: true });
}

/**
* @inheritdoc
*/
public hostDisconnected(): void {
this.#observer.disconnect();
}

/**
* Handles a mutation on the host's shadow DOM, updating the tracked collection of nodes.
* @param mutations Mutations that occurred.
*/
#onMutation(mutations: MutationRecord[]): void {
let isOptionRemoved = false;

for (const { addedNodes, removedNodes, target, type } of mutations) {

Check failure on line 63 in src/ui/controllers/option-controller.ts

View workflow job for this annotation

GitHub Actions / Build (20.18.0, macos-latest)

'target' is assigned a value but never used

Check failure on line 63 in src/ui/controllers/option-controller.ts

View workflow job for this annotation

GitHub Actions / Build (20.18.0, macos-latest)

'type' is assigned a value but never used
// When a new option was added, simply rebuild the collection of options to maintain correct ordering.
for (const added of addedNodes) {
if (added instanceof SDOptionElement) {
this.#setOptionsByQueryingHost();
return;
}
}

// When an option was removed, remove it from the collection, but continue the evaluation.
for (const removed of removedNodes) {
if (removed instanceof SDOptionElement) {
const index = this.options.indexOf(removed);
if (index !== -1) {
this.options
.splice(index, 1)
.forEach((option) => option.removeEventListener("update", this.#requestUpdate));

isOptionRemoved = true;
}
}
}
}

if (isOptionRemoved) {
this.#host.requestUpdate();
}
}

/**
* Sets the options associated with this instance by querying the host.
*/
#setOptionsByQueryingHost(): void {
this.options.length = 0;

this.#host.querySelectorAll(":scope > sd-option").forEach((option) => {
this.options.push(<SDOptionElement>option);
option.addEventListener("update", this.#requestUpdate);
});

this.#host.requestUpdate();
}
}
Loading

0 comments on commit 743c563

Please sign in to comment.