Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
050fb46
init
julczka Jan 26, 2021
7179e7e
Update uui-radio-group.element.ts
julczka Jan 26, 2021
5f6a4c1
uncheck method added to uui-radio
julczka Jan 26, 2021
4368d09
Merge branch 'v0/dev' into v0/feature/uui-radio
julczka Feb 1, 2021
006fe3a
Merge branch 'v0/dev' into v0/feature/uui-radio
julczka Feb 2, 2021
9a0acd1
Merge branch 'v0/dev' into v0/feature/uui-radio
julczka Feb 2, 2021
7138191
radio styles done & keybord WIP
julczka Feb 2, 2021
20b3ac3
Merge branch 'v0/dev' into v0/feature/uui-radio
julczka Feb 3, 2021
a677bef
radio keyboard nav work in progeress
julczka Feb 3, 2021
81cac44
radio keyboard navigation
julczka Feb 4, 2021
4a12a09
Merge branch 'v0/dev' into v0/feature/uui-radio
julczka Feb 5, 2021
636bc0e
radio group form associated
julczka Feb 5, 2021
0120b6a
Merge branch 'main' into v0/feature/uui-radio
julczka Feb 5, 2021
a7718ab
Merge branch 'v0/dev' into v0/feature/uui-radio
julczka Feb 5, 2021
02d04c6
Merge branch 'v0/dev' into v0/feature/uui-radio
julczka Feb 15, 2021
9f6b2ef
double checked attribute fixed
julczka Feb 15, 2021
c847780
Programmatically select disabled element, keyboard nav fixed
julczka Feb 15, 2021
b6ccd33
radio group test added
julczka Feb 16, 2021
ff68de0
Merge branch 'v0/dev' into v0/feature/uui-radio
julczka Feb 16, 2021
da430fa
radio events
julczka Feb 16, 2021
2471f16
Merge branch 'v0/dev' into v0/feature/uui-radio
julczka Feb 16, 2021
2907459
radio keybord nav bugfix
julczka Feb 16, 2021
2a7fd3c
refactor single element selection
julczka Feb 17, 2021
95e8023
get slotted radios as promise in firstupdate and slotchange
julczka Feb 17, 2021
47a8ba5
Merge branch 'v0/dev' into v0/feature/uui-radio
julczka Feb 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions COMPONENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Umbraco UI components


### For RFC prototype:

- Avatar
- Avatar Group
- Button
Expand All @@ -11,8 +11,8 @@
- Dialog
- Delete Dialog


### Basics

- Avatar
- Avatar Group
- Badge
Expand Down Expand Up @@ -43,12 +43,15 @@
- TextAreaField?
- Select List
- Select List Item

### Fragments

- Dialog
- Notification
- Tooltip

### Navigation

- Breadcrumbs
- Overlay
- Overlay Manager
Expand Down
6 changes: 6 additions & 0 deletions src/components/basics/uui-radio-group/UUIRadioGroupEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { UUIEvent } from '../../../event/UUIEvent';
import { UUIRadioGroupElement } from './uui-radio-group.element';

export class UUIRadioGroupEvent extends UUIEvent<{}, UUIRadioGroupElement> {
public static readonly CHANGE = 'change';
}
3 changes: 3 additions & 0 deletions src/components/basics/uui-radio-group/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { UUIRadioGroupElement } from './uui-radio-group.element';

customElements.define('uui-radio-group', UUIRadioGroupElement);
251 changes: 251 additions & 0 deletions src/components/basics/uui-radio-group/uui-radio-group.element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import {
LitElement,
html,
css,
property,
query,
internalProperty,
} from 'lit-element';
import { UUIRadioElement } from '../uui-radio/uui-radio.element';
import { UUIRadioEvent } from '../uui-radio/UUIRadioEvent';
import { UUIRadioGroupEvent } from './UUIRadioGroupEvent';

/**
* @element uui-radio-group
* @slot for uui-radio elements
*/

//TODO required?
//TODO focused style
//TODO fix the disabled selected - how that should behave

