Skip to content

Commit

Permalink
fix: default value propagation inside ControlValueAccessors
Browse files Browse the repository at this point in the history
  • Loading branch information
Rikarin committed Jun 6, 2024
1 parent 5f247f1 commit 337a220
Show file tree
Hide file tree
Showing 21 changed files with 98 additions and 75 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
xUI is a customizable Angular 15 - 18 UI Library with gaming look which can be easily adapted to your brand.
Contains default dark and light theme with SCSS functions to generate your own themes in a few steps.


## Features

- **35+ High-Quality Angular Components:** Ready to use out of the box.
Expand All @@ -27,7 +26,6 @@ Contains default dark and light theme with SCSS functions to generate your own t
- **Powerful Theme Customization:** Detailed customization options.
- **TypeScript:** Written with predictable static types.


## Table of Contents

- [StackBlitz Demo](https://stackblitz.com/fork/xui)
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/docs/app.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<xui-layout class="dark-theme">
<xui-sider width="280px">
<div class="logo" routerLink="/"><span class="x-logo">x</span>UI</div>
<div class="logo" [tabindex]="-1" routerLink="/"><span class="x-logo">x</span>UI</div>
<xui-menu mode="default">
<xui-menu-item link="/docs">Overview</xui-menu-item>
<xui-menu-item link="/docs/getting-started">Getting Started</xui-menu-item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<xui-radio-option value="first">First</xui-radio-option>
<xui-radio-option value="second">Second</xui-radio-option>
<xui-radio-option color="success" value="third">Third</xui-radio-option>
<xui-radio-option color="primary" value="forth" disabled>Disabled</xui-radio-option>
<xui-radio-option color="primary" value="fourth" disabled>Disabled</xui-radio-option>
<xui-radio-option color="error" value="fifth">
With description
<div xuiDescription>Lorem ipsum dolor sit a met</div>
Expand Down Expand Up @@ -49,7 +49,7 @@
<xui-radio-option value="first">First</xui-radio-option>
<xui-radio-option value="second">Second</xui-radio-option>
<xui-radio-option color="success" value="third">Third</xui-radio-option>
<xui-radio-option color="primary" value="forth" disabled>Disabled</xui-radio-option>
<xui-radio-option color="primary" value="fourth" disabled>Disabled</xui-radio-option>
<xui-radio-option color="error" value="fifth">
With description
<div xuiDescription>Lorem ipsum dolor sit a met</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ import { CommonModule } from '@angular/common';
})
export class RadioListComponent {
model = new FormControl('second');
disabledModel = new FormControl({ value: 'second', disabled: true });
disabledModel = new FormControl({ value: 'fourth', disabled: true });
langs = new FormControl('en_US');
}
9 changes: 9 additions & 0 deletions libs/theme-core/src/core/normalize.scss
Original file line number Diff line number Diff line change
Expand Up @@ -333,10 +333,19 @@
&:hover {
text-decoration: underline;
}

&:focus-visible {
text-decoration: underline;
}
}

// TODO: Remove this after moving settings into overlay
.cdk-overlay-container {
z-index: 10000;
}

*:focus-visible {
outline: 2px solid var(--focus-color);
z-index: 1;
}
}
2 changes: 1 addition & 1 deletion libs/theme-core/src/theming/theming.scss
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
--text-muted-opacity: 0.5;

--text-link-color: var(--color-info-default);
--focus-color: var(--color-info-400);
--focus-color: #01a8fc;
}

