Akryum / floating-vue

💬 Easy tooltips, popovers, dropdown, menus... for Vue
https://floating-vue.starpad.dev/
MIT License
3.33k stars 338 forks source link

Add Typescript types definition #188

Open karolkarolka opened 5 years ago

karolkarolka commented 5 years ago

Will "v-tooltip" support Typescript definitions?

builder7777 commented 5 years ago

I'm in the same boat, for now i'm using it like this:

const VTooltip = require('v-tooltip');
Vue.use(VTooltip);

TSLint doesn't complain about that

derweise commented 4 years ago

I'm in the same boat, for now i'm using it like this:

const VTooltip = require('v-tooltip');
Vue.use(VTooltip);

TSLint doesn't complain about that

Not working for me

mnapoli commented 3 years ago

To make it work with TypeScript I had to use this instead of what's described in the README:

import vToolTip from 'v-tooltip';

Vue.use(vToolTip);
manuelodelain commented 3 years ago

Hey,

I guess there is a regression because latest published version 3.0.0-alpha.21 doesn't include TS definitions. But it seems ok if I revert to the latest stable version 2.1.2

g0shed commented 3 years ago

add

declare module 'v-tooltip'

to your shims.d..ts file

RobinRadic commented 3 years ago

This certainly isn't correct by any means but it provides my editor with all the code completion. Specifically the component props/attributes used in the SFC's <template> block. Just sharing for some who might be interested. There's a chance you have to modify it a bit. Note that my webpack config does not use the same tsconfig.json i have here, this one is just for my IDE.

v-tooltip@4.0.0-alpha.1 and vue@3.2.10

lib/types/v-tooltip.d.ts

declare module 'v-tooltip' {
    import { StrictModifiers } from '@popperjs/core/lib/types';
    import { App, defineComponent, Directive } from 'vue';
    import { Placement } from '@popperjs/core';

    export interface PluginOptions {
        // Disable popper components
        disabled?: boolean //false,
        // Default position offset [skidding, distance] (px)
        offset?: [ number, number ] //[0, 5],
        // Default container where the tooltip will be appended
        container?: string //'body',
        // Element used to compute position and size boundaries
        boundary?: Element | HTMLElement //undefined,
        // Skip delay & CSS transitions when another popper is shown, so that the popper appear to instanly move to the new position.
        instantMove?: boolean//false,
        // Auto destroy tooltip DOM nodes (ms)
        disposeTimeout?: number //5000,
        // Triggers on the popper itself
        popperTriggers?: any[] //[],
        // Positioning strategy
        strategy?: string //'absolute',
        // Popperjs modifiers
        modifiers?: any[] //[],
        // Other options passed to Popperjs constructor
        popperOptions?: any // {},
        // Themes
        themes?: {
            tooltip?: {
                // Default tooltip placement relative to target element
                placement?: Placement //'top',
                // Default events that trigger the tooltip
                triggers?: Array<'hover' | 'focus' | 'touch' | 'click'> //'hover'|'focus'|'touch'
                // Close tooltip on click on tooltip target
                hideTriggers?: (triggers) => string[] //events => [...events, 'click'],
                // Delay (ms)
                delay?: {
                    show?: number // 200,
                    hide?: number // 0,
                },
                // Update popper on content resize
                handleResize?: boolean //false,
                // Enable HTML content in directive
                html?: boolean //false,
                // Displayed when tooltip content is loading
                loadingContent?: string //'...',
            },
            dropdown?: {
                // Default dropdown placement relative to target element
                placement?: Placement //'bottom',
                // Default events that trigger the dropdown
                triggers?: Array<'hover' | 'focus' | 'touch' | 'click'> //'hover'|'focus'|'touch'
                // Delay (ms)
                delay?: number //0,
                // Update popper on content resize
                handleResize?: boolean //true,
                // Hide on clock outside
                autoHide?: boolean // true,
            },
            menu?: {
                $extend?: string // 'dropdown',
                triggers?: Array<'hover' | 'focus' | 'touch' | 'click'> //'hover'|'focus'|'touch'
                popperTriggers?: Array<'hover' | 'focus' | 'touch' | 'click'> //'hover'|'focus'|'touch'
                delay?: {
                    show?: number// 0,
                    hide?: number//400,
                },
            },
        },
    }

    export const options: PluginOptions;
    export const VTooltip: Directive;
    export const VClosePopper: Directive;

    export function destroyTooltip(el: Element | HTMLElement)

    export function createTooltip(el: Element | HTMLElement, options?: PluginOptions['themes']['tooltip'], modifiers?: StrictModifiers)

