Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Made a new KRadioButtonGroup component to fix Firefox radio button movement with arrow keys issue #650

Open
wants to merge 20 commits into
base: develop
Choose a base branch
from

Conversation

muditchoudhary
Copy link
Contributor

Description

  • This PR, creates a new KRadioButtonGroup component that fixes radio button movement with arrow keys issues in Firefox.
  • It also has some Unit tests for KRadioButtonGroup.
  • It creates docs for KRadioButtonGroup

Issue addressed

Fixes: 10491

Addresses #PR# HERE

Before/after screenshots

Before

Before.mp4

After

After.mp4

Changelog

  • [#PR no]
    • Description: Summary of change(s)
    • Products impact: Choose from - none (for internal updates) / bugfix / new API / updated API / removed API. If it's 'none', use "-" for all items below to indicate they are not relevant.
    • Addresses: Link(s) to GH issue(s) addressed. Include KDS links as well as links to related issues in a consumer product repository too.
    • Components: Affected public KDS component. Do not include internal sub-components or documentation components.
    • Breaking: Will this change break something in a consumer? Choose from: yes / no
    • Impacts a11y: Does this change improve a11y or adds new features that can be used to improve it? Choose from: yes / no
    • Guidance: Why and how to introduce this update to a consumer? Required for breaking changes, appreciated for changes with a11y impact, and welcomed for non-breaking changes when relevant.

[#PR no]: PR link

Steps to test

  1. Yarn link the Kolibri with KDS. How? given: here
  2. Import KRadioButtonGroup component and wrap KRadioButton components inside KRadioButtonGroup component, in these files:
    2.1 kolibri/plugins/device/assets/src/views/DeviceSettingsPage/index.vue
    2.2 kolibri/core/assets/src/views/language-switcher/LanguageSwitcherModal.vue
  3. Run Server and test in Firefox by visiting these pages:
    3.1 http://127.0.0.1:8000/en/device/#/settings
    3.2 Choose Language

Second way

  1. Paste the given playground.vue code. here
  2. Run the KDS server and visit playground and test.

(optional) Implementation notes

At a high level, how did you implement this?

Does this introduce any tech-debt items?

Testing checklist

  • Contributor has fully tested the PR manually
  • If there are any front-end changes, before/after screenshots are included
  • Critical and brittle code paths are covered by unit tests
  • The change is described in the changelog section above

Reviewer guidance

  • Is the code clean and well-commented?
  • Are there tests for this change?
  • Are all UI components LTR and RTL compliant (if applicable)?
  • Add other things to check for here

After review

  • The changelog item has been pasted to the CHANGELOG.md

Comments

For the change language model KGrid is used, which creates two KRadioButtonGroup components as the array is split.
Although the arrow keys are working fine but when we shift tab it puts focus on the last focus radio button on the first list.
I want to know your thoughts. Is that acceptable?

@muditchoudhary
Copy link
Contributor Author

muditchoudhary commented Jun 4, 2024

Hi @MisRob Could you please review the PR?

@MisRob
Copy link
Member

MisRob commented Jun 5, 2024

Hi @muditchoudhary, thanks a lot! I think as first step, we will QA in Kolibri. Would you be able to open a Kolibri PR for testing that would reference the latest commit of this KDS PR and use KRadioButton at least at one Kolibri place? Our QA team doesn't work with code directly. If you couldn't do that, I can prepare the PR as well.

@muditchoudhary
Copy link
Contributor Author

Sure @MisRob I had tested on Kolibri and I have the changes in stash, so I will have no problem in making a PR. I will send a PR soon.

@MisRob MisRob self-assigned this Jun 7, 2024
@MisRob MisRob self-requested a review June 7, 2024 08:31
Copy link
Member

@MisRob MisRob left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much @muditchoudhary, firstly for researching the issue and approaches to solve it, and then in this PR for finalizing it. Also thanks for taking care of docs and tests. Overall it's great work and feels solid.

I'm leaving a couple of comments. The most important is the one related to many event listeners and it's the only change I request before proceeding to merging. I'd also like to clarify the enable logic.

I'd appreciate if we could have the improvement regarding the dependency to KGridItem, however it's also something that I could open a follow-up issue for. Same applies to the remaining comments that are rather minor. It really depends on your bandwith - let me know what works for you.

await wrapper.vm.$nextTick();
const radioButtons = wrapper.findAllComponents(KRadioButton);
await radioButtons.at(2).trigger('click');
expect(wrapper.vm.focusedRadioIdx).toBe(2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be great if we could test that this radio button is focused without dependency on its internal implementation, that is looking at vm.focusedRadioIdx. Imagine a situation when we'd rename/remove focusedRadioIdx, but in a way that focus would still work properly for users. In such a situation, this test would give negative result, even though there would be no problem for users. So for maintenance and clarity benefits, whenever possible, we to implement tests in a way that's similar to how the user interact with elements.

I think this technique should do https://stackoverflow.com/a/53042010 (but with attachTo as attachToDocument is deprecated)

await radioButtons.at(2).trigger('click');
expect(wrapper.vm.focusedRadioIdx).toBe(2);
expect(radioButtons.at(2).vm.tabIndex).toBe(0);
expect(radioButtons.at(1).vm.tabIndex).toBe(-1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the same reason as mentioned above, here I'd recommend looking into attributes of the DOM node rather than into vm's private data. I assume it could look something like expect(radioButtons.at(1).attributes('tab-index')).toBe(0).

* Specifies whether the radio button group is enabled or disabled.
* Used to make the first radio button active when the radio group becomes enabled again after being disabled.
*/
enable: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand yet in what case we'd need to use enable functionality, would you explain on an example?

mounted() {
this.$nextTick(() => {
if (this.isFirefox) {
if (this.$children[0].$options._componentTag === 'KGridItem') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking specifically for KGridItem, could we recursively search through $children and their $children etc. until we find the level of radio buttons?

This would make KRadioButtonGroup work with any other component that wraps its KRadioButtons, such as KFixedGridItem or even HTML elements such as div.

The alternative would be to properly document that only this single use-case is supported, however it seems to me that this suggestion shouldn't hopefully be complicated and I believe it would also help with clarity and deduplicated some of the code.

gridItem.$children.forEach(fixedGridItem => {
fixedGridItem.$children.forEach(radioBtn => {
this.radioButtons.push(radioBtn);
radioBtn.$el.addEventListener('keyup', this.onKeyUp);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In larger groups of radio buttons, such as the language selection, adding an event listener to each one of them could potentially have negative performance impact, particularly in combination with looping through many elements. A general recommendation we follow in our codebases is to delegate listeners to a container element higher in the structure, so that we have as less listeners as possible.

In this case, KRadioButtonGroup element when mounted (and only in Firefox) would attach a single keyup event listener. This event's handler function would then determine whether event.target was a radio button, and if yes, it would run the logic needed.

I would also recommend doing the very same thing for the input event (line 71)

So you'd have something like

mounted() {
  this.$el.addEventListener('keyup', onKeyUp); 
  this.$el.addEventListener('input', onInput);
  ...

and these two would be the only event listeners in KRadioButtonGroup.

Related logic would need to be adjusted to work in this updated way.

},
mounted() {
this.$nextTick(() => {
if (this.isFirefox) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a minor suggestion that could help with code readability. This would avoid one level of nesting:

mounted() {
  if (!this.isFirefox) {
    return;
  }
  this.$nextTick(() => {
  ...
  ...
  ...

},
watch: {
// To focus on the first radio button when radio group enables again
enable(newVal, oldVal) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that before running the code below, we'd also need isFirefox check here

},
focusRadio(radioIdx, event) {
if (radioIdx !== undefined && radioIdx !== null) {
this.radioButtons[radioIdx].toggleCheck(event);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're calling KRadioButton's (currently private) toggleCheck method, could you please add @public JSDoc comment to toggleCheck, similarly to how you did for setTabIndex? It will ensure that if refactoring KRadioButton, we will know that the method is being used from another component and don't remove/rename it accidentally.

@@ -220,6 +222,13 @@
*/
this.$emit('blur');
},
/**
* @public
* Set the tabIndex value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for making sure its documented. Just a little improvement for clarity:

Suggested change
* Set the tabIndex value
* Sets `tab-index` on input

@muditchoudhary
Copy link
Contributor Author

Thanks a lot, @MisRob for giving the detailed review and suggesting changes. I will send the changes soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Keyboard navigation inside radio button group (Firefox)
2 participants