const ARROW_LEFT = 'ArrowLeft';
const ARROW_UP = 'ArrowUp';
const ARROW_RIGHT = 'ArrowRight';
const ARROW_DOWN = 'ArrowDown';
const SPACE = ' ';

export class UUIRadioGroupElement extends LitElement {
static styles = [css``];

static formAssociated = true;

private _internals;

constructor() {
super();
this._internals = (this as any).attachInternals();
this.addEventListener('keydown', this._onKeydown);
}

connectedCallback() {
super.connectedCallback();
this.setAttribute('role', 'radiogroup');
}

private radioElements!: UUIRadioElement[];

private getRadioElements(): Promise<UUIRadioElement[]> {
return new Promise(resolve => {
const promisedRadios = this.slotElement
? (this.slotElement
.assignedElements({ flatten: true })
.filter(el => el instanceof UUIRadioElement) as UUIRadioElement[])
: [];
resolve(promisedRadios);
});
}

async firstUpdated() {
this.radioElements = await this.getRadioElements();
if (this.radioElements.length > 0)
this.radioElements[0].setAttribute('tabindex', '0');
this._addNameToRadios(this.name, this.radioElements);
if (this.disabled) this._toggleDisableOnChildren(true);
}

@query('slot') protected slotElement!: HTMLSlotElement;

protected get enabledRadioElements(): UUIRadioElement[] {
return this.radioElements.filter(el => !el.disabled);
}

//someone help me refactor these two to one method
private _addNameToRadios(name: string, radios: UUIRadioElement[]) {
radios.forEach(el => (el.name = name));
}

private _toggleDisableOnChildren(value: boolean) {
this.radioElements.forEach(el => (el.disabled = value));
}

private async _handleSlotChange() {
this.radioElements = await this.getRadioElements();

const checkedRadios = this.radioElements.filter(el => el.checked === true);

if (checkedRadios.length > 1) {
this.radioElements.forEach(el => {
el.checked = false;
});
throw new Error(
'There can only be one checked element among the <uui-radio-group> children'
);
}
if (checkedRadios.length === 1) {
this._selected = this.radioElements.indexOf(checkedRadios[0]);
this.value = checkedRadios[0].value;
}
}

private _disabled = false;
@property({ type: Boolean, reflect: true })
get disabled() {
return this._disabled;
}

set disabled(newVal) {
const oldVal = this._disabled;
this._disabled = newVal;
this.requestUpdate('disabled', oldVal);
this._toggleDisableOnChildren(newVal);
}

private _value: FormDataEntryValue = '';
@internalProperty()
get value() {
return this._value;
}
set value(newValue) {
const oldVal = this._value;
this._value = newValue;
this._internals.setFormValue(this._value);
this.requestUpdate('value', oldVal);
}

private _name = '';
@property({ type: String })
get name() {
return this._name;
}

set name(newVal) {
const oldVal = this._name;
this._name = newVal;
if (this.radioElements)
this._addNameToRadios(this._name, this.radioElements);
this.requestUpdate('name', oldVal);
}

private _selected: number | null = null;
@property({ type: Number })
get selected() {
return this._selected;
}

set selected(newVal) {
const oldVal = this._selected;
this._setSelected(newVal);
if (this._selected !== null) {
this.radioElements[this._selected].check();
}
this.requestUpdate('selected', oldVal);
}

private _setSelected(newVal: number | null) {
this._selected = newVal;
this._lastSelectedIndex = this.enabledElementsIndexes.findIndex(
index => index === this._selected
);
if (newVal === null) {
this.radioElements[0].setAttribute('tabindex', '0');
}
const notSelected = this.radioElements.filter(
el => this.radioElements.indexOf(el) !== this._selected
);
notSelected.forEach(el => el.uncheck());
this.value = newVal !== null ? this.radioElements[newVal].value : '';
}

protected get enabledElementsIndexes() {
const indexes: number[] = [];
this.radioElements.forEach(el => {
if (el.disabled === false) indexes.push(this.radioElements.indexOf(el));
});
return indexes;
}

private _lastSelectedIndex = 0; //this is index in the array of enalbled radios indexes (this.enabledElementsIndexes)
private _selectPreviousElement() {
if (
this.selected === null ||
this.selected === this.enabledElementsIndexes[0]
) {
this.selected = this.enabledElementsIndexes[
this.enabledElementsIndexes.length - 1
];
this._lastSelectedIndex = this.enabledElementsIndexes.length - 1;
} else {
this._lastSelectedIndex--;
this.selected = this.enabledElementsIndexes[this._lastSelectedIndex];
}
this._fireChangeEvent();
}

private _selectNextElement() {
if (
this.selected === null ||
this.selected ===
this.enabledElementsIndexes[this.enabledElementsIndexes.length - 1]
) {
this.selected = this.enabledElementsIndexes[0];
this._lastSelectedIndex = 0;
} else {
this._lastSelectedIndex++;
this.selected = this.enabledElementsIndexes[this._lastSelectedIndex];
}
this._fireChangeEvent();
}

private _onKeydown(e: KeyboardEvent) {
switch (e.key) {
case ARROW_LEFT:
case ARROW_UP: {
e.preventDefault();
this._selectPreviousElement();
break;
}
case ARROW_RIGHT:
case ARROW_DOWN: {
e.preventDefault();
this._selectNextElement();
break;
}

case SPACE: {
if (this.selected === null)
this.selected = this.enabledElementsIndexes[0];
}
}
}

private _fireChangeEvent() {
this.dispatchEvent(new UUIRadioGroupEvent(UUIRadioGroupEvent.CHANGE));
}

//TODO add event
private _handleSelectOnClick(e: UUIRadioEvent) {
e.stopPropagation();
this._setSelected(this.radioElements.indexOf(e.target));
this._fireChangeEvent();
}

render() {
return html`
<slot
@slotchange=${this._handleSlotChange}
@change=${this._handleSelectOnClick}
></slot>
`;
}
}
60 changes: 60 additions & 0 deletions src/components/basics/uui-radio-group/uui-radio-group.story.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { html } from 'lit-html';
import './index';