    export const Dropdown         = defineComponent({
        ...PopperWrapper,
        name: 'VDropdown',
        vPopperTheme: 'dropdown',
    });
    export const Menu             = defineComponent({
        ...PopperWrapper,
        name: 'VMenu',
        vPopperTheme: 'menu',
    });
    export const Popper           = defineComponent({
        props: {
            theme: {
                type    : String,
                required: true,
            },

            targetNodes: {
                type    : Function,
                required: true,
            },

            referenceNode: {
                type    : Function,
                required: true,
            },

            popperNode: {
                type    : Function,
                required: true,
            },

            arrowNode: {
                type   : Function,
                default: null,
            },

            shown: {
                type   : Boolean,
                default: false,
            },

            showGroup: {
                type   : String,
                default: null,
            },

            // eslint-disable-next-line vue/require-prop-types
            ariaId: {
                default: null,
            },

            disabled: {
                type: Boolean,
                default(props) {
                    return getDefaultConfig(props.theme, 'disabled');
                },
            },

            placement: {
                type: String,
                default(props) {
                    return getDefaultConfig(props.theme, 'placement');
                },
                validator: value => placements.includes(value),
            },

            delay: {
                type: [ String, Number, Object ],
                default(props) {
                    return getDefaultConfig(props.theme, 'delay');
                },
            },

            offset: {
                type: [ Array, Function ],
                default(props) {
                    return getDefaultConfig(props.theme, 'offset');
                },
            },

            triggers: {
                type: Array,
                default(props) {
                    return getDefaultConfig(props.theme, 'triggers');
                },
            },

            showTriggers: {
                type: [ Array, Function ],
                default(props) {
                    return getDefaultConfig(props.theme, 'showTriggers');
                },
            },

            hideTriggers: {
                type: [ Array, Function ],
                default(props) {
                    return getDefaultConfig(props.theme, 'hideTriggers');
                },
            },

            popperTriggers: {
                type: Array,
                default(props) {
                    return getDefaultConfig(props.theme, 'popperTriggers');
                },
            },

            popperShowTriggers: {
                type: [ Array, Function ],
                default(props) {
                    return getDefaultConfig(props.theme, 'popperShowTriggers');
                },
            },

            popperHideTriggers: {
                type: [ Array, Function ],
                default(props) {
                    return getDefaultConfig(props.theme, 'popperHideTriggers');
                },
            },

            container: {
                type: [ String, Object, Element, Boolean ],
                default(props) {
                    return getDefaultConfig(props.theme, 'container');
                },
            },

            boundary: {
                type: [ String, Element ],
                default(props) {
                    return getDefaultConfig(props.theme, 'boundary');
                },
            },

            strategy: {
                type     : String,
                validator: value => [ 'absolute', 'fixed' ].includes(value),
                default(props) {
                    return getDefaultConfig(props.theme, 'strategy');
                },
            },

            modifiers: {
                type: Array,
                default(props) {
                    return getDefaultConfig(props.theme, 'modifiers');
                },
            },

            popperOptions: {
                type: Object,
                default(props) {
                    return getDefaultConfig(props.theme, 'popperOptions');
                },
            },

            autoHide: {
                type: Boolean,
                default(props) {
                    return getDefaultConfig(props.theme, 'autoHide');
                },
            },

            handleResize: {
                type: Boolean,
                default(props) {
                    return getDefaultConfig(props.theme, 'handleResize');
                },
            },

            instantMove: {
                type: Boolean,
                default(props) {
                    return getDefaultConfig(props.theme, 'instantMove');
                },
            },

            eagerMount: {
                type: Boolean,
                default(props) {
                    return getDefaultConfig(props.theme, 'eagerMount');
                },
            },
        },

        emits: [
            'show',
            'hide',
            'update:shown',
            'apply-show',
            'apply-hide',
            'close-group',
            'close-directive',
            'auto-hide',
            'resize',
            'dispose',
        ],
    });
    export const PopperContent    = defineComponent({
        name: 'VPopperContent',

        components: {
            ResizeObserver,
        },

        mixins: [
            ThemeClass,
        ],

        props: {
            popperId: String,
            theme: String,
            shown: Boolean,
            mounted: Boolean,
            skipTransition: Boolean,
            autoHide: Boolean,
            handleResize: Boolean,
            classes: Object,
        },

        emits: [
            'hide',
            'resize',
        ],
    })
    export const PopperMethods    = defineComponent({
        methods: {
            show (...args) {
                return this.$refs.popper.show(...args)
            },
            hide (...args) {
                return this.$refs.popper.hide(...args)
            },
            dispose (...args) {
                return this.$refs.popper.dispose(...args)
            },
            onResize (...args) {
                return this.$refs.popper.onResize(...args)
            },
        },
    })
    export const PopperWrapper    = defineComponent({
        name: 'VPopperWrapper',

        components: {
            Popper: Popper(),
            PopperContent,
        },

        mixins: [
            PopperMethods,
            ThemeClass,
        ],

        inheritAttrs: false,

        props: {
            theme: {
                type: String,
                default: null,
            },
        },

        computed: {
            finalTheme () {
                return this.theme ?? this.$options.vPopperTheme
            },
        },

        methods: {
            getTargetNodes () {
                const children = [...this.$refs.reference.children]
                return children.slice(0, children.length - 1).filter(Boolean)
            },
        },
    });
    export const ThemeClass       = defineComponent({
        computed: {
            themeClass () {
                return getThemeClasses(this.theme)
            },
        },
    });
    export const Tooltip          = defineComponent({
        ...PopperWrapper,
        name: 'VTooltip',
        vPopperTheme: 'tooltip',
    })
    export const TooltipDirective = defineComponent({
        name: 'VTooltipDirective',

        components: {
            Popper: Popper(),
            PopperContent,
        },

        mixins: [
            PopperMethods,
        ],

        inheritAttrs: false,

        props: {
            theme: {
                type: String,
                default: 'tooltip',
            },

            html: {
                type: Boolean,
                default (props) {
                    return getDefaultConfig(props.theme, 'html')
                },
            },

            content: {
                type: [String, Number, Function],
                default: null,
            },

            loadingContent: {
                type: String,
                default (props) {
                    return getDefaultConfig(props.theme, 'loadingContent')
                },
            },
        },

        data () {
            return {
                asyncContent: null,
            }
        },

        computed: {
            isContentAsync () {
                return typeof this.content === 'function'
            },

            loading () {
                return this.isContentAsync && this.asyncContent == null
            },

            finalContent () {
                if (this.isContentAsync) {
                    return this.loading ? this.loadingContent : this.asyncContent
                }
                return this.content
            },
        },

        watch: {
            content: {
                handler () {
                    this.fetchContent(true)
                },
                immediate: true,
            },

            finalContent (value) {
                this.$nextTick(() => {
                    this.$refs.popper.onResize()
                })
            },
        },

        created () {
            this.$_fetchId = 0
        },

        methods: {
            fetchContent (force) {
                if (typeof this.content === 'function' && this.$_isShown &&
                    (force || (!this.$_loading && this.asyncContent == null))) {
                    this.asyncContent = null
                    this.$_loading = true
                    const fetchId = ++this.$_fetchId
                    const result = this.content(this)
                    if (result.then) {
                        result.then(res => this.onResult(fetchId, res))
                    } else {
                        this.onResult(fetchId, result)
                    }
                }
            },

            onResult (fetchId, result) {
                if (fetchId !== this.$_fetchId) return
                this.$_loading = false
                this.asyncContent = result
            },

            onShow () {
                this.$_isShown = true
                this.fetchContent()
            },

            onHide () {
                this.$_isShown = false
            },
        },
    })

