Vue 3 creating completely custom checkbox and a radio group wrapper

I have been trying to create a completely custom checkbox/radio group component that does not depend on the native/vue input elements. One reason to have complete freedom of styling it, second for learning purposes. (Vue 3)

It should work similary to what the vue checkbox provides, so that:

a) I can bind the v-model to an array of selected values: ["1", "2", "3"]

<checkbox value="1" v-model="selected"></checkbox>
<checkbox value="2" v-model="selected"></checkbox>
<checkbox value="3" v-model="selected"></checkbox>

b) I can bind it to a v-model boolean:

<checkbox v-model="checked"></checkbox>

My checkbox implementation while not the prettiest seems to work ok (barring any edge cases).

//<Checkbox> component
import {defineComponent} from 'vue';

export default defineComponent({
    props: {  
        value:          {type: null, default: null},
        modelValue:     {type: [Array,Boolean], default: () => []}, 
    },
    emits: ['update:modelValue'],  
    data() { 
        let _checked;
        if (typeof this.modelValue == 'boolean') {
            _checked = this.modelValue == true;
        } else { 
            _checked = this.modelValue.indexOf(this.value) != -1;
        } 
        return {
            checked: _checked
        }
    },
    computed: { 
        class() {
            return {'checked': this.checked}
        },  
    },
    watch: { 
        modelValue: {  
          deep: true,
          handler(newv, oldv) {    
              if (typeof newv == 'boolean') {
                  this.checked = newv == true;
              } else { 
                  if (newv) {  
                      this.checked = newv.indexOf(this.value) != -1;
                  }
              }
            }
        },
        value(newv, oldv) {
           if (newv == oldv) return;
           let arr = [...this.modelValue];
           let idx = arr.indexOf(oldv);
           if (idx != -1) {
                arr[idx] = newv;
           }
           this.$emit("update:modelValue", arr);
         }, 
         checked(newv, oldv) {  
             if (newv == oldv)  return;
             if (typeof this.modelValue == 'boolean') {
                this.$emit("update:modelValue", newv);
             } else {
                let arr = [...this.modelValue];
                if (newv) {
                    arr.push(this.value);
                } else {
                    arr.splice(arr.indexOf(this.value), 1);
                }
                this.$emit("update:modelValue", arr);
            } 
         }
    }
})
</script>

<template>
    <div @click.stop="checked = !checked" :class="this.class">  
        <slot></slot>
    </div> 
</template>

Now, with the checkbox working I wanted to have a way to provide a radio like functionality so only one component can be checked at a time. My idea was to wrap it in a parent component that will orchestrate it, like so:

<group v-model="selected">
    <checkbox value="1"></checkbox>
    <checkbox value="2"></checkbox>
    <checkbox value="3"></checkbox>
</group>

First thing, I need to have a way for the individual checkboxes to register with the group component. I achieved it with using provide and inject.

//<Group> component
export default defineComponent({
    props: {
        modelValue: { type: [Boolean, Array], default: () => []}
    },
    provide() {
        // provide this instance to the child checkbox components
        return {
            'group': this
        }
    },
    data() {
        return {
            checkboxes: []
        }
    },
    mounted() { 
           // got this.checkboxes filled with <checkbox> components at this point
    }, 
});

In the child checkboxes components, I have added code to receive the injected group component and register the component with the parent.

//rest of <checkbox> component code...
inject: ['group'],
mounted() {
    this.group.checkboxes.push(this);
}
//...rest of <checkbox> component code... 

Now at this stage I am pretty much stuck. My <Group> component has all the available child <Checkbox> components in it’s mounted lifecycle. But I have no idea how can I register for the <checkbox> events or how to pass a modelValue. There is no API for listening to events, and I can’t pass the Groups component modelValue to a checkbox.

//<Group component 
props: {
    modelValue: {type: [Boolean, Array], default: () => []}
},
mounted() {
  // can't set the modelValue like this on each checkbox, props are readonly!
  //for(let checkbox of this.checkboxes) {
  //   checkbox.modelValue = this.modelValue;   
  //}
  // no `$on` event listener available in Vue 3!
  //for(let checkbox of this.checkboxes) {
  //      checkbox.$on("update:modelValue", () => {
  //               //do something with it...
  //      });
  //}
}