import Col from "components/layout/Column.vue"; import Row from "components/layout/Row.vue"; import { CoercableComponent, Component as ComponentKey, GatherProps, GenericComponent, JSXFunction } from "features/feature"; import { Component, computed, ComputedRef, DefineComponent, defineComponent, isRef, PropType, ref, Ref, ShallowRef, shallowRef, unref, watchEffect } from "vue"; import { DoNotCache, ProcessedComputable } from "./computed"; export function coerceComponent( component: CoercableComponent, defaultWrapper = "span" ): DefineComponent { if (typeof component === "function") { return defineComponent({ render: component }); } if (typeof component === "string") { if (component.length > 0) { component = component.trim(); if (component.charAt(0) !== "<") { component = `<${defaultWrapper}>${component}`; } return defineComponent({ template: component }); } return defineComponent({ render: () => ({}) }); } return component; } export type VueFeature = { [ComponentKey]: GenericComponent; [GatherProps]: () => Record; }; export function render(object: VueFeature | CoercableComponent): JSX.Element | DefineComponent { if (isCoercableComponent(object)) { if (typeof object === "function") { return (object as JSXFunction)(); } return coerceComponent(object); } const Component = object[ComponentKey]; return ; } export function renderRow(...objects: (VueFeature | CoercableComponent)[]): JSX.Element { return {objects.map(render)}; } export function renderCol(...objects: (VueFeature | CoercableComponent)[]): JSX.Element { return {objects.map(render)}; } export function isCoercableComponent(component: unknown): component is CoercableComponent { if (typeof component === "string") { return true; } else if (typeof component === "object") { if (component == null) { return false; } return "render" in component || "component" in component; // eslint-disable-next-line @typescript-eslint/no-explicit-any } else if (typeof component === "function" && (component as any)[DoNotCache] === true) { return true; } return false; } export function setupHoldToClick( onClick?: Ref, onHold?: Ref ): { start: VoidFunction; stop: VoidFunction; handleHolding: VoidFunction; } { const interval = ref(null); function start() { if (!interval.value) { interval.value = setInterval(handleHolding, 250); } } function stop() { if (interval.value) { clearInterval(interval.value); interval.value = null; } } function handleHolding() { if (onHold && onHold.value) { onHold.value(); } else if (onClick && onClick.value) { onClick.value(); } } return { start, stop, handleHolding }; } export function computeComponent( component: Ref>, defaultWrapper = "div" ): ShallowRef { const comp = shallowRef(); watchEffect(() => { comp.value = coerceComponent(unwrapRef(component), defaultWrapper); }); return comp as ShallowRef; } export function computeOptionalComponent( component: Ref | undefined>, defaultWrapper = "div" ): ShallowRef { const comp = shallowRef(null); watchEffect(() => { const currComponent = unwrapRef(component); comp.value = currComponent == null ? null : coerceComponent(currComponent, defaultWrapper); }); return comp; } export function wrapRef(ref: Ref>): ComputedRef { return computed(() => unwrapRef(ref)); } export function unwrapRef(ref: Ref>): T { return unref(unref(ref)); } export function setRefValue(ref: Ref>, value: T) { if (isRef(ref.value)) { ref.value.value = value; } else { ref.value = value; } } export type PropTypes = | typeof Boolean | typeof String | typeof Number | typeof Function | typeof Object | typeof Array; // TODO Unfortunately, the typescript engine gives up on typing completely when you use this method, // Even though it has the same typing as when doing it manually export function processedPropType(...types: PropTypes[]): PropType> { if (!types.includes(Object)) { types.push(Object); } return types as PropType>; }