const BLOCK_EVENT_KEY_PREFIX = 'on'
const BLOCK_EVENT_KEY_PATTERN = /^on(?:[A-Z]|Update:)/
function toPascalCase(input = '') {
return String(input)
.split(/[-_\s]+/)
.filter(Boolean)
.map((item) => item.charAt(0).toUpperCase() + item.slice(1))
.join('')
}
function isImportedBlockEventBindingKey(key = '') {
return BLOCK_EVENT_KEY_PATTERN.test(String(key))
}
function toCamelCase(input = '') {
return String(input).replace(/-([a-zA-Z0-9])/g, (_match, segment) => String(segment).toUpperCase())
}
export function normalizeImportedBlockPropName(propName = '') {
const raw = String(propName || '').trim()
if (!raw) return ''
return toCamelCase(raw)
}
export function toImportedBlockEventKey(eventName = '') {
const raw = String(eventName || '').trim()
if (!raw) return ''
if (isImportedBlockEventBindingKey(raw)) return raw
if (raw.startsWith('update:')) {
const [head, ...rest] = raw.split(':')
return `${BLOCK_EVENT_KEY_PREFIX}${toPascalCase(head)}:${rest.join(':')}`
}
return `${BLOCK_EVENT_KEY_PREFIX}${toPascalCase(raw)}`
}
export function splitImportedBlockBindings(bindings: Record<string, any> = {}) {
const props: Record<string, any> = {}
const events: Record<string, any> = {}
Object.entries(bindings || {}).forEach(([key, value]) => {
if (!key || key === 'key' || key === 'ref') return
if (isImportedBlockEventBindingKey(key)) {
events[key] = value
return
}
const normalizedKey = normalizeImportedBlockPropName(key)
if (!normalizedKey) return
props[normalizedKey] = value
})
return { props, events }
}
export function buildImportedBlockEvents(emits: string[] = [], boundEvents: Record<string, any> = {}) {
const events: Record<string, any> = {}
const eventKeys = new Set<string>()
;(Array.isArray(emits) ? emits : []).forEach((eventName) => {
const normalizedKey = toImportedBlockEventKey(eventName)
if (normalizedKey) eventKeys.add(normalizedKey)
})
Object.keys(boundEvents || {}).forEach((key) => {
if (isImportedBlockEventBindingKey(key)) eventKeys.add(key)
})
eventKeys.forEach((key) => {
events[key] = {
name: key,
label: {
zh_CN: key
},
description: {
zh_CN: key
}
}
})
return events
}
export function normalizeImportedBlockDefaultValue(value: any) {
if (value === null || value === undefined) return ''
if (typeof value === 'object' && value.type === 'JSExpression') {
return { ...value }
}
return value
}
function getImportedBlockEmptyDefaultValueByType(type = '') {
switch (String(type || '').toLowerCase()) {
case 'number':
return 0
case 'boolean':
return false
case 'array':
return []
case 'object':
return {}
case 'function':
return {
type: 'JSFunction',
value: 'function() {}'
}
case 'string':
default:
return ''
}
}
function isImportedBlockLiteralExpression(value = '') {
const raw = String(value || '').trim()
if (!raw) return false
if (/^['"`].*['"`]$/.test(raw)) return true
if (/^-?\d+(\.\d+)?$/.test(raw)) return true
if (raw === 'true' || raw === 'false' || raw === 'null') return true
if ((raw.startsWith('[') && raw.endsWith(']')) || (raw.startsWith('{') && raw.endsWith('}'))) return true
return false
}
function isImportedBlockDynamicBindingValue(value: any) {
return (
value?.type === 'JSExpression' && typeof value?.value === 'string' && !isImportedBlockLiteralExpression(value.value)
)
}
function isStringLiteralType(raw = '') {
return /^['"`].*['"`]$/.test(String(raw).trim())
}
function isNumericLiteralType(raw = '') {
return /^-?\d+(\.\d+)?$/.test(String(raw).trim())
}
function normalizeImportedSingleBlockPropType(raw = '') {
if (!raw) return ''
const lowerType = raw.toLowerCase()
if (['string', 'number', 'boolean', 'array', 'object', 'function'].includes(lowerType)) {
return lowerType
}
if (lowerType === 'any' || lowerType === 'unknown' || lowerType === 'void' || lowerType === 'never') {
return 'string'
}
if (
lowerType.startsWith('array<') ||
lowerType.startsWith('readonlyarray<') ||
lowerType.endsWith('[]') ||
lowerType.startsWith('tuple')
) {
return 'array'
}
if (
lowerType === 'record' ||
lowerType.startsWith('record<') ||
lowerType.startsWith('map<') ||
lowerType.startsWith('{') ||
lowerType === 'date'
) {
return 'object'
}
if (lowerType.includes('=>') || lowerType.startsWith('(') || lowerType === 'fn') {
return 'function'
}
if (isStringLiteralType(raw)) return 'string'
if (isNumericLiteralType(raw)) return 'number'
if (raw === 'true' || raw === 'false') return 'boolean'
if (/^[A-Z]/.test(raw)) return 'object'
return 'string'
}
function normalizeImportedUnionType(type = '') {
const segments = String(type)
.split('|')
.map((item) => item.trim())
.filter((item) => item && item !== 'undefined' && item !== 'null')
if (!segments.length) return ''
if (segments.every((item) => item === 'string' || isStringLiteralType(item))) return 'string'
if (segments.every((item) => item === 'number' || isNumericLiteralType(item))) return 'number'
if (segments.every((item) => item === 'boolean' || item === 'true' || item === 'false')) return 'boolean'
const normalizedSegments = segments.map((item) => normalizeImportedSingleBlockPropType(item)).filter(Boolean)
return normalizedSegments.length === 1 || new Set(normalizedSegments).size === 1 ? normalizedSegments[0] : 'string'
}
export function normalizeImportedBlockPropType(type: any) {
const raw = String(type || '').trim()
if (!raw) return ''
if (raw.toLowerCase().includes('|')) {
return normalizeImportedUnionType(raw)
}
return normalizeImportedSingleBlockPropType(raw)
}
export function inferImportedBlockPropTypeFromValue(value: any) {
if (value === null || value === undefined) return 'string'
if (Array.isArray(value)) return 'array'
if (typeof value === 'object') {
if (value.type === 'JSExpression') return 'string'
if (value.type === 'JSFunction') return 'function'
return 'object'
}
if (['string', 'number', 'boolean', 'function'].includes(typeof value)) {
return typeof value
}
return 'string'
}
export function resolveImportedBlockPropType(declaredType: any, value: any) {
const normalizedDeclaredType = normalizeImportedBlockPropType(declaredType)
if (normalizedDeclaredType) return normalizedDeclaredType
return inferImportedBlockPropTypeFromValue(value)
}
export function resolveImportedBlockDefaultValue(declaredDefault: any, value: any, propType?: string) {
const normalizedDeclaredDefault = normalizeImportedBlockDefaultValue(declaredDefault)
const normalizedValue = normalizeImportedBlockDefaultValue(value)
const resolvedType = String(propType || resolveImportedBlockPropType(undefined, value) || 'string')
if (isImportedBlockDynamicBindingValue(value)) {
if (declaredDefault !== undefined) {
return normalizedDeclaredDefault
}
return getImportedBlockEmptyDefaultValueByType(resolvedType)
}
if (value !== undefined) return normalizedValue
if (declaredDefault !== undefined) return normalizedDeclaredDefault
return getImportedBlockEmptyDefaultValueByType(resolvedType)
}