Skip to content

Commit

Permalink
feat(Tooltip): add new directive (#19053)
Browse files Browse the repository at this point in the history
Co-authored-by: Kael <[email protected]>
Co-authored-by: userquin <[email protected]>
  • Loading branch information
3 people authored Apr 12, 2024
1 parent 380e8e6 commit 2e1e743
Show file tree
Hide file tree
Showing 33 changed files with 429 additions and 165 deletions.
4 changes: 1 addition & 3 deletions packages/api-generator/src/locale/en/v-click-outside.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
{
"argument": {
"value": "By default takes a function that is invoked when user clicks outside of the element the directive is attached to. It can also be an object, which allows you to provide `closeConditional` and `include` callbacks."
}
"value": "Takes either a function that is invoked when user clicks outside of the element the directive is attached to, or an object containing `handler`, `closeConditional` and `include` callbacks."
}
6 changes: 2 additions & 4 deletions packages/api-generator/src/locale/en/v-intersect.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{
"argument": {
"value": "By default takes a handler function that is invoked when the element that the directive is attached to enters or leaves the visible browser area. It can also take an object, which allows you to pass along [IntersectionObserver options](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver)."
},
"value": "A handler function that is invoked when the element that the directive is attached to enters or leaves the visible browser area, or an object of [IntersectionObserver options](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver).",
"modifiers": {
"once": "The provided handler function is only invoked once, the first time the element is visible.",
"once": "The handler function is only invoked once, the first time the element is visible.",
"quiet": "Will not invoke the handler function if the element is visible when the IntersectionObserver is created."
}
}
4 changes: 1 addition & 3 deletions packages/api-generator/src/locale/en/v-mutate.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
{
"argument": {
"value": "By default takes a handler function that is invoked when the element that the directive is attached to is mutated. It can also take an object, which allows you to pass along [MutationObserver options](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe)."
},
"value": "A handler function that is invoked when the element that the directive is attached to is mutated, or an object of [MutationObserver options](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe).",
"modifiers": {
"attr": "Sets the value of [attributes](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit/attributes) to true.",
"char": "Sets the value of [characterData](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit/characterData) to true.",
Expand Down
6 changes: 2 additions & 4 deletions packages/api-generator/src/locale/en/v-resize.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{
"argument": {
"value": "The provided handler function will be invoked each time the browser window is resized."
},
"value": "A function that will be invoked each time the browser window is resized.",
"modifiers": {
"active": "By default the resize event listener is added to window with the `passive` option. This modifier sets `passive` to **false**.",
"quiet": "By default the provided handler function is invoked once when the directive is attached to the element. This modifier disabled that behavior."
"quiet": "By default the provided handler function is invoked once when the directive is attached to the element. This modifier disables that behavior."
}
}
4 changes: 1 addition & 3 deletions packages/api-generator/src/locale/en/v-ripple.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
{
"argument": {
"value": "An object containing options for the ripple effect. `class` applies a custom class to the ripple, and can be used for changing color. `center` forces the ripple to originate from the center of the target."
},
"value": "An object containing options for the ripple effect. `class` applies a custom class to the ripple, and can be used for changing color. `center` forces the ripple to originate from the center of the target instead of the cursor position.",
"modifiers": {
"center": "Makes it so that the ripple originates from the center of the element, instead where the user clicked on it.",
"circle": "Changes the ripple behavior to better match circular elements.",
Expand Down
6 changes: 2 additions & 4 deletions packages/api-generator/src/locale/en/v-scroll.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
{
"argument": {
"arg": "The argument can be used to specify a query selector to attach the scroll event listener to. If no argument is provided then it is attached to the window object.",
"value": "By default takes a handler function that is invoked whenever the target of the directive is scrolled. It can also take an object, which allows you to pass along event listener options as described [here](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)."
},
"argument": "Specify a query selector to attach the scroll event listener to. If no argument is provided then it is attached to the window object.",
"value": "A handler function that is invoked whenever the target element is scrolled, or an object of [event listener options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener).",
"modifiers": {
"self": "By default the scroll event listener is attached to the argument provided to the directive, interpreted as a query selector. If no argument is provided then it is attached to the window object. If this modifier is used then it is instead attached to the element the directive is used on."
}
Expand Down
4 changes: 4 additions & 0 deletions packages/api-generator/src/locale/en/v-tooltip.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"argument": "Applies the VTooltip location prop.",
"value": "**string**: Sets the tooltip content. \n**boolean**: Controls visibility, tooltip content will be the innerText of the bound element. \n**object**: Use any [VTooltip props](/api/v-tooltip), content can be set with `text`. Keys are camelCase."
}
4 changes: 1 addition & 3 deletions packages/api-generator/src/locale/en/v-touch.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
{
"argument": {
"value": "The value is always an object. The `start`, `end`, `move`, `left`, `right`, `up` and `down` functions can be used to invoke a function when the corresponding touch action occurs. If the `parent` option attaches the touch listeners to the parent element instead of the element the directive is used on. The `options` object is described [here](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)."
}
"value": "The value is always an object. The `start`, `end`, `move`, `left`, `right`, `up` and `down` functions can be used to invoke a function when the corresponding touch action occurs. If the `parent` option attaches the touch listeners to the parent element instead of the element the directive is used on. The `options` object is described [here](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)."
}
4 changes: 3 additions & 1 deletion packages/api-generator/src/shims.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ts } from '@ts-morph/common'
import '@ts-morph/common'

declare module 'ts-morph' {
export interface Type {
Expand All @@ -18,3 +18,5 @@ declare module 'ts-morph' {
}
}
}

export {}
51 changes: 33 additions & 18 deletions packages/api-generator/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ export async function generateDirectiveDataFromTypes (): Promise<DirectiveData[]
fileName: `v-${kebabName}`,
displayName: `v-${kebabName}`,
pathName: `v-${kebabName}-directive`,
argument: { value: await prettifyType(name, (data as ObjectDefinition).properties.value) },
value: await prettifyType(name, (data as ObjectDefinition).properties.value),
argument: (data as ObjectDefinition).properties.arg,
modifiers: ((data as ObjectDefinition).properties.modifiers as ObjectDefinition).properties,
}
})
Expand All @@ -91,10 +92,17 @@ export async function generateDirectiveDataFromTypes (): Promise<DirectiveData[]
export async function generateComponentDataFromTypes (component: string): Promise<ComponentData> {
const sourceFile = project.addSourceFileAtPath(`./templates/tmp/${component}.d.ts`)

const props = await inspect(project, sourceFile.getTypeAlias('ComponentProps'))
const events = await inspect(project, sourceFile.getTypeAlias('ComponentEvents'))
const slots = await inspect(project, sourceFile.getTypeAlias('ComponentSlots'))
const exposed = await inspect(project, sourceFile.getTypeAlias('ComponentExposed'))
const [
props,
events,
slots,
exposed,
] = await Promise.all([
inspect(project, sourceFile.getTypeAlias('ComponentProps')),
inspect(project, sourceFile.getTypeAlias('ComponentEvents')),
inspect(project, sourceFile.getTypeAlias('ComponentSlots')),
inspect(project, sourceFile.getTypeAlias('ComponentExposed')),
])

const sections = [props, events, slots, exposed]

Expand All @@ -114,6 +122,10 @@ export async function generateComponentDataFromTypes (component: string): Promis
events: events.properties,
slots: slots.properties,
exposed: exposed.properties,
displayName: component,
fileName: component,
pathName: kebabCase(component),
sass: {},
}
}

