Skip to content

Commit

Permalink
feat(addon/components/paper-radio-group): converts to a glimmer compo…
Browse files Browse the repository at this point in the history
…nent.
  • Loading branch information
matthewhartstonge committed Nov 15, 2024
1 parent 197d3dd commit f5c7e3c
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 56 deletions.
44 changes: 31 additions & 13 deletions addon/components/paper-radio-group.hbs
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
{{! template-lint-disable no-action }}
{{yield (hash
label=(component this.labelComponent
setAriaLabelledby=(action (mut this.ariaLabelledby))
)
radio=(component this.radioComponent
toggle=this.toggle
disabled=this.disabled
groupValue=@groupValue
onChange=(action "onChange")
parentComponent=this
shouldRegister=true)
)}}
<md-radio-group
class="{{if this.focused " md-focused"}} {{@class}}"
aria-label={{@ariaLabel}}
aria-labelledby={{if this.hasLabel this.labelId}}
disabled={{this.disabled}}
role="radiogroup"
tabindex="0"
{{did-insert this.didInsertNode}}
{{will-destroy this.willDestroyNode}}
{{on 'keydown' this.onKeyDown}} {{! template-lint-disable no-down-event-binding }}
...attributes
>
{{yield
(hash
label=(component
this.labelComponent
labelId=this.labelId
setAriaLabelledby=this.didInsertLabel
)
radio=(component
this.radioComponent
toggle=this.toggle
disabled=this.disabled
groupValue=@groupValue
onChange=this.onChange
parentComponent=this
shouldRegister=true
)
)
}}
</md-radio-group>
173 changes: 130 additions & 43 deletions addon/components/paper-radio-group.js
Original file line number Diff line number Diff line change
@@ -1,84 +1,171 @@
/* eslint-disable ember/no-actions-hash, ember/no-classic-components, ember/no-get, ember/no-mixins, ember/require-tagless-components */
/**
* @module ember-paper
*/
import Focusable from './-focusable';
import { inject as service } from '@ember/service';

import { filterBy, mapBy, notEmpty } from '@ember/object/computed';
import Component from '@ember/component';
import { tracked } from '@glimmer/tracking';
import { A } from '@ember/array';
import { assert } from '@ember/debug';
import FocusableMixin from 'ember-paper/mixins/focusable-mixin';
import { ParentMixin } from 'ember-composability-tools';
import { action } from '@ember/object';
import { guidFor } from '@ember/object/internals';
import { isPresent } from '@ember/utils';
import { invokeAction } from 'ember-paper/utils/invoke-action';

/**
* @class PaperRadioGroup
* @extends Ember.Component
* @uses FocusableMixin
* @uses ParentMixin
*/
export default Component.extend(FocusableMixin, ParentMixin, {
tagName: 'md-radio-group',
tabindex: 0,
export default class PaperRadioGroup extends Focusable {
@service constants;

/**
* Reference to the component's DOM element
* @type {HTMLElement}
*/
element;
/**
* labelComponent specifies the component to be yielded as a radio group label.
* @type {string}
*/
labelComponent;
/**
* radioComponent specifies the component to be yielded as a radio.
* @type {string}
*/
radioComponent;
/**
* provides a globally unique component id for tracking bindings between aria
* tags and labels.
* @type {string}
*/
labelId;
/**
* enables toggling the returned value from a child component
* @type {boolean}
*/
toggle;

/* FocusableMixin Overrides */
focusOnlyOnKey: true,
/* Focusable Overrides */
focusOnlyOnKey = true;

radioComponent: 'paper-radio',
labelComponent: 'paper-radio-group-label',
role: 'radiogroup',
constants: service(),
/**
* Array of child components
* @type {A}
*/
@tracked children;
/**
* tracks whether the label id should be displayed.
* @type {boolean}
*/
@tracked hasLabel;

// Lifecycle hooks
init() {
this._super(...arguments);
constructor(owner, args) {
super(owner, args);

this.children = A([]);
this.hasLabel = false;
this.labelId = `${guidFor(this)}-label`;
this.toggle = this.args.toggle || false;

this.labelComponent = this.args.labelComponent || 'paper-radio-group-label';
this.radioComponent = this.args.radioComponent || 'paper-radio';

assert(
'{{paper-radio-group}} requires an `onChange` action or null for no action',
this.onChange !== undefined
'<PaperRadioGroup> requires an `onChange` action or null for no action',
this.args.onChange !== undefined
);
},
}

/**
* Performs any required DOM setup.
* @param element
*/
@action didInsertNode(element) {
this.registerListeners(element);
}

attributeBindings: ['role', 'ariaLabelledby:aria-labelledby'],
@action didUpdateNode() {
// noop
}

enabledChildRadios: filterBy('childComponents', 'disabled', false),
childValues: mapBy('enabledChildRadios', 'value'),
hasLabel: notEmpty('labelNode'),
/**
* Performs any required DOM teardown.
* @param element
*/
@action willDestroyNode(element) {
this.unregisterListeners(element);
}

keyDown(ev) {
/**
* Registers a child form component
* @param {Component} child - The form component to register
*/
@action registerChild(child) {
this.children.pushObject(child);
}
/**
* Removes a registered child form component
* @param {Component} child - The form component to unregister
*/
@action unregisterChild(child) {
this.children.removeObject(child);
}

get enabledChildRadios() {
let filteredChildren = A(this.children.filter((c) => c.disabled === false));
return filteredChildren;
}
get childValues() {
return this.enabledChildRadios.map((c) => c.value);
}
/**
* provides a callback to notify if a label has been injected into the DOM
* to enable aria-labelledby being rendered on the radio group.
*/
@action didInsertLabel() {
this.hasLabel = true;
}

@action onKeyDown(ev) {
switch (ev.which) {
case this.get('constants.KEYCODE.LEFT_ARROW'):
case this.get('constants.KEYCODE.UP_ARROW'):
case this.constants.KEYCODE.LEFT_ARROW:
case this.constants.KEYCODE.UP_ARROW:
ev.preventDefault();
this.select(-1);
break;
case this.get('constants.KEYCODE.RIGHT_ARROW'):
case this.get('constants.KEYCODE.DOWN_ARROW'):
case this.constants.KEYCODE.RIGHT_ARROW:
case this.constants.KEYCODE.DOWN_ARROW:
ev.preventDefault();
this.select(1);
break;
}
},
}

select(increment) {
let groupValue = this.groupValue;
let groupValue = this.args.groupValue;
let index = 0;

if (isPresent(groupValue)) {
index = this.childValues.indexOf(groupValue);
index += increment;
let length = this.get('childValues.length');
let length = this.childValues.length;
index = ((index % length) + length) % length;
}

let childRadio = this.enabledChildRadios.objectAt(index);
childRadio.set('focused', true);
invokeAction(this, 'onChange', childRadio.get('value'));
},

actions: {
onChange(value) {
invokeAction(this, 'onChange', value);
},
},
});
if (childRadio) {
childRadio.focused = true;
if (this.args.onChange) {
this.args.onChange(childRadio.value);
}
}
}

@action onChange(value) {
if (this.args.onChange) {
this.args.onChange(value);
}
}
}

0 comments on commit f5c7e3c

Please sign in to comment.