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

feat: Readonly attribute for ion-checkbox, ion-toggle, ion-radio #29603

Open
3 tasks done
fudom opened this issue Jun 10, 2024 · 7 comments
Open
3 tasks done

feat: Readonly attribute for ion-checkbox, ion-toggle, ion-radio #29603

fudom opened this issue Jun 10, 2024 · 7 comments
Assignees
Labels

Comments

@fudom
Copy link

fudom commented Jun 10, 2024

Prerequisites

Describe the Feature Request

I updated Ionic from 6 (to 7) to 8 and have problems with the breaking changes of the input components. Feedback: I'm not sure if the new label attribute solution is better than the ion-label before. It restricts a lot of features like text-wrap and more complex labels like sub-label etc. If I use the legacy ion-label, it blocks the click event and you cannot toggle the checkbox anymore... I'm sure you had good reasons for that, but it feels like one step forward and two steps backward. Anyway...

The reason for this ticket is the following problem:

The new behavior of ion-item with an input element like e.g. ion-toggle is now different:

  • Ionic 6: The click event of ion-item didn't affected the input element inside.
  • Ionic 8: The click event of ion-item is directed to the input. Item click always toggles checked.

What's the problem? Well, it's ok. But you can't control the ion-toggle (or checkbox whatever) indirectly anymore. Let me show an use case.

Describe the Use Case

<ion-item button (click)="toggleValue()">
  <ion-toggle class="no-pointer-events" [checked]="value" justify="space-between">
    Toggle Me
  </ion-toggle>
</ion-item>

The code above is a migration from Ionic 6 to 8. I use the new label style. Well, looks good. But the problem is, that (click) on item changes the toggle value. In this case, I don't want to change the value directly. Therefore I added pointer-event: none to it. Works great in Ionic 6. Ionic 8 does not care about. Because ion-items handles it now.

Background: The function toggleValue on click calls and changes the value if possible. But now the binding checked is ignored and the toggle is always toggles. Or toggles twice if the value is toggles indirectly when clicked.

Describe Preferred Solution

I want disconnect the input element like ion-toggle or ion-checkbox from ion-item to use the click event without affecting the input element. Like stopPropagation or preventDefault which I already tried. Maybe add an attribute mute or passive to the input? Btw. readonly does also not work and disabled would change the appearence and would the click event would not work.

In other words, in this case: The ion-toggle etc should only be used as indicator. A dummy only for showing the current state. It should not be controlled directly by click. Only indirectly by item click event. We could implement readonly which is the same as disabled but appears regular (not grayed out).

My current workaround: Use disabled and a class name to set the opacity back to 1. Fortunately, the click event of ion-item still works. It seems to work as expected. So readonly attribute may fix this issue.

@liamdebeasi
Copy link
Contributor

liamdebeasi commented Jun 11, 2024

This might be the same request as #20521 (just with checkbox instead of toggle)

It restricts a lot of features like text-wrap and more complex labels like sub-label etc.

You can still do this with the new syntax. Check out the playground demo on https://ionicframework.com/docs/api/item#text (this example uses a toggle with wrapping/subtext, but it works the same way with checkboxes).

@liamdebeasi
Copy link
Contributor

Also I wouldn’t recommend the usage you are proposing as it creates a nested interactive. A nested interactive is a bad practice that makes your app less accessible because screen readers don’t always know which interactive to focus. In this case there is a checkbox inside of a button.

If you want to indirectly control the checkbox using another button in the item I’d try putting the checkbox in the start slot and putting the toggle button in the default slot.

@fudom
Copy link
Author

fudom commented Jun 11, 2024

In Ionic 6 I used pointer-events: none for the toggle/checkbox inside an item. This allowed me to toggle it with a click on ion-item and prevents nested interactive action.

