-
Notifications
You must be signed in to change notification settings - Fork 105
Add v-model support to KCheckbox #1140
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
base: develop
Are you sure you want to change the base?
Add v-model support to KCheckbox #1140
Conversation
|
Hi @WinnyChang, good to see you around and thanks a lot :) I will review documentation. For the feature itself, we will assign a reviewer next week. |
|
@MisRob Got it. Thanks for the update! |
AlexVelezLl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot, @WinnyChang, this looks pretty good! However, this change was not intended to be a breaking change, i.e,. KDS consumers should not need to migrate all existing checkboxes to the new v-model api after this change is released; it should be an additional, optional api. So both APIs should be supported by KCheckbox: the v-model one and the :checked/@change API. Apart from that, this looks good!
| /** | ||
| * Checked state | ||
| * Reactive v-model checkbox state. Updates with the `input` event. | ||
| */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these double comment blocks the intended result to only show the first one in the docs page?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that’s intentional — only the first comment block is shown. The hidden ones are copied from Studio's Checkbox.
I chose to show only the first ones since the comments are quite long, but I can include all of them in the docs page if you think that would be better.
Ah yes, I should have mentioned it explicitly in the issue - apologies @WinnyChang @AlexVelezLl. Preventing a breaking change is an important aspect, and another reason is that even though As for the documentation, I would suggest that the above is reflected:
|
…s' description, add aditional unit tests
|
Just leaving a note that I skimmed quickly through the updated documentation and it felt clear to me, and as balanced amount of information. Very nice work, thank you. |
Percy Visual Test ResultsPercy Dashboard: View Detailed Report Environment:
Instructions for Reviewers:
|
AlexVelezLl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @WinnyChang! We're almost there! Just found a little regression in Kolibri because of an unrecognized legacy api usage, and reflected on a possible way this could be implemented safely.
| usingLegacyApi() { | ||
| return this.checked !== undefined; | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to be extra cautious, the old API assumed that if checked was null or undefined, the checkbox was unchecked, and there may be some consumers who rely on this too. So, to determine if the legacy api is being used, I think we can instead assert against the new value or inputValue, and if any of them are set, then we are not using the legacy API (which would 100% cover all examples of KCheckbox right now).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a brief skim, I found a place in Kolibri where we assume that undefined is the falsy value of the checkbox, and this PR is introducing a regression on this checkbox:
Grabacion.de.pantalla.2025-10-28.a.la.s.1.34.14.p.m.mov
| this.isChecked = !this.isChecked; | ||
| if (this.usingLegacyApi) { | ||
| /** | ||
| * Emits change event with the new legacy API checkbox state. | ||
| */ | ||
| this.$emit('change', !this.checked, event); | ||
| } | ||
| } | ||
| }, | ||
| updateInputValue(newValue) { | ||
| /** | ||
| * Emits input event with the new v-model checkbox state. | ||
| * Used by v-model to keep the checkbox state in sync. | ||
| */ | ||
| this.$emit('input', newValue); | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can centralize the emission of the input/change event in the updateInputValue and let it decide which event to emit. This way, we abstract the change of the isChecked computed property. If, for any reason, another method changes its value, the different methods should not have to determine whether to emit or not the change event. (Not sure if I made this clear, if not please let me know 😅)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other option may be to use the @change event as the input event for the v-model instead of @input, and just always emit this event for all values independently of the api that is being used. I think that may be the safest way to prevent any unintended behavior if, for any reason, a consumer sets an undefined value as a falsy value, but they intended to use the v-model api. What do you think @WinnyChang, @MisRob?
This will prevent any confusion if we ever, for any reason (not reading the documentation well 😅), declare something like:
<template>
<KCheckbox
v-model="checkboxValue"
...
/>
</template>
<script>
...
setup() {
return {
checkboxValue: ref(),
}
}
</script>This would fail if we determine that this is not using the new api because both their value and input value are undefined, then we won't emit the input event, and would emit the change event instead. But if we used the same event for both, we wouldn't need to worry about cases like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can centralize the emission of the input/change event in the updateInputValue and let it decide which event to emit.
In my current code, I separate the mechanism of the legacy API and v-model, so the legacy API won't trigger updateInputValue. Is it okay to trigger updateInputValue when using the legacy API and put the input/change emissions together?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! That would be ideal! It should be the responsibility of the computed property to determine how to manage the changes to the isChecked property, in any case. This helps us centralize the property update mechanism. If, for some reason, we want to change the value of this variable in another method (without using the current toggle method) in the future, the legacy update mechanism will still work without duplicating code!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other option may be to use the @change event as the input event for the v-model instead of @input, and just always emit this event for all values independently of the api that is being used.
This sounds like a good idea. The only concern I have is that @change is with a boolean, while @input is with array/boolean/number/string/object. I wonder if this will cause any issues in some cases.
I also thought about always emitting both events, no matter which API is used. But I wasn't sure if it follows the best practices. 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just coordinated with the team, and yes, we agreed on having the change event as the event for the v-model api @WinnyChang 😄. So we would just always emit the @change event
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it! I'll update accordingly.
|
Thanks @WinnyChang - we will re-review. We have more internal commitments this and next week so it may take longer than usual. |
AlexVelezLl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some final nitpicks! Everything else looks excellent! Thanks @WinnyChang!!
| if (typeof this.inputValue === 'boolean') { | ||
| return this.inputValue; | ||
| } | ||
| return Boolean(this.inputValue !== null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Nitpick) Since this.inputValue !== null is already a comparison, it returns a boolean value, and we no longer need to wrap it with the Boolean() type
| */ | ||
| this.$emit('change', !this.checked, event); | ||
| this.isChecked = !this.isChecked; | ||
| this.lastUserEvent = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At first read, it is not apparent what the lastUserEvent is for here, or why it is set to null again. We should add a couple of comments to explain this better.
However, to play safe, I would suggest setting the lastUserEvent to null inside a nextTick call, just to make sure that this event is being set to null just after the change event has already been emitted, and not before. Another alternative to actually make sure that this is set to null just after the change event has been emitted is to set it to null within the updateInputValue method. Since it would mean "okay, I already used it, we don't need this anymore", and this will ensure that this value is removed just after the change event has been emitted.
Description
v-modelsupport toKCheckbox.KCheckboxdocumentation page.KCheckbox's v-model. (Percy build for KCheckbox visual tests)Issue addressed
Fixes #1135
Changelog
Testing checklist
Reviewer guidance
@testing-library/vue.