Michael added a comment.

  Using vuejs dependency injections API `provide`/`inject` in practice
  --------------------------------------------------------------------
  
  In the design system (and query builder) we need a component to toggle 
between two options. To make it easier to adjust the contents of the individual 
options, we decided to go with one wrapping component that get's the individual 
buttons in its default slot. Like so:
  
    <ToggleButtonGroup>
        <ToggleButton>
        <ToggleButton>
        <ToggleButton>
    </ToggleButtonGroup>
  
  However, we also would like the wrapping component, i.e. ToggleButtonGroup, 
to do the internal state management for us. That creates a challenge, because 
usually components do not have meaningful access to the content in their slots, 
let alone listen to their events or tell them which `isActive` prop they are 
supposed to have.
  
  That being said, it has to be possible, because that is exactly the 
functionality that vuetify 
<https://vuetifyjs.com/en/components/button-groups/#usage> provides:
  
    <v-btn-toggle v-model="toggle_exclusive">
        <v-btn>
            <v-icon>mdi-format-align-left</v-icon>
        </v-btn>
    
        <v-btn>
            <v-icon>mdi-format-align-center</v-icon>
        </v-btn>
    
        <v-btn>
            <v-icon>mdi-format-align-right</v-icon>
        </v-btn>
    
        <v-btn>
            <v-icon>mdi-format-align-justify</v-icon>
        </v-btn>
    </v-btn-toggle>
  
  The scavenger hunt to figure out what they are doing lead through vuetify's 
registrable 
<https://github.com/vuetifyjs/vuetify/blob/f33d0c1356c5eb3a685c86f754bdf85c5dd706d0/packages/vuetify/src/mixins/registrable/index.ts>
 mixin, used in their groupable 
<https://github.com/vuetifyjs/vuetify/blob/f33d0c1356c5eb3a685c86f754bdf85c5dd706d0/packages/vuetify/src/mixins/groupable/index.ts>
 mixin used in their VBtn 
<https://github.com/vuetifyjs/vuetify/blob/f33d0c1356c5eb3a685c86f754bdf85c5dd706d0/packages/vuetify/src/components/VBtn/VBtn.ts>
 component with the methods defined in the ItemGroup 
<https://github.com/vuetifyjs/vuetify/blob/f33d0c1356c5eb3a685c86f754bdf85c5dd706d0/packages/vuetify/src/components/VItemGroup/VItemGroup.ts#L126-L170>
 component. The solution is that they are using provide/inject to register the 
`<v-btn>` buttons in the slot inside the `<v-btn-toggle>`.
  
  Our own solution is somewhat less sophisticated, but still has to make use of 
the provide/inject API that vuejs provides for dependency injection (vue2 docs 
<https://vuejs.org/v2/api/#provide-inject>, vue3 docs 
<https://v3.vuejs.org/guide/component-provide-inject.html>).
  
  In the Proof of Concept <https://github.com/wmde/wikit/pull/358> for the 
ToggleButtonGroup, we are providing a listener for each button and a method to 
get the currently selected value for the group:
  
    export interface ToggleButtonGroupInjection {
        groupValue: ( () => string ) | null;
        toggleListener: ( ( value: string ) => void ) | null;
    }
    export default Vue.extend( {
        name: 'ToggleButtonGroup',
        provide(): ToggleButtonGroupInjection {
                return {
                        groupValue: (): string => this.value,
                        toggleListener: ( event: string ): void => {
                                this.$emit( 'input', event );
                        },
                };
        },
        // props etc.
    } );
  
  That is then injected in the ToggleButton:
  
    import { ToggleButtonGroupInjection } from 
'@/components/ToggleButtonGroup.vue';
    export default ( Vue as VueConstructor<Vue & ToggleButtonGroupInjection> 
).extend( {
        name: 'ToggleButton',
        methods: {
                onClick(): void {
                        if ( this.toggleListener !== null ) {
                                this.toggleListener( this.value );
                                return;
                        }
                        /**
                         * only emitted when not use as part of a 
ToggleButtonGroup
                         */
                        this.$emit( 'click' );
                },
        },
        inject: {
                groupValue: { default: null },
                toggleListener: { default: null },
        } as Record<keyof ToggleButtonGroupInjection, object>,
        computed: {
                buttonIsActive(): boolean {
                        if ( this.groupValue !== null ) {
                                return this.groupValue() === this.value;
                        }
                        return this.isActive;
                },
        },
        // props etc.
    } );
  
  Note the line
  
    export default ( Vue as VueConstructor<Vue & ToggleButtonGroupInjection> 
).extend( {
  
  That is needed so that TypeScript knows about these two fields on `this`.
  
  That way it is possible to directly transfer knowledge between a component 
and the children in its slots.

TASK DETAIL
  https://phabricator.wikimedia.org/T272460

EMAIL PREFERENCES
  https://phabricator.wikimedia.org/settings/panel/emailpreferences/

To: Michael
Cc: Michael, Charlie_WMDE, Aklapper, Sarai-WMDE, Lydia_Pintscher, Akuckartz, 
Nandana, Lahi, Gq86, GoranSMilovanovic, QZanden, LawExplorer, _jensen, 
rosalieper, Scott_WUaS, Wikidata-bugs, aude, Mbch331
_______________________________________________
Wikidata-bugs mailing list
Wikidata-bugs@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/wikidata-bugs

Reply via email to