
Research
Two Malicious Rust Crates Impersonate Popular Logger to Steal Wallet Keys
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
@fastkit/vue-utils
Advanced tools
🌐 English | 日本語
A comprehensive utility library for efficient Vue application development. Provides development efficiency tools including component development, routing, property management, slot processing, and directives.
npm install @fastkit/vue-utils
import { defineSlots } from '@fastkit/vue-utils'
// Type-safe slot definition
const slots = defineSlots<{
default?: (props: { item: any; index: number }) => any
header?: (props: { title: string }) => any
footer?: () => any
}>()
export const MyComponent = defineComponent({
name: 'MyComponent',
props: {
...slots()
},
slots,
setup(props, { slots }) {
return () => (
<div class="my-component">
{slots.header?.({ title: 'Header Title' })}
<main>
{items.map((item, index) =>
slots.default?.({ item, index })
)}
</main>
{slots.footer?.()}
</div>
)
}
})
import { withCtx } from '@fastkit/vue-utils'
export const DataTable = defineComponent({
setup() {
const data = ref([])
return () => (
<table>
{data.value.map((row, index) => (
<tr key={index}>
{withCtx(() => (
<td>{row.name}</td>
), { row, index })}
</tr>
))}
</table>
)
}
})
import { createPropsOptions } from '@fastkit/vue-utils'
// Reusable property definitions
export const createSizeProps = () => createPropsOptions({
size: {
type: String as PropType<'small' | 'medium' | 'large'>,
default: 'medium'
},
width: Number,
height: Number
})
export const createColorProps = () => createPropsOptions({
color: {
type: String as PropType<'primary' | 'secondary' | 'success' | 'warning' | 'error'>,
default: 'primary'
},
variant: {
type: String as PropType<'filled' | 'outlined' | 'text'>,
default: 'filled'
}
})
// Usage in components
export const Button = defineComponent({
name: 'Button',
props: {
...createSizeProps(),
...createColorProps(),
disabled: Boolean,
loading: Boolean
},
setup(props) {
const classes = computed(() => [
'button',
`button--${props.size}`,
`button--${props.color}`,
`button--${props.variant}`,
{
'button--disabled': props.disabled,
'button--loading': props.loading
}
])
return { classes }
}
})
import { extractRouteMatchedItems } from '@fastkit/vue-utils'
export const useBreadcrumb = () => {
const route = useRoute()
const breadcrumbItems = computed(() => {
const matchedItems = extractRouteMatchedItems(route)
return matchedItems.map(item => ({
name: item.meta?.title || item.name,
path: item.path,
component: item.component
}))
})
return { breadcrumbItems }
}
import { getRouteQuery, RouteQueryType } from '@fastkit/vue-utils'
export const useSearchParams = () => {
const route = useRoute()
const router = useRouter()
// Type-safe query parameter retrieval
const search = computed(() => getRouteQuery(route, 'search', RouteQueryType.String))
const page = computed(() => getRouteQuery(route, 'page', RouteQueryType.Number) || 1)
const filters = computed(() => getRouteQuery(route, 'filters', RouteQueryType.Array))
const updateQuery = (params: Record<string, any>) => {
router.push({
query: {
...route.query,
...params
}
})
}
return {
search,
page,
filters,
updateQuery
}
}
import { RouteLocationNormalized } from 'vue-router'
export const createAuthGuard = (requiredRole?: string) => {
return (to: RouteLocationNormalized) => {
const user = getCurrentUser()
if (!user) {
return { name: 'Login', query: { redirect: to.fullPath } }
}
if (requiredRole && !user.roles.includes(requiredRole)) {
throw new Error('Access denied')
}
return true
}
}
// Usage in route definitions
const routes = [
{
path: '/admin',
component: AdminLayout,
beforeEnter: createAuthGuard('admin')
}
]
import { renderVNodeChild } from '@fastkit/vue-utils'
export const DynamicRenderer = defineComponent({
props: {
content: {
type: [String, Function, Object] as PropType<VNodeChild>,
required: true
},
props: Object
},
setup(props) {
return () => renderVNodeChild(props.content, props.props)
}
})
// Usage examples
<DynamicRenderer
:content="MyComponent"
:props="{ title: 'Dynamic Title' }"
/>
<DynamicRenderer
:content="() => h('div', 'Dynamic content')"
/>
<DynamicRenderer
content="Simple text content"
/>
import { conditionalRender } from '@fastkit/vue-utils'
export const ConditionalComponent = defineComponent({
props: {
condition: Boolean,
fallback: [String, Object, Function] as PropType<VNodeChild>
},
setup(props, { slots }) {
return () => conditionalRender(
props.condition,
() => slots.default?.(),
() => renderVNodeChild(props.fallback)
)
}
})
Determines whether a VNode corresponds to an actual DOM element.
import { isElementVNode } from '@fastkit/vue-utils'
export const ComponentInspector = defineComponent({
setup() {
const myRef = ref<ComponentPublicInstance>()
const checkVNode = () => {
const instance = getCurrentInstance()
if (instance?.subTree) {
const isRealElement = isElementVNode(instance.subTree)
console.log('VNode corresponds to DOM element:', isRealElement)
}
}
return { myRef, checkVNode }
}
})
Recursively searches a VNode tree and finds the first VNode that corresponds to an actual DOM element.
import { findFirstDomVNode, type VNodeSkipHandler } from '@fastkit/vue-utils'
export const VNodeTraverser = defineComponent({
setup() {
const containerRef = ref<HTMLElement>()
const findTargetElement = () => {
const instance = getCurrentInstance()
if (instance?.subTree) {
// Basic usage
const firstDomVNode = findFirstDomVNode(instance.subTree.children)
console.log('First DOM VNode:', firstDomVNode)
// Usage with skip handler
const skipHandler: VNodeSkipHandler = (vnode, el) => {
// Skip elements with data-skip attribute
if (el.hasAttribute('data-skip')) {
return true // Skip
}
// Only target elements with class="target"
if (!el.classList.contains('target')) {
return true // Skip
}
// Continue processing when condition is met
return undefined
}
const targetVNode = findFirstDomVNode(
instance.subTree.children,
skipHandler
)
console.log('Matching condition VNode:', targetVNode)
}
}
return { containerRef, findTargetElement }
}
})
import { defineComponent, getCurrentInstance } from 'vue'
import { findFirstDomVNode } from '@fastkit/vue-utils'
export const AccessibilityHelper = defineComponent({
setup() {
const findFocusableElement = () => {
const instance = getCurrentInstance()
if (!instance?.subTree) return
// Search for focusable elements
const focusableVNode = findFirstDomVNode(
instance.subTree.children,
(vnode, el) => {
const tagName = el.tagName.toLowerCase()
// Skip disabled or hidden elements
if (el.hasAttribute('disabled') || el.hasAttribute('hidden')) {
return true
}
// Check if element is focusable
const focusable = [
'button', 'input', 'select', 'textarea', 'a'
].includes(tagName) || el.hasAttribute('tabindex')
if (!focusable) {
return true // Skip
}
// Condition met
return undefined
}
)
if (focusableVNode?.el instanceof HTMLElement) {
focusableVNode.el.focus()
}
}
return { findFocusableElement }
}
})
<template>
<div>
<h1>Content rendered on server too</h1>
<!-- Client-only rendering -->
<ClientOnly>
<template #default>
<InteractiveChart :data="chartData" />
</template>
<template #fallback>
<div class="chart-placeholder">
Loading chart...
</div>
</template>
</ClientOnly>
</div>
</template>
<script setup lang="ts">
import { ClientOnly } from '@fastkit/vue-utils'
import InteractiveChart from './InteractiveChart.vue'
const chartData = ref([])
</script>
<template>
<ClientOnly>
<template #default>
<Suspense>
<template #default>
<AsyncHeavyComponent />
</template>
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</template>
<template #fallback>
<div>Loading on client side...</div>
</template>
</ClientOnly>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
import { ClientOnly } from '@fastkit/vue-utils'
const AsyncHeavyComponent = defineAsyncComponent(() =>
import('./HeavyComponent.vue')
)
</script>
<template>
<div>
<!-- Element visibility monitoring -->
<div
v-visibility="onVisibilityChange"
class="observed-element"
>
Observed element
</div>
<!-- With options -->
<div
v-visibility="{
handler: onIntersect,
options: {
threshold: 0.5,
rootMargin: '10px'
}
}"
>
Triggers when 50% or more is visible
</div>
</div>
</template>
<script setup lang="ts">
import { vVisibility } from '@fastkit/vue-utils'
const onVisibilityChange = (isVisible: boolean, entry: IntersectionObserverEntry) => {
console.log('Element visibility:', isVisible)
if (isVisible) {
// Process when element becomes visible
console.log('Element became visible')
}
}
const onIntersect = (isVisible: boolean) => {
if (isVisible) {
// Start lazy loading or animation
console.log('50% or more visible')
}
}
</script>
import { defineComponent, PropType } from 'vue'
import { createPropsOptions } from '@fastkit/vue-utils'
// Define base properties
const createBaseProps = () => createPropsOptions({
id: String,
class: [String, Array, Object] as PropType<any>,
style: [String, Object] as PropType<any>
})
// Feature-specific properties
const createFormFieldProps = () => createPropsOptions({
...createBaseProps(),
name: String,
label: String,
required: Boolean,
disabled: Boolean,
readonly: Boolean,
error: String,
helperText: String
})
// Component factory function
export function createFormField<T>(
fieldType: string,
extraProps: Record<string, any> = {},
renderFn: (props: any, context: any) => VNode
) {
return defineComponent({
name: `FormField${fieldType}`,
props: {
...createFormFieldProps(),
...extraProps
},
setup(props, context) {
return () => renderFn(props, context)
}
})
}
// Specific field components
export const FormInput = createFormField(
'Input',
{
type: {
type: String as PropType<'text' | 'email' | 'password' | 'number'>,
default: 'text'
},
placeholder: String,
maxlength: Number
},
(props, { emit }) => (
<div class="form-field">
{props.label && <label>{props.label}</label>}
<input
type={props.type}
name={props.name}
placeholder={props.placeholder}
disabled={props.disabled}
readonly={props.readonly}
maxlength={props.maxlength}
onInput={(e) => emit('update:modelValue', e.target.value)}
/>
{props.error && <div class="error">{props.error}</div>}
{props.helperText && <div class="helper">{props.helperText}</div>}
</div>
)
)
import { computed, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getRouteQuery, RouteQueryType } from '@fastkit/vue-utils'
export function useAdvancedRouter() {
const route = useRoute()
const router = useRouter()
// History management
const history = ref<string[]>([])
watch(() => route.path, (newPath) => {
history.value.push(newPath)
// History up to 50 entries maximum
if (history.value.length > 50) {
history.value = history.value.slice(-50)
}
}, { immediate: true })
// Type-safe query parameter management
const createQueryManager = <T extends Record<string, RouteQueryType>>(
schema: T
) => {
const queryValues = computed(() => {
const result = {} as any
for (const [key, type] of Object.entries(schema)) {
result[key] = getRouteQuery(route, key, type)
}
return result
})
const updateQuery = (updates: Partial<Record<keyof T, any>>) => {
const newQuery = { ...route.query }
for (const [key, value] of Object.entries(updates)) {
if (value === null || value === undefined) {
delete newQuery[key]
} else {
newQuery[key] = String(value)
}
}
router.replace({ query: newQuery })
}
return { queryValues, updateQuery }
}
// Navigation helper
const goBack = () => {
if (history.value.length > 1) {
router.back()
} else {
router.push('/')
}
}
const canGoBack = computed(() => history.value.length > 1)
return {
history: readonly(history),
canGoBack,
goBack,
createQueryManager
}
}
// Usage example
export function useProductFilters() {
const { createQueryManager } = useAdvancedRouter()
const { queryValues, updateQuery } = createQueryManager({
search: RouteQueryType.String,
category: RouteQueryType.String,
minPrice: RouteQueryType.Number,
maxPrice: RouteQueryType.Number,
tags: RouteQueryType.Array,
page: RouteQueryType.Number
})
const applyFilters = (filters: Partial<typeof queryValues.value>) => {
updateQuery({ ...filters, page: 1 }) // Reset page when filters change
}
return {
filters: queryValues,
applyFilters,
updateQuery
}
}
import { defineAsyncComponent, ref, computed } from 'vue'
import { renderVNodeChild } from '@fastkit/vue-utils'
export function useDynamicComponents() {
const componentCache = new Map()
const loadComponent = async (componentPath: string) => {
if (componentCache.has(componentPath)) {
return componentCache.get(componentPath)
}
try {
const module = await import(/* @vite-ignore */ componentPath)
const component = module.default || module
componentCache.set(componentPath, component)
return component
} catch (error) {
console.error(`Failed to load component: ${componentPath}`, error)
return null
}
}
const createDynamicComponent = (componentPath: string) => {
return defineAsyncComponent({
loader: () => loadComponent(componentPath),
loadingComponent: () => h('div', 'Loading...'),
errorComponent: () => h('div', 'Failed to load component'),
delay: 200,
timeout: 3000
})
}
return {
loadComponent,
createDynamicComponent,
componentCache: readonly(componentCache)
}
}
import { computed, ref, shallowRef } from 'vue'
export function useOptimizedList<T>(
items: Ref<T[]>,
keyFn: (item: T) => string | number = (item, index) => index
) {
const itemCache = new Map()
const optimizedItems = computed(() => {
const result = []
const newCache = new Map()
for (let i = 0; i < items.value.length; i++) {
const item = items.value[i]
const key = keyFn(item, i)
if (itemCache.has(key)) {
// Reuse cached item
const cached = itemCache.get(key)
newCache.set(key, cached)
result.push(cached)
} else {
// Create new item
const processedItem = {
key,
data: item,
index: i
}
newCache.set(key, processedItem)
result.push(processedItem)
}
}
// Update cache
itemCache.clear()
newCache.forEach((value, key) => {
itemCache.set(key, value)
})
return result
})
return {
optimizedItems,
clearCache: () => itemCache.clear()
}
}
// Slot definition
function defineSlots<T extends Record<string, (...args: any[]) => any>>(): PropType<T>
// Context-aware rendering
function withCtx<T>(fn: () => VNodeChild, ctx?: T): VNodeChild
// Component type checking
function isComponentCustomOptions(Component: unknown): Component is ComponentCustomOptions
// Property option creation
function createPropsOptions<T extends Record<string, any>>(props: T): T
// Component prop type extraction
type ExtractComponentPropTypes<C extends { setup?: DefineComponent<any>['setup'] }>
// Route query retrieval
function getRouteQuery(
route: RouteLocationNormalizedLoaded,
key: string,
type: RouteQueryType
): any
// Route matched items extraction
function extractRouteMatchedItems(route: RouteLocationNormalizedLoaded): RouteMatchedItem[]
// Query types
enum RouteQueryType {
String,
Number,
Boolean,
Array
}
// DOM element VNode detection
function isElementVNode(vnode: VNode): boolean
// Skip handler type
type VNodeSkipHandler = (
currentVNode: VNode,
el: Element
) => boolean | VNode | void
// First DOM element VNode search
function findFirstDomVNode(
children: VNode | VNodeNormalizedChildren | undefined,
skipVNode?: VNodeSkipHandler
): VNode | undefined
// Client-only rendering
interface ClientOnlyProps {
fallback?: VNodeChild
placeholder?: VNodeChild
}
@fastkit/helpers
- Helper functions@fastkit/ts-type-utils
- TypeScript type utilities@fastkit/visibility
- Visibility detectionvue
- Vue.js framework (peer dependency)vue-router
- Vue Router (peer dependency)MIT
FAQs
Utilities for efficient development of Vue applications.
The npm package @fastkit/vue-utils receives a total of 648 weekly downloads. As such, @fastkit/vue-utils popularity was classified as not popular.
We found that @fastkit/vue-utils demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
Research
A malicious package uses a QR code as steganography in an innovative technique.
Research
/Security News
Socket identified 80 fake candidates targeting engineering roles, including suspected North Korean operators, exposing the new reality of hiring as a security function.