Expand Down Expand Up @@ -217,6 +229,7 @@ export type ComponentData = BaseData & {
slots: Record<string, Definition>
events: Record<string, Definition>
exposed: Record<string, Definition>
value?: never
argument?: never
modifiers?: never
}
Expand All @@ -226,7 +239,8 @@ export type DirectiveData = BaseData & {
slots?: never
events?: never
exposed?: never
argument: { value: Definition }
value: Definition
argument: Definition
modifiers: Record<string, Definition>
}
export type ComposableData = BaseData & {
Expand All @@ -235,6 +249,7 @@ export type ComposableData = BaseData & {
slots?: never
events?: never
exposed: Record<string, Definition>
value?: never
argument?: never
modifiers?: never
}
Expand Down Expand Up @@ -268,21 +283,21 @@ function getSource (declaration?: Node<ts.Node>) {
return filePath && startLine ? `${filePath}#L${startLine}-L${endLine}` : undefined
}

function listFlags (flags: object, value?: number) {
if (!value) return []
// function listFlags (flags: object, value?: number) {
// if (!value) return []

const entries = Object.entries(flags).filter(([_, flag]) => typeof flag === 'number')
// const entries = Object.entries(flags).filter(([_, flag]) => typeof flag === 'number')

return entries.reduce<string[]>((arr, [name, flag]) => {
if (value & flag) {
arr.push(name)
}
return arr
}, [])
}
// return entries.reduce<string[]>((arr, [name, flag]) => {
// if (value & flag) {
// arr.push(name)
// }
// return arr
// }, [])
// }

function getCleanText (text: string) {
return text.replaceAll(/import\(.*?\)\./g, '')
return text.replace(/import\(.*?\)\./g, '')
}

function count (arr: string[], needle: string) {
Expand Down Expand Up @@ -607,7 +622,7 @@ function getRecursiveTypes (recursiveTypes: string[], type: Type<ts.Type>) {
function findPotentialRecursiveTypes (type?: Type<ts.Type>): string[] {
if (type == null) return []

const recursiveTypes = []
const recursiveTypes: string[] = []

if (type.isUnion()) {
recursiveTypes.push(...getUnionTypes(type).map(t => t.getText()))
Expand Down
23 changes: 13 additions & 10 deletions packages/api-generator/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { execSync } from 'child_process'
import stringifyObject from 'stringify-object'
import prettier from 'prettier'
import * as typescriptParser from 'prettier/plugins/typescript'
import type { Definition } from './types'
import type { Definition, DirectiveData } from './types'

function parseFunctionParams (func: string) {
const [, regular] = /function\s\((.*)\)\s\{.*/i.exec(func) || []
Expand Down Expand Up @@ -134,10 +134,12 @@ async function getSources (name: string, locale: string, sources: string[]) {
const sourcesMap = [name, ...sources, 'generic']

return {
find: (section: string, key: string, ogSource = name) => {
find (section: string, key?: string, ogSource = name) {
for (let i = 0; i < arr.length; i++) {
const source = arr[i] as any
const found: string | undefined = source?.[section]?.[key]
const found: string | undefined = ['argument', 'value'].includes(section)
? source?.[section]
: source?.[section]?.[key!]
if (found) {
return { text: found, source: sourcesMap[i] }
}
Expand Down Expand Up @@ -167,25 +169,26 @@ export async function addDescriptions (name: string, componentData: ComponentDat

export async function addDirectiveDescriptions (
name: string,
componentData: { argument: { value: Definition }, modifiers: Record<string, Definition> },
componentData: DirectiveData,
locales: string[],
sources: string[] = [],
) {
for (const locale of locales) {
const descriptions = await getSources(name, locale, sources)

if (componentData.argument) {
for (const [name, arg] of Object.entries(componentData.argument)) {
arg.description = arg.description ?? {}
if (componentData.value) {
componentData.value.description = componentData.value.description ?? {}
componentData.value.description[locale] = descriptions.find('value')?.text
}

arg.description[locale] = descriptions.find('argument', name)?.text
}
if (componentData.argument) {
componentData.argument.description = componentData.argument.description ?? {}
componentData.argument.description[locale] = descriptions.find('argument')?.text
}

if (componentData.modifiers) {
for (const [name, modifier] of Object.entries(componentData.modifiers)) {
modifier.description = modifier.description ?? {}

modifier.description[locale] = descriptions.find('modifiers', name)?.text
}
}
Expand Down
8 changes: 4 additions & 4 deletions packages/api-generator/src/web-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const createWebTypesApi = (componentData: ComponentData[], directiveData:
const createAttributeValue = (argument: any) => {
return {
kind: 'expression',
type: argument.type?.trim(),
type: argument.text,
}
}

Expand All @@ -106,14 +106,14 @@ export const createWebTypesApi = (componentData: ComponentData[], directiveData:
'doc-url': getDocUrl(directive.pathName),
default: '',
required: false,
value: createAttributeValue(directive.argument),
value: createAttributeValue(directive.value),
source: {
module: './src/directives/index.ts',
symbol: capitalize(directive.displayName.slice(2)),
},
'vue-argument': directive.argument?.value && createAttributeVueArgument(directive.argument?.value), // TODO: how to use this in comparison to value?
'vue-argument': directive.argument && createAttributeVueArgument(directive.argument),
'vue-modifiers': directive.modifiers &&
Object.entries(directive.modifiers ?? {}).map(createAttributeVueModifier),
Object.entries(directive.modifiers).map(createAttributeVueModifier),
}
}

Expand Down
33 changes: 23 additions & 10 deletions packages/api-generator/templates/directives.d.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
import type { DirectiveBinding } from 'vue'
import type * as directives from '../../vuetify/lib/directives/index.d.mts'
import type { DirectiveBinding, ObjectDirective } from 'vue'
import type { CustomDirective } from '../../vuetify/src/composables/directiveComponent'
import type * as directives from '../../vuetify/src/directives/index.ts'

type ExtractDirectiveBindings<T> = T extends object
? {
[K in keyof T]: T[K] extends { mounted: infer M }
? M extends (first: any, second: infer B) => any
? B extends object
? {
[KK in keyof B as KK extends keyof Omit<DirectiveBinding, 'modifiers' | 'value'> ? never : KK]: B[KK]
}
[K in keyof T]: T[K] extends CustomDirective<infer M>
? {
[K in Exclude<keyof M, 'instance' | 'oldValue' | 'dir'>]: K extends 'modifiers'
? Record<string, boolean> extends M[K] ? never : M[K]
: K extends 'arg'
? string extends M[K] ? never : M[K]
: M[K]
} & {}
: T[K] extends { mounted: infer M }
? M extends (first: any, second: infer B) => any
? B extends object
? {
[KK in keyof B as KK extends keyof Omit<DirectiveBinding, 'modifiers' | 'value'> ? never : KK]: B[KK]
}
: never
: never
: T[K] extends ObjectDirective<any, infer B>
? B extends object
? { value: B, modifiers: {} }
: never
: never
: never
: {}
}
: never

Expand Down
2 changes: 1 addition & 1 deletion packages/docs/build/api-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const API_PAGES_ROOT = resolve('./node_modules/.cache/api-pages')

const require = createRequire(import.meta.url)

const sections = ['props', 'events', 'slots', 'exposed', 'sass', 'argument', 'modifiers'] as const
const sections = ['props', 'events', 'slots', 'exposed', 'sass', 'argument', 'modifiers', 'value'] as const
// This can't be imported from the api-generator because it mixes the type definitions up
type Data = {
displayName: string // user visible name used in page titles
Expand Down
10 changes: 1 addition & 9 deletions packages/docs/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ declare module 'vue' {
AboutTeamMembers: typeof import('./src/components/about/TeamMembers.vue')['default']
Alert: typeof import('./src/components/Alert.vue')['default']
ApiApiTable: typeof import('./src/components/api/ApiTable.vue')['default']
ApiDirectiveTable: typeof import('./src/components/api/DirectiveTable.vue')['default']
ApiEventsTable: typeof import('./src/components/api/EventsTable.vue')['default']
ApiExposedTable: typeof import('./src/components/api/ExposedTable.vue')['default']
ApiInline: typeof import('./src/components/api/Inline.vue')['default']
Expand All @@ -23,8 +24,6 @@ declare module 'vue' {
ApiSection: typeof import('./src/components/api/Section.vue')['default']
ApiSlotsTable: typeof import('./src/components/api/SlotsTable.vue')['default']
AppBackToTop: typeof import('./src/components/app/BackToTop.vue')['default']
AppBanner: typeof import('./src/components/app/Banner.vue')['default']
AppBarAuthDialog: typeof import('./src/components/app/bar/AuthDialog.vue')['default']
AppBarBar: typeof import('./src/components/app/bar/Bar.vue')['default']
AppBarEcosystemMenu: typeof import('./src/components/app/bar/EcosystemMenu.vue')['default']
AppBarEnterpriseLink: typeof import('./src/components/app/bar/EnterpriseLink.vue')['default']
Expand Down Expand Up @@ -78,7 +77,6 @@ declare module 'vue' {
AppSettingsOptionsQuickbarOption: typeof import('./src/components/app/settings/options/QuickbarOption.vue')['default']
AppSettingsOptionsRailDrawerOption: typeof import('./src/components/app/settings/options/RailDrawerOption.vue')['default']
AppSettingsOptionsSlashSearchOption: typeof import('./src/components/app/settings/options/SlashSearchOption.vue')['default']
AppSettingsOptionsSyncOption: typeof import('./src/components/app/settings/options/SyncOption.vue')['default']
AppSettingsOptionsThemeOption: typeof import('./src/components/app/settings/options/ThemeOption.vue')['default']
AppSettingsPerksOptions: typeof import('./src/components/app/settings/PerksOptions.vue')['default']
AppSettingsSettingsHeader: typeof import('./src/components/app/settings/SettingsHeader.vue')['default']
Expand Down Expand Up @@ -152,16 +150,10 @@ declare module 'vue' {
SponsorCard: typeof import('./src/components/sponsor/Card.vue')['default']
SponsorLink: typeof import('./src/components/sponsor/Link.vue')['default']
SponsorSponsors: typeof import('./src/components/sponsor/Sponsors.vue')['default']
UserAccountConnectedAccounts: typeof import('./src/components/user/account/ConnectedAccounts.vue')['default']
UserAccountOneSubscription: typeof import('./src/components/user/account/OneSubscription.vue')['default']
UserBadgesUserAdminBadge: typeof import('./src/components/user/badges/UserAdminBadge.vue')['default']
UserBadgesUserOneBadge: typeof import('./src/components/user/badges/UserOneBadge.vue')['default']
UserBadgesUserSponsorBadge: typeof import('./src/components/user/badges/UserSponsorBadge.vue')['default']
UserDiscordLogin: typeof import('./src/components/user/DiscordLogin.vue')['default']
UserGithubLogin: typeof import('./src/components/user/GithubLogin.vue')['default']
UserOneSubCard: typeof import('./src/components/user/OneSubCard.vue')['default']
UserUserBadges: typeof import('./src/components/user/UserBadges.vue')['default']
UserUserProfile: typeof import('./src/components/user/UserProfile.vue')['default']
UserUserTabs: typeof import('./src/components/user/UserTabs.vue')['default']
}
}
Loading

0 comments on commit 2e1e743

Please sign in to comment.