With Ionic 8 (I skipped 7), this works out of the box which is great. Now you can click on item to toggle the value without hacks (additional changes). But this prevents me from using the item click to toggle the value anymore. If I do so, it toggles twice which appears as not changed. You know what I mean? I use the click event to have more control over how to toggle. And the toggle/checkbox just indicates the actual state. Use case: On item click > try to enable something > may keep it disabled.

I think implementing readonly (like text inputs have), will fix this issue. It should behave like disabled without opacity changes. It appears with regular styles. But also used pointer-events none to prevent nested actions. This is my current workaround. I use the disabled attribute to mute it and an additional css selector to reset the opacity back to 1. I think this would be the best and easiest way. Just officially supported by Ionic with the readonly attribute.

Alternative: $event.preventDefault/stopPropagation on item. But how can Ionic detect this? It's more effort and more complex for the dev to work with than just adding readonly attribute.

Just a demo of my current solution:

<ion-item button (click)="toggle()">
  <ion-label class="ion-text-wrap">
    <h2>Toggle me</h2>
    <p>Toggles the value if possible.</p>
  </ion-label>
  <ion-toggle slot="end" readonly disabled [checked]="value"></ion-toggle>
</ion-item>
// Appears as readonly. Can be removed when Ionic officially supports it.
[readonly][disabled] {
  opacity: 1;
}

// Adds a gap to text.
.in-item[slot='end'] {
  margin-left: 20px;
}

item

The toggle is not used as input directly. It's more like an icon, an indicator. It cannot be changed directly. Therefore I used the layout like from Ionic 6 with item slots. But I could also use the new label style. It does not really matter for this issue/case. It's about readonly input.

@liamdebeasi
Copy link
Contributor

If I do so, it toggles twice which appears as not change

Inside of ion-toggle is a label. When a click event is dispatched on that element (by the user clicking) it will dispatch another click event on any descendant inputs. Both of these click events bubble up across the shadow dom boundary. You can filter out the duplicate click event by ignoring ones with pointerId: -1

The toggle is not used as input directly. It's more like an icon, an indicator. It cannot be changed directly. Therefore I used the layout like from Ionic 6 with item slots. But I could also use the new label style. It does not really matter for this issue/case. It's about readonly input.

I'm not entirely sure why you are wanting to do this as it goes against both iOS and Android design principles. Even if you removed the pointer events from the toggle screen readers would still read it as a toggle instead of an icon which could confused users.

@liamdebeasi
Copy link
Contributor

liamdebeasi commented Jun 11, 2024

Also your current solution is confusing because screen readers will inform users that the toggle is not interactive because it is disabled. However, clicking on the item will still update the toggle.

I'd strongly recommend following the patterns shown on https://ionicframework.com/docs/api/item#content-types. These patterns align with how list items in iOS and Android behave. By aligning with this patterns you can ensure that you are providing an experience that is familiar to a majority of your users.

@fudom
Copy link
Author

fudom commented Jun 11, 2024

I don't really care about screen readers. It's a Cordova/Capacitor app always in md mode. In general, I agree with you. But I don't have time for major changes. How would you migrate this? Especially the click event which does not always toggle the value. For demo lets say: this.value = Math.random() > 0.5. I could use [(ngModel)] with get and set. The get replaces [checked] and set replaces (click). But is more effort to implement. At the moment I am under time pressure to migrate the app. But I will take a closer look at other solutions you mentioned when I get the chance. My solution with readonly works good for now.

@fudom fudom changed the title feat: Option to disconnect ion-item click event with input element. feat: Readonly attribute for ion-checkbox, ion-toggle, ion-radio Jun 11, 2024
@liamdebeasi
Copy link
Contributor

Screen readers exist on mobile devices (VoiceOver on iOS and TalkBack on Android) and can be used with Capacitor/Cordova apps.

Doing something like this will give you an experience that is accessible and familiar to most users: https://codepen.io/liamdebeasi/pen/pomWpZr

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

No branches or pull requests

3 participants