    /* Vue plugin */

    export function install(app:App, options:PluginOptions = {}) {
        if ( install.installed ) return;
        install.installed = true;
        // Directive
        app.directive('tooltip', options);
        app.directive('close-popper', VClosePopper);
        // Components
        // eslint-disable-next-line vue/component-definition-name-casing
        app.component('v-tooltip', Tooltip);
        app.component('VTooltip', Tooltip);
        // eslint-disable-next-line vue/component-definition-name-casing
        app.component('v-dropdown', Dropdown);
        app.component('VDropdown', Dropdown);
        // eslint-disable-next-line vue/component-definition-name-casing
        app.component('v-menu', Menu);
        app.component('VMenu', Menu);
    }

    const plugin: {
        // eslint-disable-next-line no-undef
        version: string,
        install: typeof install,
        options: PluginOptions,
    };

    export default plugin;

}

tsconfig.json

{
    "compileOnSave": false,
    "compilerOptions": {
        "target": "esnext",
        "module": "esnext",
        "jsx": "preserve",
        "jsxFactory": "h",
        "moduleResolution": "node",
        "experimentalDecorators": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "noImplicitAny": false,
        "noImplicitReturns": false,
        "noImplicitThis": false,
        "noImplicitUseStrict": false,
        "sourceMap": true,
        "baseUrl": ".",
        "typeRoots": [
            "node_modules/@types",
            "lib/types"
        ],
        "lib": [
            "esnext",
            "dom",
            "dom.iterable",
            "scripthost"
        ],
        "paths": {
            "@/*": ["./lib/*"],
            "#/*": ["./lib/components/*"],
            "#": ["./lib/components/index.ts"],
            "@f/*": ["./lib/foundation/*"],
            "@f": ["./lib/foundation/index.ts"],
            "@i/*": ["./lib/interfaces/*"],
            "@i": ["./lib/interfaces/index.ts"],
            "@u/*": ["./lib/utils/*"],
            "@layouts/*": ["./lib/layouts/*"],
            "@pages/*": ["./lib/pages/*"],
            "@plugins/*": ["./lib/plugins/*"],
            "@providers/*": ["./lib/providers/*"],
            "@styles/*": ["./lib/styles/*"],
            "@store/*": ["./store/index.ts"]
        }
    },
    "include": [
        "lib",
        "lib/**/*.ts",
        "lib/**/*.tsx",
        "lib/**/*.vue",
        "tests/**/*.ts",
        "tests/**/*.tsx"
    ],
    "exclude": [
        "node_modules"
    ]
}
Anoesj commented 1 year ago

Would be very nice indeed to add type definitions. Lots of projects need to migrate from v-tooltip to floating-vue, but it's a pain when you don't get any feedback from TS. Could the types above be updated and included in the repo?

JackEdwardLyons commented 9 months ago

I'd love to see types as well :) hopefully it's not too far off?