@mixin create-elevation-variables($theme) {
Expand Down
10 changes: 6 additions & 4 deletions libs/xui/src/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ export class XuiCheckbox implements ControlValueAccessor {
_disabled = signal(false);
_value = signal(false);

disabled = input(false, { transform: (v: string | boolean) => convertToBoolean(v) });
color = input<CheckboxColor>('primary');
value = input<boolean>(false);
value = input<boolean>();
disabled = input<boolean | undefined, string | boolean>(undefined, {
transform: (v: string | boolean) => convertToBoolean(v)
});

constructor(
private configService: XuiConfigService,
Expand All @@ -53,8 +55,8 @@ export class XuiCheckbox implements ControlValueAccessor {
this.control.valueAccessor = this;
}

effect(() => this._disabled.set(this.disabled()), { allowSignalWrites: true });
effect(() => this._value.set(this.value()), { allowSignalWrites: true });
effect(() => this.disabled() && this._disabled.set(this.disabled()!), { allowSignalWrites: true });
effect(() => this.value() && this._value.set(this.value()!), { allowSignalWrites: true });
effect(() => this.onChange?.(this._value()));
}

Expand Down
8 changes: 5 additions & 3 deletions libs/xui/src/date-picker/date-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ export class XuiDatePicker implements ControlValueAccessor {
_disabled = signal(false);

@Input() disabledDate?: (current: DateTime) => boolean;
value = input<string | null>();
value = input<string>();
placeholder = input<string>();
color = input<DatePickerColor>('light');
size = input<DatePickerSize>('large');
disabled = input(false, { transform: (v: string | boolean) => convertToBoolean(v) });
disabled = input<boolean | undefined, string | boolean>(undefined, {
transform: (v: string | boolean) => convertToBoolean(v)
});
readOnly = input(false, { transform: (v: string | boolean) => convertToBoolean(v) });
allowClear = input(false, { transform: (v: string | boolean) => convertToBoolean(v) });

Expand All @@ -74,7 +76,7 @@ export class XuiDatePicker implements ControlValueAccessor {
this.control.valueAccessor = this;
}

effect(() => this._disabled.set(this.disabled()), { allowSignalWrites: true });
effect(() => this.disabled() && this._disabled.set(this.disabled()!), { allowSignalWrites: true });
effect(
() => {
this.onChange?.(this._value()?.toISODate() ?? null);
Expand Down
6 changes: 4 additions & 2 deletions libs/xui/src/image-upload/image-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export class XuiImageUpload implements ControlValueAccessor {
type = input<ImageUploadType>('square');
aspectRatio = input(1);
hoverLabel = input('xui.image_upload.change_image');
disabled = input(false, { transform: (v: string | boolean) => convertToBoolean(v) });
disabled = input<boolean | undefined, string | boolean>(undefined, {
transform: (v: string | boolean) => convertToBoolean(v)
});

_borderRadius = computed(() => (this.type() === 'round' ? 50 : 4));
_backgroundImageUrl = computed(() => (this._backgroundImage() ? `url(${this._backgroundImage()})` : null));
Expand All @@ -60,7 +62,7 @@ export class XuiImageUpload implements ControlValueAccessor {
this.control.valueAccessor = this;
}

effect(() => this._disabled.set(this.disabled()), { allowSignalWrites: true });
effect(() => this.disabled() && this._disabled.set(this.disabled()!), { allowSignalWrites: true });
}

handleFileInput(event: unknown) {
Expand Down
10 changes: 6 additions & 4 deletions libs/xui/src/input/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ export class XuiInput implements ControlValueAccessor {
_disabled = signal(false);
_value = signal<string | null>(null);

value = input<string | null>(null);
value = input<string>();
placeholder = input<string>();
color = input<InputColor>('light');
size = input<InputSize>('large');
type = input<InputType>('text');
dataList = input<string[] | null>();
disabled = input(false, { transform: (v: string | boolean) => convertToBoolean(v) });
disabled = input<boolean | undefined, string | boolean>(undefined, {
transform: (v: string | boolean) => convertToBoolean(v)
});
readOnly = input(false, { transform: (v: string | boolean) => convertToBoolean(v) });

_styles = computed(() => {
Expand Down Expand Up @@ -66,8 +68,8 @@ export class XuiInput implements ControlValueAccessor {
this.control.valueAccessor = this;
}

effect(() => this._disabled.set(this.disabled()), { allowSignalWrites: true });
effect(() => this._value.set(this.value()), { allowSignalWrites: true });
effect(() => this.disabled() && this._disabled.set(this.disabled()!), { allowSignalWrites: true });
effect(() => this.value() && this._value.set(this.value()!), { allowSignalWrites: true });
effect(() => this.onChange?.(this._value()));
}

Expand Down
43 changes: 15 additions & 28 deletions libs/xui/src/radio-list/radio-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,27 @@ import { XuiRadioOption } from './radio-option';
'[tabindex]': '_disabled() ? -1 : 0',
'(keydown.arrowup)': '_prev($event)',
'(keydown.arrowdown)': '_next($event)',
'(mousedown)': '_mouseDown2()',
'(mouseup)': '_mouseUp()',
'(mousedown)': '_mouseDown = true',
'(mouseup)': '_mouseDown = false',
'(focusin)': '_focusIn()',
'(focusout)': '_focusOut()'
}
})
export class XuiRadioList implements RadioListAccessor, ControlValueAccessor {
private onChange?: (source: RadioListValue) => void;
private onTouched?: () => void;
private _mouseDown = false;
_mouseDown = false;

_value = signal<RadioListValue>(null);
_disabled = signal(false);
_focusedValue = signal<RadioListValue>(null);

value = input<RadioListValue>(null);
value = input<RadioListValue>();
size = input<RadioListSize>('md');
color = input<RadioListColor>('light');
disabled = input(false, { transform: (v: string | boolean) => convertToBoolean(v) });
disabled = input<boolean | undefined, string | boolean>(undefined, {
transform: (v: string | boolean) => convertToBoolean(v)
});

@ContentChildren(XuiRadioOption) private optionsRef!: QueryList<XuiRadioOption>;

Expand All @@ -57,8 +59,8 @@ export class XuiRadioList implements RadioListAccessor, ControlValueAccessor {
this.control.valueAccessor = this;
}

effect(() => this._disabled.set(this.disabled()), { allowSignalWrites: true });
effect(() => this._value.set(this.value()), { allowSignalWrites: true });
effect(() => this.disabled() && this._disabled.set(this.disabled()!), { allowSignalWrites: true });
effect(() => this.value() && this._value.set(this.value()!), { allowSignalWrites: true });
effect(() => this.onChange?.(this._value()));
}

Expand Down Expand Up @@ -90,14 +92,6 @@ export class XuiRadioList implements RadioListAccessor, ControlValueAccessor {
this.select(false);
}

_mouseDown2() {
this._mouseDown = true;
}

_mouseUp() {
this._mouseDown = false;
}

_focusIn() {
if (!this._mouseDown) {
if (this._value() == null) {
Expand All @@ -113,28 +107,21 @@ export class XuiRadioList implements RadioListAccessor, ControlValueAccessor {
this.onTouched?.();
}

private select(prev: boolean) {
private select(backwards: boolean) {
if (this._disabled()) {
return;
}

const options = this.optionsRef.toArray();
let next = options.findIndex(x => x.value() == this._value());
let iteration = 0;
let iterations = options.length;

do {
next += prev ? -1 : 1;

if (prev && next < 0) {
next = options.length - 1;
iteration++;
} else if (!prev && next >= options.length) {
next = 0;
iteration++;
}
next += backwards ? options.length - 1 : 1;
next %= options.length;

if (iteration == 2) {
return;
if (!iterations--) {
throw 'No enabled options';
}
} while (options[next].disabled());

Expand Down
10 changes: 6 additions & 4 deletions libs/xui/src/radio/radio-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,20 @@ export class XuiRadioGroup implements RadioGroupAccessor, ControlValueAccessor {
_disabled = signal(false);
_value = signal<RadioValue>(null);

value = input<RadioValue>(null);
value = input<RadioValue>();
color = input<RadioColor>('none');
items = input<RadioItem[]>();
disabled = input(false, { transform: (v: string | boolean) => convertToBoolean(v) });
disabled = input<boolean | undefined, string | boolean>(undefined, {
transform: (v: string | boolean) => convertToBoolean(v)
});

constructor(@Self() @Optional() public control?: NgControl) {
if (this.control) {
this.control.valueAccessor = this;
}

effect(() => this._disabled.set(this.disabled()), { allowSignalWrites: true });
effect(() => this._value.set(this.value()), { allowSignalWrites: true });
effect(() => this.disabled() && this._disabled.set(this.disabled()!), { allowSignalWrites: true });
effect(() => this.value() && this._value.set(this.value()!), { allowSignalWrites: true });
effect(() => this.onChange?.(this._value()));
}

Expand Down
2 changes: 1 addition & 1 deletion libs/xui/src/radio/radio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { convertToBoolean } from '../utils';
host: {
class: 'x-radio',
'[class.x-radio-disabled]': '_disabled()',
'[class]': `"x-radio-" + color() ?? _group.color`,
'[class]': `"x-radio-" + color() ?? _group.color()`,
'(click)': '_click()'
}
})
Expand Down
1 change: 1 addition & 0 deletions libs/xui/src/select/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { XUI_SELECT_ACCESSOR, SelectAccessor, SelectValue } from './select.types
'[class]': '"x-select-option-" + _select.color',
'[class.x-select-option-selected]': '_isSelected()',
'[class.x-select-option-disabled]': 'disabled()',
'[tabIndex]': 'disabled() ? -1 : 0',
'(click)': '_click()'
}
})
Expand Down
3 changes: 3 additions & 0 deletions libs/xui/src/select/select.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
[tabindex]="_disabled() ? -1 : 0"
(click)="open()"
cdkOverlayOrigin
[cdkTrapFocus]="_isOpened()"
#trigger="cdkOverlayOrigin"
(keydown.enter)="_isOpened.set(true)"
(keydown.space)="_isOpened.set(true)"
>
{{ !_viewValue() ? placeholder() ?? '&nbsp;' : _viewValue() }}
<xui-icon [icon]="_isOpened() ? 'keyboard_arrow_up' : 'keyboard_arrow_down'"></xui-icon>
Expand Down
3 changes: 2 additions & 1 deletion libs/xui/src/select/select.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { OverlayModule } from '@angular/cdk/overlay';
import { XuiOption } from './option';
import { XuiIcon } from '../icon';
import { XuiDecagram } from '../decagram';
import { A11yModule } from '@angular/cdk/a11y';

@NgModule({
imports: [CommonModule, FormsModule, OverlayModule, XuiIcon, XuiDecagram, TranslateModule.forChild()],
imports: [CommonModule, FormsModule, A11yModule, OverlayModule, XuiIcon, XuiDecagram, TranslateModule.forChild()],
declarations: [XuiSelect, XuiOption],
exports: [XuiSelect, XuiOption]
})
Expand Down
10 changes: 6 additions & 4 deletions libs/xui/src/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ export class XuiSelect implements SelectAccessor, ControlValueAccessor {
_disabled = signal(false);
_value = signal<SelectValue>(null);

value = input<SelectValue>(null);
value = input<SelectValue>();
placeholder = input<string>();
color = input<SelectColor>('light');
size = input<SelectSize>('large');
items = input<SelectItem[]>();
disabled = input(false, { transform: (v: string | boolean) => convertToBoolean(v) });
disabled = input<boolean | undefined, string | boolean>(undefined, {
transform: (v: string | boolean) => convertToBoolean(v)
});

_styles = computed(() => {
const ret: { [klass: string]: boolean } = {
Expand All @@ -63,8 +65,8 @@ export class XuiSelect implements SelectAccessor, ControlValueAccessor {
this.control.valueAccessor = this;
}

effect(() => this._disabled.set(this.disabled()), { allowSignalWrites: true });
effect(() => this._value.set(this.value()), { allowSignalWrites: true });
effect(() => this.disabled() && this._disabled.set(this.disabled()!), { allowSignalWrites: true });
effect(() => this.value() && this._value.set(this.value()!), { allowSignalWrites: true });
effect(() => this.onChange?.(this._value()));
}

Expand Down
Loading

0 comments on commit 337a220

Please sign in to comment.