Add Vue linting rules and fix all violations
Enable 12 vue/ rules in oxlint: lifecycle, props, emits, imports. Convert 9 components from `const props = defineProps()` to destructured form (Vue 3.5+ reactive destructuring).
This commit is contained in:
parent
7a334268d0
commit
9c110bff92
17
oxlint.json
17
oxlint.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["typescript", "import", "unicorn"],
|
||||
"plugins": ["typescript", "import", "unicorn", "vue"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2024": true
|
||||
|
|
@ -17,7 +17,20 @@
|
|||
"import/no-self-import": "error",
|
||||
|
||||
"unicorn/no-instanceof-array": "error",
|
||||
"unicorn/no-typeof-undefined": "error"
|
||||
"unicorn/no-typeof-undefined": "error",
|
||||
|
||||
"vue/no-arrow-functions-in-watch": "error",
|
||||
"vue/no-deprecated-destroyed-lifecycle": "error",
|
||||
"vue/no-export-in-script-setup": "error",
|
||||
"vue/no-lifecycle-after-await": "error",
|
||||
"vue/no-import-compiler-macros": "error",
|
||||
"vue/prefer-import-from-vue": "error",
|
||||
"vue/valid-define-emits": "error",
|
||||
"vue/valid-define-props": "error",
|
||||
"vue/no-required-prop-with-default": "error",
|
||||
"vue/define-emits-declaration": "error",
|
||||
"vue/define-props-destructuring": "error",
|
||||
"vue/require-typed-ref": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
|
||||
import { selectContent, selectItem, selectTrigger } from '@/components/ui/select'
|
||||
|
||||
defineProps<{
|
||||
const { options, placeholder } = defineProps<{
|
||||
options: { value: T; label: string }[]
|
||||
placeholder?: string
|
||||
}>()
|
||||
|
|
|
|||
|
|
@ -4,13 +4,10 @@ import { colorToHexRaw, parseColor } from '@open-pencil/core'
|
|||
|
||||
import type { Color } from '@/types'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
color: Color
|
||||
editable?: boolean
|
||||
}>(),
|
||||
{ editable: false }
|
||||
)
|
||||
const { color, editable = false } = defineProps<{
|
||||
color: Color
|
||||
editable?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
update: [color: Color]
|
||||
|
|
@ -18,8 +15,8 @@ const emit = defineEmits<{
|
|||
|
||||
function onHexChange(e: Event) {
|
||||
const hex = (e.target as HTMLInputElement).value
|
||||
const color = parseColor(hex.startsWith('#') ? hex : `#${hex}`)
|
||||
emit('update', { ...color, a: props.color.a })
|
||||
const parsed = parseColor(hex.startsWith('#') ? hex : `#${hex}`)
|
||||
emit('update', { ...parsed, a: color.a })
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import HsvColorArea from './HsvColorArea.vue'
|
|||
|
||||
import type { Color } from '@/types'
|
||||
|
||||
const props = defineProps<{
|
||||
const { color } = defineProps<{
|
||||
color: Color
|
||||
}>()
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ const emit = defineEmits<{
|
|||
update: [color: Color]
|
||||
}>()
|
||||
|
||||
const swatchColor = computed(() => colorToCSS(props.color))
|
||||
const swatchColor = computed(() => colorToCSS(color))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const DEFAULT_GRADIENT_TRANSFORMS: Record<GradientSubtype, GradientTransform> =
|
|||
GRADIENT_DIAMOND: { m00: 0.5, m01: 0, m02: 0.5, m10: 0, m11: 0.5, m12: 0.5 }
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
const { fill } = defineProps<{
|
||||
fill: Fill
|
||||
}>()
|
||||
|
||||
|
|
@ -55,68 +55,68 @@ const emit = defineEmits<{
|
|||
const activeStopIndex = ref(0)
|
||||
|
||||
const fillCategory = computed<FillCategory>(() => {
|
||||
if (props.fill.type.startsWith('GRADIENT')) return 'GRADIENT'
|
||||
if (props.fill.type === 'IMAGE') return 'IMAGE'
|
||||
if (fill.type.startsWith('GRADIENT')) return 'GRADIENT'
|
||||
if (fill.type === 'IMAGE') return 'IMAGE'
|
||||
return 'SOLID'
|
||||
})
|
||||
|
||||
const isGradient = computed(() => fillCategory.value === 'GRADIENT')
|
||||
|
||||
const gradientSubtype = computed(() =>
|
||||
isGradient.value ? (props.fill.type as GradientSubtype) : 'GRADIENT_LINEAR'
|
||||
isGradient.value ? (fill.type as GradientSubtype) : 'GRADIENT_LINEAR'
|
||||
)
|
||||
|
||||
const activeColor = computed(() => {
|
||||
if (isGradient.value && props.fill.gradientStops?.length) {
|
||||
const idx = Math.min(activeStopIndex.value, props.fill.gradientStops.length - 1)
|
||||
return props.fill.gradientStops[idx].color
|
||||
if (isGradient.value && fill.gradientStops?.length) {
|
||||
const idx = Math.min(activeStopIndex.value, fill.gradientStops.length - 1)
|
||||
return fill.gradientStops[idx].color
|
||||
}
|
||||
return props.fill.color
|
||||
return fill.color
|
||||
})
|
||||
|
||||
function onColorUpdate(color: Color) {
|
||||
if (isGradient.value && props.fill.gradientStops?.length) {
|
||||
const stops = [...props.fill.gradientStops]
|
||||
if (isGradient.value && fill.gradientStops?.length) {
|
||||
const stops = [...fill.gradientStops]
|
||||
const idx = Math.min(activeStopIndex.value, stops.length - 1)
|
||||
stops[idx] = { ...stops[idx], color }
|
||||
emit('update', { ...props.fill, gradientStops: stops })
|
||||
emit('update', { ...fill, gradientStops: stops })
|
||||
} else {
|
||||
emit('update', { ...props.fill, color })
|
||||
emit('update', { ...fill, color })
|
||||
}
|
||||
}
|
||||
|
||||
function setCategory(cat: FillCategory) {
|
||||
if (cat === fillCategory.value) return
|
||||
if (cat === 'SOLID') {
|
||||
const color = props.fill.gradientStops?.length
|
||||
? { ...props.fill.gradientStops[0].color }
|
||||
: props.fill.color
|
||||
emit('update', { ...props.fill, type: 'SOLID', color })
|
||||
const color = fill.gradientStops?.length
|
||||
? { ...fill.gradientStops[0].color }
|
||||
: fill.color
|
||||
emit('update', { ...fill, type: 'SOLID', color })
|
||||
} else if (cat === 'GRADIENT') {
|
||||
const type: GradientSubtype = 'GRADIENT_LINEAR'
|
||||
const stops = props.fill.gradientStops?.length
|
||||
? props.fill.gradientStops
|
||||
const stops = fill.gradientStops?.length
|
||||
? fill.gradientStops
|
||||
: [
|
||||
{ color: { ...props.fill.color }, position: 0 },
|
||||
{ color: { ...fill.color }, position: 0 },
|
||||
{ color: { r: 1, g: 1, b: 1, a: 1 }, position: 1 }
|
||||
]
|
||||
emit('update', {
|
||||
...props.fill,
|
||||
...fill,
|
||||
type,
|
||||
gradientStops: stops,
|
||||
gradientTransform: DEFAULT_GRADIENT_TRANSFORMS[type]
|
||||
})
|
||||
activeStopIndex.value = 0
|
||||
} else {
|
||||
emit('update', { ...props.fill, type: 'IMAGE' })
|
||||
emit('update', { ...fill, type: 'IMAGE' })
|
||||
}
|
||||
}
|
||||
|
||||
function setGradientSubtype(type: string) {
|
||||
const subtype = type as GradientSubtype
|
||||
if (subtype === props.fill.type) return
|
||||
if (subtype === fill.type) return
|
||||
emit('update', {
|
||||
...props.fill,
|
||||
...fill,
|
||||
type: subtype,
|
||||
gradientTransform: DEFAULT_GRADIENT_TRANSFORMS[subtype]
|
||||
})
|
||||
|
|
@ -127,8 +127,8 @@ function selectStop(index: number) {
|
|||
}
|
||||
|
||||
function addStop() {
|
||||
if (!props.fill.gradientStops) return
|
||||
const stops = [...props.fill.gradientStops]
|
||||
if (!fill.gradientStops) return
|
||||
const stops = [...fill.gradientStops]
|
||||
const newPos =
|
||||
stops.length >= 2
|
||||
? (stops[stops.length - 2].position + stops[stops.length - 1].position) / 2
|
||||
|
|
@ -137,39 +137,39 @@ function addStop() {
|
|||
stops.sort((a, b) => a.position - b.position)
|
||||
const newIndex = stops.findIndex((s) => s.position === newPos)
|
||||
activeStopIndex.value = newIndex
|
||||
emit('update', { ...props.fill, gradientStops: stops })
|
||||
emit('update', { ...fill, gradientStops: stops })
|
||||
}
|
||||
|
||||
function removeStop(index: number) {
|
||||
if (!props.fill.gradientStops || props.fill.gradientStops.length <= 2) return
|
||||
const stops = props.fill.gradientStops.filter((_, i) => i !== index)
|
||||
if (!fill.gradientStops || fill.gradientStops.length <= 2) return
|
||||
const stops = fill.gradientStops.filter((_, i) => i !== index)
|
||||
activeStopIndex.value = Math.min(activeStopIndex.value, stops.length - 1)
|
||||
emit('update', { ...props.fill, gradientStops: stops })
|
||||
emit('update', { ...fill, gradientStops: stops })
|
||||
}
|
||||
|
||||
function updateStopPosition(index: number, value: string) {
|
||||
if (!props.fill.gradientStops) return
|
||||
if (!fill.gradientStops) return
|
||||
const pos = Math.max(0, Math.min(100, Number(value))) / 100
|
||||
const stops = [...props.fill.gradientStops]
|
||||
const stops = [...fill.gradientStops]
|
||||
stops[index] = { ...stops[index], position: pos }
|
||||
emit('update', { ...props.fill, gradientStops: stops })
|
||||
emit('update', { ...fill, gradientStops: stops })
|
||||
}
|
||||
|
||||
function updateStopColor(index: number, hex: string) {
|
||||
if (!props.fill.gradientStops) return
|
||||
if (!fill.gradientStops) return
|
||||
const color = parseColor(hex.startsWith('#') ? hex : `#${hex}`)
|
||||
if (!color) return
|
||||
const stops = [...props.fill.gradientStops]
|
||||
const stops = [...fill.gradientStops]
|
||||
stops[index] = { ...stops[index], color: { ...color, a: stops[index].color.a } }
|
||||
emit('update', { ...props.fill, gradientStops: stops })
|
||||
emit('update', { ...fill, gradientStops: stops })
|
||||
}
|
||||
|
||||
function updateStopOpacity(index: number, value: string) {
|
||||
if (!props.fill.gradientStops) return
|
||||
if (!fill.gradientStops) return
|
||||
const a = Math.max(0, Math.min(100, Number(value))) / 100
|
||||
const stops = [...props.fill.gradientStops]
|
||||
const stops = [...fill.gradientStops]
|
||||
stops[index] = { ...stops[index], color: { ...stops[index].color, a } }
|
||||
emit('update', { ...props.fill, gradientStops: stops })
|
||||
emit('update', { ...fill, gradientStops: stops })
|
||||
}
|
||||
|
||||
function gradientStops(stops: GradientStop[]): string {
|
||||
|
|
@ -177,15 +177,15 @@ function gradientStops(stops: GradientStop[]): string {
|
|||
}
|
||||
|
||||
const swatchBackground = computed(() => {
|
||||
if (isGradient.value && props.fill.gradientStops?.length) {
|
||||
return `linear-gradient(to right, ${gradientStops(props.fill.gradientStops)})`
|
||||
if (isGradient.value && fill.gradientStops?.length) {
|
||||
return `linear-gradient(to right, ${gradientStops(fill.gradientStops)})`
|
||||
}
|
||||
return colorToCSS(props.fill.color)
|
||||
return colorToCSS(fill.color)
|
||||
})
|
||||
|
||||
const gradientBarBackground = computed(() => {
|
||||
if (!props.fill.gradientStops?.length) return ''
|
||||
return `linear-gradient(to right, ${gradientStops(props.fill.gradientStops)})`
|
||||
if (!fill.gradientStops?.length) return ''
|
||||
return `linear-gradient(to right, ${gradientStops(fill.gradientStops)})`
|
||||
})
|
||||
|
||||
const gradientStopBarRef = ref<HTMLDivElement | null>(null)
|
||||
|
|
@ -203,9 +203,9 @@ function onStopBarPointerMove(e: PointerEvent) {
|
|||
if (!el || draggingStopIndex.value === null || !el.hasPointerCapture(e.pointerId)) return
|
||||
const rect = el.getBoundingClientRect()
|
||||
const pos = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
|
||||
const stops = [...(props.fill.gradientStops ?? [])]
|
||||
const stops = [...(fill.gradientStops ?? [])]
|
||||
stops[draggingStopIndex.value] = { ...stops[draggingStopIndex.value], position: pos }
|
||||
emit('update', { ...props.fill, gradientStops: stops })
|
||||
emit('update', { ...fill, gradientStops: stops })
|
||||
}
|
||||
|
||||
function onStopBarPointerUp() {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { colorToHex8, rgba255ToColor } from '@open-pencil/core'
|
|||
|
||||
import type { Color } from '@/types'
|
||||
|
||||
const props = defineProps<{
|
||||
const { color } = defineProps<{
|
||||
color: Color
|
||||
}>()
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ const emit = defineEmits<{
|
|||
update: [color: Color]
|
||||
}>()
|
||||
|
||||
const hexWithAlpha = computed(() => colorToHex8(props.color))
|
||||
const hexWithAlpha = computed(() => colorToHex8(color))
|
||||
|
||||
const rekaColor = computed(() => normalizeColor(hexWithAlpha.value))
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import { initials } from '@/utils/text'
|
|||
import type { Component } from 'vue'
|
||||
import type { CollabState, RemotePeer } from '@/composables/use-collab'
|
||||
|
||||
const props = defineProps<{
|
||||
const { collabState, collabPeers, pendingRoomId, followingPeer } = defineProps<{
|
||||
collabState: CollabState
|
||||
collabPeers: RemotePeer[]
|
||||
pendingRoomId?: string | null
|
||||
|
|
@ -69,7 +69,7 @@ const menuItems: MenuAction[] = [
|
|||
{ icon: IconZoomIn, label: 'Zoom to fit', action: () => store.zoomToFit() }
|
||||
]
|
||||
|
||||
const onlineCount = computed(() => props.collabPeers.length + 1)
|
||||
const onlineCount = computed(() => collabPeers.length + 1)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -115,7 +115,7 @@ const onlineCount = computed(() => props.collabPeers.length + 1)
|
|||
<!-- Center: Online badge + action toast -->
|
||||
<div class="pointer-events-auto relative mx-auto flex flex-col items-center gap-1.5">
|
||||
<!-- Online badge with peers popover -->
|
||||
<PopoverRoot v-if="props.collabState.connected">
|
||||
<PopoverRoot v-if="collabState.connected">
|
||||
<PopoverTrigger as-child>
|
||||
<button
|
||||
class="flex h-8 cursor-pointer items-center gap-1.5 rounded-full border border-white/10 bg-panel/70 px-3 shadow-md backdrop-blur-xl select-none active:bg-hover"
|
||||
|
|
@ -137,33 +137,33 @@ const onlineCount = computed(() => props.collabPeers.length + 1)
|
|||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
class="flex size-7 shrink-0 items-center justify-center rounded-full text-[10px] font-semibold text-white"
|
||||
:style="{ background: colorToCSS(props.collabState.localColor) }"
|
||||
:style="{ background: colorToCSS(collabState.localColor) }"
|
||||
>
|
||||
{{ initials(props.collabState.localName || 'You') }}
|
||||
{{ initials(collabState.localName || 'You') }}
|
||||
</div>
|
||||
<span class="min-w-0 flex-1 truncate text-xs text-surface">
|
||||
{{ props.collabState.localName || 'You' }}
|
||||
{{ collabState.localName || 'You' }}
|
||||
</span>
|
||||
<span class="text-[10px] text-muted">you</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="peer in props.collabPeers"
|
||||
v-for="peer in collabPeers"
|
||||
:key="peer.clientId"
|
||||
class="flex cursor-pointer items-center gap-2 rounded-md px-0.5 py-0.5 select-none active:bg-hover"
|
||||
@click="
|
||||
emit('follow', props.followingPeer === peer.clientId ? null : peer.clientId)
|
||||
emit('follow', followingPeer === peer.clientId ? null : peer.clientId)
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="flex size-7 shrink-0 items-center justify-center rounded-full text-[10px] font-semibold text-white"
|
||||
:class="props.followingPeer === peer.clientId ? 'ring-2 ring-white/40' : ''"
|
||||
:class="followingPeer === peer.clientId ? 'ring-2 ring-white/40' : ''"
|
||||
:style="{ background: colorToCSS(peer.color) }"
|
||||
>
|
||||
{{ initials(peer.name) }}
|
||||
</div>
|
||||
<span class="min-w-0 flex-1 truncate text-xs text-surface">{{ peer.name }}</span>
|
||||
<span v-if="props.followingPeer === peer.clientId" class="text-[10px] text-accent"
|
||||
<span v-if="followingPeer === peer.clientId" class="text-[10px] text-accent"
|
||||
>following</span
|
||||
>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,28 +2,19 @@
|
|||
import { ref, computed } from 'vue'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: number | symbol
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
icon?: string
|
||||
label?: string
|
||||
suffix?: string
|
||||
sensitivity?: number
|
||||
placeholder?: string
|
||||
}>(),
|
||||
{
|
||||
min: -Infinity,
|
||||
max: Infinity,
|
||||
step: 1,
|
||||
sensitivity: 1,
|
||||
placeholder: 'Mixed'
|
||||
}
|
||||
)
|
||||
const { modelValue, min = -Infinity, max = Infinity, step = 1, icon, label, suffix, sensitivity = 1, placeholder = 'Mixed' } = defineProps<{
|
||||
modelValue: number | symbol
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
icon?: string
|
||||
label?: string
|
||||
suffix?: string
|
||||
sensitivity?: number
|
||||
placeholder?: string
|
||||
}>()
|
||||
|
||||
const isMixed = computed(() => typeof props.modelValue === 'symbol')
|
||||
const isMixed = computed(() => typeof modelValue === 'symbol')
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: number]
|
||||
|
|
@ -37,7 +28,7 @@ const scrubbing = ref(false)
|
|||
let stopMove: (() => void) | undefined
|
||||
let stopUp: (() => void) | undefined
|
||||
|
||||
const numericValue = computed(() => (isMixed.value ? 0 : (props.modelValue as number)))
|
||||
const numericValue = computed(() => (isMixed.value ? 0 : (modelValue as number)))
|
||||
const displayValue = computed(() => (isMixed.value ? '' : Math.round(numericValue.value)))
|
||||
|
||||
function startScrub(e: PointerEvent) {
|
||||
|
|
@ -57,9 +48,9 @@ function startScrub(e: PointerEvent) {
|
|||
document.body.style.cursor = 'ew-resize'
|
||||
}
|
||||
if (hasMoved) {
|
||||
accumulated += dx * props.step * props.sensitivity
|
||||
const clamped = Math.round(Math.min(props.max, Math.max(props.min, accumulated)))
|
||||
if (clamped !== props.modelValue) {
|
||||
accumulated += dx * step * sensitivity
|
||||
const clamped = Math.round(Math.min(max, Math.max(min, accumulated)))
|
||||
if (clamped !== modelValue) {
|
||||
emit('update:modelValue', clamped)
|
||||
}
|
||||
}
|
||||
|
|
@ -71,8 +62,8 @@ function startScrub(e: PointerEvent) {
|
|||
stopMove?.()
|
||||
stopUp?.()
|
||||
if (hasMoved) {
|
||||
if (props.modelValue !== valueBeforeScrub) {
|
||||
emit('commit', props.modelValue, valueBeforeScrub)
|
||||
if (modelValue !== valueBeforeScrub) {
|
||||
emit('commit', modelValue, valueBeforeScrub)
|
||||
}
|
||||
} else {
|
||||
startEdit()
|
||||
|
|
@ -91,7 +82,7 @@ function commitEdit(e: Event) {
|
|||
const val = +(e.target as HTMLInputElement).value
|
||||
const previous = numericValue.value
|
||||
if (!Number.isNaN(val)) {
|
||||
const clamped = Math.min(props.max, Math.max(props.min, val))
|
||||
const clamped = Math.min(max, Math.max(min, val))
|
||||
emit('update:modelValue', clamped)
|
||||
if (clamped !== previous) {
|
||||
emit('commit', clamped, previous)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { useAIChat } from '@/composables/use-chat'
|
|||
|
||||
const { providerID, providerDef, modelID, customModelID } = useAIChat()
|
||||
|
||||
const props = defineProps<{
|
||||
const { status } = defineProps<{
|
||||
status: 'ready' | 'submitted' | 'streaming' | 'error'
|
||||
}>()
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ const emit = defineEmits<{
|
|||
|
||||
const input = ref('')
|
||||
|
||||
const isStreaming = computed(() => props.status === 'streaming' || props.status === 'submitted')
|
||||
const isStreaming = computed(() => status === 'streaming' || status === 'submitted')
|
||||
const isCustomProvider = computed(() => providerID.value === 'openai-compatible')
|
||||
|
||||
const selectedModelName = computed(() => {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { AI_PROVIDERS, useAIChat } from '@/composables/use-chat'
|
|||
|
||||
const { providerID, providerDef } = useAIChat()
|
||||
|
||||
defineProps<{
|
||||
const { triggerClass, itemClass, testId } = defineProps<{
|
||||
triggerClass?: string
|
||||
itemClass?: string
|
||||
testId?: string
|
||||
|
|
|
|||
Loading…
Reference in a new issue