feat(stage-ui,stage-web): new colorsFromElement lib extracted
This commit is contained in:
@@ -1,17 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { Color } from 'culori'
|
||||
|
||||
import html2canvas from 'html2canvas'
|
||||
|
||||
import { colorFromElement } from '@proj-airi/stage-ui/libs'
|
||||
import { BasicInputFile } from '@proj-airi/ui'
|
||||
import { average } from 'culori'
|
||||
import { Vibrant } from 'node-vibrant/browser'
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue'
|
||||
|
||||
import defaultBackgroundImage from '../../assets/backgrounds/fairy-forest.e17cbc2774.ko-fi.com.avif'
|
||||
|
||||
import { useThemeColor } from '../../composables/theme-color'
|
||||
|
||||
// Reactive state
|
||||
const isCapturing = ref(false)
|
||||
const extractedColors = ref<string[]>([])
|
||||
@@ -53,48 +47,56 @@ const topBar = computed(() => {
|
||||
return ''
|
||||
})
|
||||
|
||||
// Color extraction from image
|
||||
async function extractColorsFromImage() {
|
||||
if (images.value.length === 0) {
|
||||
async function refreshColors() {
|
||||
if (!imageRef.value || images.value.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
isCapturing.value = true
|
||||
|
||||
// Load the image
|
||||
const img = new window.Image()
|
||||
img.crossOrigin = 'anonymous'
|
||||
img.src = images.value[0]
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
img.onload = resolve
|
||||
img.onerror = reject
|
||||
})
|
||||
|
||||
// Create a canvas and draw only the top portion
|
||||
const cropHeight = Math.floor(img.naturalHeight * 0.2) // top 20% of the image
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = img.naturalWidth
|
||||
canvas.height = cropHeight
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (ctx) {
|
||||
ctx.drawImage(img, 0, 0, img.naturalWidth, cropHeight, 0, 0, img.naturalWidth, cropHeight)
|
||||
if (imageRef.value instanceof HTMLImageElement && !imageRef.value.complete) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
imageRef.value?.addEventListener('load', () => resolve(), { once: true })
|
||||
imageRef.value?.addEventListener('error', () => reject(new Error('Image failed to load')), { once: true })
|
||||
})
|
||||
}
|
||||
|
||||
// Convert canvas to data URL and use Vibrant on the cropped region
|
||||
const dataUrl = canvas.toDataURL()
|
||||
const vibrant = new Vibrant(dataUrl)
|
||||
const palette = await vibrant.getPalette()
|
||||
const result = await colorFromElement(imageRef.value, {
|
||||
mode: 'both',
|
||||
vibrant: {
|
||||
imageSource: images.value[0],
|
||||
sampleTopRatio: 0.2,
|
||||
},
|
||||
html2canvas: {
|
||||
region: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: imageRef.value.offsetWidth,
|
||||
height: 100,
|
||||
},
|
||||
sampleHeight: 20,
|
||||
sampleStride: 10,
|
||||
scale: 0.5,
|
||||
backgroundColor: null,
|
||||
allowTaint: true,
|
||||
useCORS: true,
|
||||
},
|
||||
})
|
||||
|
||||
const colors = Object.values(palette)
|
||||
.map(color => color?.hex)
|
||||
.filter((color): color is string => typeof color === 'string')
|
||||
extractedColors.value = result.vibrant?.palette ?? []
|
||||
dominantColor.value = result.vibrant?.dominant ?? ''
|
||||
topEdgeColors.value = result.html2canvas?.average ?? ''
|
||||
|
||||
extractedColors.value = colors
|
||||
dominantColor.value = palette.Vibrant?.hex || palette.DarkVibrant?.hex || colors[0]
|
||||
if (canvasRef.value && result.html2canvas?.canvas) {
|
||||
const ctx = canvasRef.value.getContext('2d')
|
||||
if (ctx) {
|
||||
canvasRef.value.width = result.html2canvas.canvas.width
|
||||
canvasRef.value.height = result.html2canvas.canvas.height
|
||||
ctx.drawImage(result.html2canvas.canvas, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Update PWA theme color
|
||||
await updateThemeColor()
|
||||
}
|
||||
catch (error) {
|
||||
@@ -105,78 +107,15 @@ async function extractColorsFromImage() {
|
||||
}
|
||||
}
|
||||
|
||||
// Capture top edge colors using html2canvas
|
||||
async function captureTopEdgeColors() {
|
||||
if (!imageRef.value)
|
||||
return
|
||||
|
||||
try {
|
||||
isCapturing.value = true
|
||||
|
||||
// Capture the background element
|
||||
const canvas = await html2canvas(imageRef.value, {
|
||||
allowTaint: true,
|
||||
useCORS: true,
|
||||
backgroundColor: null,
|
||||
scale: 0.5, // Reduce quality for performance
|
||||
height: 100, // Only capture top portion
|
||||
width: imageRef.value.offsetWidth,
|
||||
logging: false,
|
||||
})
|
||||
|
||||
// Display captured canvas for debugging
|
||||
if (canvasRef.value) {
|
||||
const ctx = canvasRef.value.getContext('2d')
|
||||
if (ctx) {
|
||||
canvasRef.value.width = canvas.width
|
||||
canvasRef.value.height = canvas.height
|
||||
ctx.drawImage(canvas, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Sample colors from top edge
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (ctx) {
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, 20) // Top 20px
|
||||
const colors: Color[] = []
|
||||
|
||||
// Sample every 10th pixel to get representative colors
|
||||
for (let i = 0; i < imageData.data.length; i += 40) { // RGBA = 4 bytes, sample every 10 pixels
|
||||
const r = imageData.data[i]
|
||||
const g = imageData.data[i + 1]
|
||||
const b = imageData.data[i + 2]
|
||||
const a = imageData.data[i + 3]
|
||||
|
||||
if (a > 0) { // Skip transparent pixels
|
||||
colors.push({ mode: 'rgb', r, g, b })
|
||||
}
|
||||
}
|
||||
|
||||
if (colors.length > 0) {
|
||||
const c = average(colors as [Color, ...Color[]])
|
||||
topEdgeColors.value = `rgb(${c.r}, ${c.g}, ${c.b})`
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Canvas capture failed:', error)
|
||||
}
|
||||
finally {
|
||||
isCapturing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-extract colors on mount
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
await extractColorsFromImage()
|
||||
await captureTopEdgeColors()
|
||||
await refreshColors()
|
||||
})
|
||||
|
||||
watch(images, async () => {
|
||||
await nextTick()
|
||||
await extractColorsFromImage()
|
||||
await captureTopEdgeColors()
|
||||
await refreshColors()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -107,10 +107,12 @@
|
||||
"dompurify": "^3.3.1",
|
||||
"es-toolkit": "catalog:",
|
||||
"gpuu": "^1.0.6",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jszip": "^3.10.1",
|
||||
"localforage": "^1.10.0",
|
||||
"mediabunny": "^1.26.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"node-vibrant": "^4.0.3",
|
||||
"ofetch": "^1.5.1",
|
||||
"pinia": "^3.0.4",
|
||||
"pixi-filters": "4",
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
import type { Color } from 'culori'
|
||||
|
||||
import html2canvas from 'html2canvas'
|
||||
|
||||
import { average } from 'culori'
|
||||
import { Vibrant } from 'node-vibrant/browser'
|
||||
|
||||
export type ColorFromElementMode = 'vibrant' | 'html2canvas' | 'both'
|
||||
|
||||
export interface ColorFromElementOptions {
|
||||
/**
|
||||
* Which extraction pipeline to run. Use `'both'` to mirror the devtools view.
|
||||
* Defaults to `'both'`.
|
||||
*/
|
||||
mode?: ColorFromElementMode
|
||||
/**
|
||||
* Options for the Vibrant-based palette extraction.
|
||||
*/
|
||||
vibrant?: {
|
||||
/**
|
||||
* Optional override for the image source; falls back to the `img` element's `currentSrc/src`.
|
||||
*/
|
||||
imageSource?: string
|
||||
/**
|
||||
* Ratio (0-1) of the image height to sample from the top edge. Defaults to 0.2.
|
||||
*/
|
||||
sampleTopRatio?: number
|
||||
}
|
||||
/**
|
||||
* Options for html2canvas-based sampling of the rendered element.
|
||||
*/
|
||||
html2canvas?: {
|
||||
/**
|
||||
* Region forwarded to html2canvas. Provide only what you need; defaults to the live element box.
|
||||
*/
|
||||
region?: {
|
||||
x?: number
|
||||
y?: number
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
/**
|
||||
* How many pixels (height) to read from the captured canvas. Defaults to 20.
|
||||
*/
|
||||
sampleHeight?: number
|
||||
/**
|
||||
* Pixel stride when sampling the captured row. Defaults to 10 (i.e., every 10th pixel).
|
||||
*/
|
||||
sampleStride?: number
|
||||
/**
|
||||
* Canvas scale used by html2canvas. Defaults to 0.5.
|
||||
*/
|
||||
scale?: number
|
||||
allowTaint?: boolean
|
||||
useCORS?: boolean
|
||||
backgroundColor?: string | null
|
||||
logging?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface ColorFromElementResult {
|
||||
vibrant?: {
|
||||
palette: string[]
|
||||
dominant?: string
|
||||
}
|
||||
html2canvas?: {
|
||||
average?: string
|
||||
canvas?: HTMLCanvasElement
|
||||
}
|
||||
}
|
||||
|
||||
export async function colorFromElement(element: HTMLElement, options: ColorFromElementOptions = {}): Promise<ColorFromElementResult> {
|
||||
const mode = options.mode ?? 'both'
|
||||
const result: ColorFromElementResult = {}
|
||||
|
||||
const shouldRunVibrant = mode === 'vibrant' || mode === 'both'
|
||||
const shouldRunHtml2Canvas = mode === 'html2canvas' || mode === 'both'
|
||||
|
||||
if (shouldRunVibrant) {
|
||||
const vibrantResult = await extractWithVibrant(element, options.vibrant)
|
||||
result.vibrant = vibrantResult
|
||||
}
|
||||
|
||||
if (shouldRunHtml2Canvas) {
|
||||
const html2CanvasResult = await extractWithHtml2Canvas(element, options.html2canvas)
|
||||
result.html2canvas = html2CanvasResult
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async function extractWithVibrant(element: HTMLElement, options: ColorFromElementOptions['vibrant']): Promise<ColorFromElementResult['vibrant']> {
|
||||
const sampleTopRatio = options?.sampleTopRatio ?? 0.2
|
||||
const imageSource = options?.imageSource ?? (element instanceof HTMLImageElement ? (element.currentSrc || element.src) : undefined)
|
||||
|
||||
if (!imageSource) {
|
||||
return { palette: [], dominant: undefined }
|
||||
}
|
||||
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'anonymous'
|
||||
img.src = imageSource
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
img.onload = () => resolve()
|
||||
img.onerror = () => reject(new Error('Failed to load image for Vibrant extraction'))
|
||||
})
|
||||
|
||||
const cropHeight = Math.max(1, Math.floor(img.naturalHeight * sampleTopRatio))
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = img.naturalWidth
|
||||
canvas.height = cropHeight
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (ctx) {
|
||||
ctx.drawImage(img, 0, 0, img.naturalWidth, cropHeight, 0, 0, img.naturalWidth, cropHeight)
|
||||
}
|
||||
|
||||
const dataUrl = canvas.toDataURL()
|
||||
const vibrant = new Vibrant(dataUrl)
|
||||
const palette = await vibrant.getPalette()
|
||||
|
||||
const colors = Object.values(palette)
|
||||
.map(color => color?.hex)
|
||||
.filter((color): color is string => typeof color === 'string')
|
||||
|
||||
return {
|
||||
palette: colors,
|
||||
dominant: palette.Vibrant?.hex || palette.DarkVibrant?.hex || colors[0],
|
||||
}
|
||||
}
|
||||
|
||||
async function extractWithHtml2Canvas(element: HTMLElement, options: ColorFromElementOptions['html2canvas']): Promise<ColorFromElementResult['html2canvas']> {
|
||||
const region = options?.region ?? {}
|
||||
// NOTICE: Region defaults to the live element box; override when the rendered size differs (e.g., scaled canvases).
|
||||
const captureWidth = region.width ?? element.offsetWidth
|
||||
const captureHeight = region.height ?? element.offsetHeight
|
||||
|
||||
const canvas = await html2canvas(element, {
|
||||
allowTaint: options?.allowTaint ?? true,
|
||||
useCORS: options?.useCORS ?? true,
|
||||
backgroundColor: options?.backgroundColor ?? null,
|
||||
scale: options?.scale ?? 0.5,
|
||||
logging: options?.logging ?? false,
|
||||
width: captureWidth,
|
||||
height: captureHeight,
|
||||
x: region.x,
|
||||
y: region.y,
|
||||
})
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) {
|
||||
return { canvas, average: undefined }
|
||||
}
|
||||
|
||||
const sampleHeight = Math.max(1, Math.min(options?.sampleHeight ?? 20, canvas.height))
|
||||
const sampleStride = Math.max(1, options?.sampleStride ?? 10)
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, sampleHeight)
|
||||
const colors: Color[] = []
|
||||
|
||||
for (let i = 0; i < imageData.data.length; i += 4 * sampleStride) {
|
||||
const r = imageData.data[i]
|
||||
const g = imageData.data[i + 1]
|
||||
const b = imageData.data[i + 2]
|
||||
const a = imageData.data[i + 3]
|
||||
|
||||
if (a > 0) {
|
||||
colors.push({ mode: 'rgb', r, g, b })
|
||||
}
|
||||
}
|
||||
|
||||
if (colors.length === 0) {
|
||||
return { canvas, average: undefined }
|
||||
}
|
||||
|
||||
const averaged = average(colors as [Color, ...Color[]])
|
||||
const averageColor = averaged ? `rgb(${averaged.r}, ${averaged.g}, ${averaged.b})` : undefined
|
||||
|
||||
return {
|
||||
canvas,
|
||||
average: averageColor,
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './audio/manager'
|
||||
export * from './color-from-element'
|
||||
export * from './workers/types'
|
||||
export * from './workers/worker'
|
||||
|
||||
Generated
+10
-4
@@ -1595,6 +1595,9 @@ importers:
|
||||
gpuu:
|
||||
specifier: ^1.0.6
|
||||
version: 1.0.6
|
||||
html2canvas:
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1
|
||||
jszip:
|
||||
specifier: ^3.10.1
|
||||
version: 3.10.1
|
||||
@@ -1607,6 +1610,9 @@ importers:
|
||||
nanoid:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6
|
||||
node-vibrant:
|
||||
specifier: ^4.0.3
|
||||
version: 4.0.3(encoding@0.1.13)
|
||||
ofetch:
|
||||
specifier: ^1.5.1
|
||||
version: 1.5.1
|
||||
@@ -1672,7 +1678,7 @@ importers:
|
||||
version: 4.6.4(vue@3.5.25(typescript@5.9.3))
|
||||
vue-sonner:
|
||||
specifier: ^2.0.9
|
||||
version: 2.0.9(@nuxt/kit@4.0.3(magicast@0.5.1))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.10.4)(@vue/compiler-sfc@3.5.25)(bufferutil@4.0.9)(cac@6.7.14)(db0@0.3.4(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-beta.53)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.1.8(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))
|
||||
version: 2.0.9(@nuxt/kit@4.0.3(magicast@0.5.1))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.10.4)(@vue/compiler-sfc@3.5.25)(bufferutil@4.0.9)(cac@6.7.14)(db0@0.3.4(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-beta.53)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.1.8(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2))
|
||||
vue-tsc:
|
||||
specifier: ^3.1.8
|
||||
version: 3.1.8(typescript@5.9.3)
|
||||
@@ -29137,7 +29143,7 @@ snapshots:
|
||||
- yaml
|
||||
optional: true
|
||||
|
||||
nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.10.4)(@vue/compiler-sfc@3.5.25)(bufferutil@4.0.9)(cac@6.7.14)(db0@0.3.4(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-beta.53)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.1.8(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2):
|
||||
nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.10.4)(@vue/compiler-sfc@3.5.25)(bufferutil@4.0.9)(cac@6.7.14)(db0@0.3.4(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-beta.53)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.1.8(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2):
|
||||
dependencies:
|
||||
'@nuxt/cli': 3.31.2(cac@6.7.14)(magicast@0.5.1)
|
||||
'@nuxt/devalue': 2.0.2
|
||||
@@ -33395,11 +33401,11 @@ snapshots:
|
||||
'@nuxt/schema': 4.0.3
|
||||
nuxt: 4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.10.4)(@vue/compiler-sfc@3.5.25)(bufferutil@4.0.9)(cac@6.7.14)(db0@0.3.4(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown-vite@7.2.11(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(rolldown@1.0.0-beta.53)(rollup@2.79.2)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(vue-tsc@3.1.8(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)
|
||||
|
||||
vue-sonner@2.0.9(@nuxt/kit@4.0.3(magicast@0.5.1))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.10.4)(@vue/compiler-sfc@3.5.25)(bufferutil@4.0.9)(cac@6.7.14)(db0@0.3.4(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-beta.53)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.1.8(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)):
|
||||
vue-sonner@2.0.9(@nuxt/kit@4.0.3(magicast@0.5.1))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.10.4)(@vue/compiler-sfc@3.5.25)(bufferutil@4.0.9)(cac@6.7.14)(db0@0.3.4(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-beta.53)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.1.8(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)):
|
||||
optionalDependencies:
|
||||
'@nuxt/kit': 4.0.3(magicast@0.5.1)
|
||||
'@nuxt/schema': 4.0.3
|
||||
nuxt: 4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.10.4)(@vue/compiler-sfc@3.5.25)(bufferutil@4.0.9)(cac@6.7.14)(db0@0.3.4(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-beta.53)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.1.8(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)
|
||||
nuxt: 4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.10.4)(@vue/compiler-sfc@3.5.25)(bufferutil@4.0.9)(cac@6.7.14)(db0@0.3.4(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-beta.53)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.1.8(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)
|
||||
|
||||
vue-sonner@2.0.9(@nuxt/kit@4.0.3(magicast@0.5.1))(@nuxt/schema@4.0.3)(nuxt@4.0.3(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.0)(@types/node@24.10.4)(@vue/compiler-sfc@3.5.25)(bufferutil@4.0.9)(cac@6.7.14)(db0@0.3.4(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7)))(drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/pg@8.16.0)(gel@2.0.0)(pg@8.16.3)(postgres@3.4.7))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.8.2)(less@4.4.2)(lightningcss@1.30.2)(magicast@0.5.1)(optionator@0.9.4)(rolldown@1.0.0-beta.53)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(vite@7.2.7(@types/node@24.10.4)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2))(vue-tsc@3.1.8(typescript@5.9.3))(xml2js@0.6.2)(yaml@2.8.2)):
|
||||
optionalDependencies:
|
||||
|
||||
Reference in New Issue
Block a user