export default {
title: 'Basics/Radio Group',
component: 'uui-radio-group',
};

export const Overview = () =>
html`
<uui-radio-group name="Test">
<uui-radio value="Value 1" disabled>Option 1</uui-radio>
<uui-radio value="Value 2" label="Option 2"></uui-radio>
<uui-radio value="Value 3">Option 3</uui-radio>
<uui-radio value="Value 4" disabled>Option 4</uui-radio>
<uui-radio value="Value 5">Option 5</uui-radio>
<uui-radio value="Value 6">Option 6</uui-radio>
<uui-radio value="Value 7" disabled>Option 7</uui-radio>
</uui-radio-group>
`;

export const doubleSelect = () =>
html`
If you add more then 1 child with "checked" attribiute it will throw an
error in the console and no option will be selected
<uui-radio-group name="Test">
<uui-radio .value=${'Value 1'} checked>Option 1</uui-radio>
<uui-radio .value=${'Value 2'} label="Option 2" checked></uui-radio>
<uui-radio .value=${'Value 3'}>Option 3</uui-radio>
<uui-radio .value=${'Value 4'} disabled>Option 4</uui-radio>
<uui-radio .value=${'Value 5'}>Option 5</uui-radio>
<uui-radio .value=${'Value 6'}>Option 6</uui-radio>
<uui-radio .value=${'Value 7'} disabled>Option 7</uui-radio>
</uui-radio-group>
`;

export const InFrorm = () =>
html`
<form action="">
<uui-radio-group name="Test">
<uui-radio .value=${'Value 1'}>Option 1</uui-radio>
<uui-radio .value=${'Value 3'}>Option 3</uui-radio>
<uui-radio .value=${'Value 2'} label="Option 2"></uui-radio>
</uui-radio-group>
</form>
`;

export const SelectDisabled = () =>
html`
<uui-radio-group name="Test">
<uui-radio .value=${'Value 1'}>Option 1</uui-radio>
<uui-radio
.value=${'Value 2'}
disabled
checked
label="Option 2"
></uui-radio>
<uui-radio .value=${'Value 3'}>Option 3</uui-radio>
</uui-radio-group>
`;
Loading