Skip to content

Instantly share code, notes, and snippets.

@satouriko
Created December 1, 2023 01:32

Revisions

  1. satouriko created this gist Dec 1, 2023.
    46 changes: 46 additions & 0 deletions modelToAttrs.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,46 @@
    /* eslint-disable @typescript-eslint/no-explicit-any */
    import { Ref, UnwrapNestedRefs, reactive } from 'vue';

    import { ToComputedRefs, toComputedRefs } from './toComputedRefs';

    type Model<T extends object, K extends object> = {
    state: Ref<T>;
    getters: {
    [key in keyof K]: K[key] | Ref<K[key]>;
    };
    };

    type UseModelFunction<T extends object, K extends object> = (...args: any) => Model<T, K>;

    type ModelToComputedRefs<T extends UseModelFunction<object, object>> = ToComputedRefs<ReturnType<T>['state']> &
    ReturnType<T>['getters'] & { model: Omit<ReturnType<T>, 'state' | 'getters'> };

    export type ModelToProps<T extends UseModelFunction<object, object>> = UnwrapNestedRefs<ModelToComputedRefs<T>>;
    export type ModelToModelKeys<T extends UseModelFunction<object, object>> = keyof ReturnType<T>['state']['value'] &
    string;
    export type ModelToEmits<T extends UseModelFunction<object, object>, ModelKeys extends ModelToModelKeys<T>> = {
    <K extends ModelKeys>(e: `update:${K}`, value: ReturnType<T>['state']['value'][K]): void;
    };

    export function modelToAttrs<T extends object, K extends object>(
    model: Model<T, K>,
    vModel: Array<keyof T> = [],
    ): ModelToProps<UseModelFunction<T, K>> {
    const { state, getters, ...rest } = model;
    const states = toComputedRefs(state);
    const listeners = vModel.reduce(
    (obj, key) => ({
    ...obj,
    [`onUpdate:${String(key)}`]: (val: (typeof states)[typeof key]) => {
    states[key].value = val;
    },
    }),
    {},
    );
    return reactive({
    ...states,
    ...getters,
    ...listeners,
    model: rest,
    });
    }
    39 changes: 39 additions & 0 deletions toComputedRefs.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,39 @@
    /* eslint-disable no-param-reassign */
    /* eslint-disable guard-for-in */
    /* eslint-disable no-restricted-syntax */
    /* eslint-disable @typescript-eslint/no-explicit-any */
    import { Ref, WritableComputedRef, computed, isRef } from 'vue';

    function propertyToRef<T extends object>(object: T, key: string | symbol) {
    return computed({
    get() {
    return isRef(object) ? object.value[key] : object[key];
    },
    set(val) {
    if (isRef(object)) {
    object.value[key] = val;
    } else {
    object[key] = val;
    }
    },
    });
    }

    type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;

    export type ToComputedRef<T> = IfAny<T, WritableComputedRef<T>, [T] extends [Ref] ? T : WritableComputedRef<T>>;
    export type ToComputedRefs<T = any> = T extends Ref
    ? {
    [K in keyof T['value']]: ToComputedRef<T['value'][K]>;
    }
    : {
    [K in keyof T]: ToComputedRef<T[K]>;
    };
    export function toComputedRefs<T extends object>(object: T): ToComputedRefs<T> {
    const obj: any = isRef(object) ? object.value : object;
    const ret: any = Array.isArray(obj) ? new Array(obj.length) : {};
    for (const key in obj) {
    ret[key] = propertyToRef(object, key);
    }
    return ret;
    }