From 5561cf3cf19e1783deb7c7dd013879c7d9c41c53 Mon Sep 17 00:00:00 2001
From: Ashley Hunter <20795331+ashley-hunter@users.noreply.github.com>
Date: Tue, 26 Nov 2024 22:33:46 +0000
Subject: [PATCH 1/3] feat(radio-group): migrate to signals
---
libs/ui/radio-group/brain/package.json | 3 +-
libs/ui/radio-group/brain/src/index.ts | 4 +-
.../src/lib/brn-radio-group.component.ts | 114 ++++
.../brain/src/lib/brn-radio-group.token.ts | 12 +
.../brain/src/lib/brn-radio.component.ts | 537 ++++--------------
package.json | 1 -
6 files changed, 226 insertions(+), 445 deletions(-)
create mode 100644 libs/ui/radio-group/brain/src/lib/brn-radio-group.component.ts
create mode 100644 libs/ui/radio-group/brain/src/lib/brn-radio-group.token.ts
diff --git a/libs/ui/radio-group/brain/package.json b/libs/ui/radio-group/brain/package.json
index 287115d20..7ed15daab 100644
--- a/libs/ui/radio-group/brain/package.json
+++ b/libs/ui/radio-group/brain/package.json
@@ -4,7 +4,8 @@
"peerDependencies": {
"@angular/core": ">=18.0.0",
"@angular/cdk": ">=18.0.0",
- "@angular/forms": ">=18.0.0"
+ "@angular/forms": ">=18.0.0",
+ "@spartan-ng/ui-forms-brain": "0.0.1-alpha.356"
},
"dependencies": {},
"sideEffects": false,
diff --git a/libs/ui/radio-group/brain/src/index.ts b/libs/ui/radio-group/brain/src/index.ts
index c57460fcb..22f19056f 100644
--- a/libs/ui/radio-group/brain/src/index.ts
+++ b/libs/ui/radio-group/brain/src/index.ts
@@ -1,7 +1,9 @@
import { NgModule } from '@angular/core';
-import { BrnRadioComponent, BrnRadioGroupComponent } from './lib/brn-radio.component';
+import { BrnRadioGroupComponent } from './lib/brn-radio-group.component';
+import { BrnRadioComponent } from './lib/brn-radio.component';
+export * from './lib/brn-radio-group.component';
export * from './lib/brn-radio.component';
export const BrnRadioGroupImports = [BrnRadioGroupComponent, BrnRadioComponent] as const;
diff --git a/libs/ui/radio-group/brain/src/lib/brn-radio-group.component.ts b/libs/ui/radio-group/brain/src/lib/brn-radio-group.component.ts
new file mode 100644
index 000000000..fff367a19
--- /dev/null
+++ b/libs/ui/radio-group/brain/src/lib/brn-radio-group.component.ts
@@ -0,0 +1,114 @@
+/* eslint-disable @typescript-eslint/no-empty-function */
+import { BooleanInput } from '@angular/cdk/coercion';
+import {
+ booleanAttribute,
+ Component,
+ computed,
+ contentChildren,
+ forwardRef,
+ input,
+ model,
+ output,
+ signal,
+} from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+import { ChangeFn, TouchFn } from '@spartan-ng/ui-forms-brain';
+import { provideBrnRadioGroupToken } from './brn-radio-group.token';
+import { BrnRadioChange, BrnRadioComponent } from './brn-radio.component';
+
+export const BRN_RADIO_GROUP_CONTROL_VALUE_ACCESSOR = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => BrnRadioGroupComponent),
+ multi: true,
+};
+
+@Component({
+ selector: 'brn-radio-group',
+ standalone: true,
+ providers: [BRN_RADIO_GROUP_CONTROL_VALUE_ACCESSOR, provideBrnRadioGroupToken(BrnRadioGroupComponent)],
+ host: {
+ role: 'radiogroup',
+ '(focusout)': 'onTouched()',
+ },
+ template: '',
+})
+export class BrnRadioGroupComponent implements ControlValueAccessor {
+ private static _nextUniqueId = 0;
+
+ protected onChange: ChangeFn = () => {};
+
+ protected onTouched: TouchFn = () => {};
+
+ public readonly name = input(`brn-radio-group-${BrnRadioGroupComponent._nextUniqueId++}`);
+
+ /**
+ * The value of the selected radio button.
+ */
+ public readonly value = model();
+
+ /**
+ * Whether the radio group is disabled.
+ */
+ public disabled = input(false, {
+ transform: booleanAttribute,
+ });
+
+ /**
+ * Whether the radio group should be required.
+ */
+ public readonly required = input(false, {
+ transform: booleanAttribute,
+ });
+
+ /**
+ * The direction of the radio group.
+ */
+ public readonly direction = input<'ltr' | 'rtl' | null>('ltr');
+
+ /**
+ * Event emitted when the group value changes.
+ */
+ public readonly change = output>();
+
+ /**
+ * The internal disabled state of the radio group. This could be switched to a linkedSignal when we can drop v18 support.
+ * @internal
+ */
+ public readonly disabledState = computed(() => signal(this.disabled()));
+
+ /**
+ * Access the radio buttons within the group.
+ * @internal
+ */
+ public readonly radioButtons = contentChildren(BrnRadioComponent, { descendants: true });
+
+ writeValue(value: T) {
+ this.value.set(value);
+ }
+
+ registerOnChange(fn: ChangeFn) {
+ this.onChange = fn;
+ }
+
+ registerOnTouched(fn: TouchFn) {
+ this.onTouched = fn;
+ }
+
+ setDisabledState(isDisabled: boolean) {
+ this.disabledState().set(isDisabled);
+ }
+
+ /**
+ * Select a radio button.
+ * @internal
+ */
+ select(radioButton: BrnRadioComponent, value: T) {
+ if (this.value() === value) {
+ return;
+ }
+
+ this.value.set(value);
+ this.onChange(value);
+ this.change.emit(new BrnRadioChange(radioButton, value));
+ }
+}
diff --git a/libs/ui/radio-group/brain/src/lib/brn-radio-group.token.ts b/libs/ui/radio-group/brain/src/lib/brn-radio-group.token.ts
new file mode 100644
index 000000000..a19dce4bf
--- /dev/null
+++ b/libs/ui/radio-group/brain/src/lib/brn-radio-group.token.ts
@@ -0,0 +1,12 @@
+import { ExistingProvider, inject, InjectionToken, Type } from '@angular/core';
+import type { BrnRadioGroupComponent } from './brn-radio-group.component';
+
+const BrnRadioGroupToken = new InjectionToken>('BrnRadioGroupToken');
+
+export function provideBrnRadioGroupToken(component: Type>): ExistingProvider {
+ return { provide: BrnRadioGroupToken, useExisting: component };
+}
+
+export function injectBrnRadioGroup(): BrnRadioGroupComponent {
+ return inject(BrnRadioGroupToken) as BrnRadioGroupComponent;
+}
diff --git a/libs/ui/radio-group/brain/src/lib/brn-radio.component.ts b/libs/ui/radio-group/brain/src/lib/brn-radio.component.ts
index 428e9e5e8..1582d4db8 100644
--- a/libs/ui/radio-group/brain/src/lib/brn-radio.component.ts
+++ b/libs/ui/radio-group/brain/src/lib/brn-radio.component.ts
@@ -1,55 +1,38 @@
-import { FocusMonitor, type FocusOrigin, type FocusableOption } from '@angular/cdk/a11y';
-import { UniqueSelectionDispatcher } from '@angular/cdk/collections';
+import { FocusMonitor } from '@angular/cdk/a11y';
+import { BooleanInput } from '@angular/cdk/coercion';
import {
- type AfterContentInit,
- type AfterViewInit,
ChangeDetectionStrategy,
- ChangeDetectorRef,
Component,
- ContentChildren,
- type DoCheck,
ElementRef,
- EventEmitter,
- Input,
type OnDestroy,
- type OnInit,
- Output,
- type QueryList,
- ViewChild,
ViewEncapsulation,
booleanAttribute,
- forwardRef,
+ computed,
inject,
- numberAttribute,
+ input,
+ output,
+ viewChild,
} from '@angular/core';
-import { NG_VALUE_ACCESSOR } from '@angular/forms';
+import { injectBrnRadioGroup } from './brn-radio-group.token';
-export const BRN_RADIO_GROUP_CONTROL_VALUE_ACCESSOR = {
- provide: NG_VALUE_ACCESSOR,
- useExisting: forwardRef(() => BrnRadioGroupComponent),
- multi: true,
-};
-
-export class BrnRadioChange {
+export class BrnRadioChange {
constructor(
- public source: BrnRadioComponent,
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- public value: any,
+ public source: BrnRadioComponent,
+ public value: T,
) {}
}
@Component({
selector: 'brn-radio',
standalone: true,
- imports: [],
host: {
class: 'brn-radio',
- '[attr.id]': 'id',
- '[class.brn-radio-checked]': 'checked',
- '[class.brn-radio-disabled]': 'disabled',
- '[attr.data-checked]': 'checked',
- '[attr.data-disabled]': 'disabled',
- '[attr.data-value]': 'value',
+ '[attr.id]': 'id()',
+ '[class.brn-radio-checked]': 'checked()',
+ '[class.brn-radio-disabled]': 'disabledState()',
+ '[attr.data-checked]': 'checked()',
+ '[attr.data-disabled]': 'disabledState()',
+ '[attr.data-value]': 'value()',
// Needs to be removed since it causes some a11y issues (see #21266).
'[attr.tabindex]': 'null',
'[attr.aria-label]': 'null',
@@ -58,215 +41,119 @@ export class BrnRadioChange {
// Note: under normal conditions focus shouldn't land on this element, however it may be
// programmatically set, for example inside of a focus trap, in this case we want to forward
// the focus to the native element.
- '(focus)': '_inputElement.nativeElement.focus()',
+ '(focus)': '_inputElement().nativeElement.focus()',
},
exportAs: 'brnRadio',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
-
+
-