@lukso/transaction-view-headless 0.2.2-dev.a8c9315
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +51 -0
- package/CHANGELOG.md +88 -0
- package/COMPONENT_ARCHITECTURE.md +190 -0
- package/COMPONENT_CONVERSION_ANALYSIS.md +371 -0
- package/CONTEXT_VARIANT_GUIDE.md +425 -0
- package/LAZY_LOADING_GUIDE.md +534 -0
- package/LICENSE +201 -0
- package/PLAYBACK_EXAMPLES.md +658 -0
- package/PLAYBACK_GUIDE.md +402 -0
- package/README.md +43 -0
- package/REGISTRY_SUMMARY.md +638 -0
- package/SUFFERING_ECONOMICS.md +346 -0
- package/SUFFERING_ECONOMICS.zip +0 -0
- package/VIEW_REGISTRATION_GUIDE.md +795 -0
- package/dist/composables/index.cjs +1321 -0
- package/dist/composables/index.cjs.map +1 -0
- package/dist/composables/index.d.cts +6 -0
- package/dist/composables/index.d.ts +6 -0
- package/dist/composables/index.js +1276 -0
- package/dist/composables/index.js.map +1 -0
- package/dist/index-CMLU1hKL.d.ts +374 -0
- package/dist/index-CSDjhiKV.d.cts +374 -0
- package/dist/index.cjs +1534 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +93 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +1463 -0
- package/dist/index.js.map +1 -0
- package/dist/registry/index.cjs +522 -0
- package/dist/registry/index.cjs.map +1 -0
- package/dist/registry/index.d.cts +46 -0
- package/dist/registry/index.d.ts +46 -0
- package/dist/registry/index.js +487 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/types/index.cjs +19 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +203 -0
- package/dist/types/index.d.ts +203 -0
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.cjs +217 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +47 -0
- package/dist/utils/index.d.ts +47 -0
- package/dist/utils/index.js +188 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/view-loader-dD86QAlQ.d.cts +121 -0
- package/dist/view-loader-ii8AxiQR.d.ts +121 -0
- package/package.json +64 -0
- package/publish.log +0 -0
- package/src/composables/index.ts +59 -0
- package/src/composables/use-image-loader.ts +315 -0
- package/src/composables/use-transaction-playback.ts +396 -0
- package/src/composables/use-transaction-view.ts +309 -0
- package/src/config/lukso-config.ts +171 -0
- package/src/examples/context-aware-views.ts +300 -0
- package/src/examples/example-views.ts +197 -0
- package/src/examples/lit-plus-rn-strategy.ts +448 -0
- package/src/examples/lsp7-custom-matcher-examples.ts +174 -0
- package/src/examples/opt-in-context-examples.ts +372 -0
- package/src/examples/optimized-view-definitions.ts +408 -0
- package/src/examples/vue-to-lit-compilation.ts +358 -0
- package/src/hooks/useAddressQuery.ts +135 -0
- package/src/hooks/useAddressResolution.ts +437 -0
- package/src/index.ts +36 -0
- package/src/registry/index.ts +13 -0
- package/src/registry/view-loader.ts +305 -0
- package/src/registry/view-registry.ts +135 -0
- package/src/types/index.ts +16 -0
- package/src/types/view-contexts.ts +72 -0
- package/src/types/view-matching.ts +173 -0
- package/src/utils/context-matcher.ts +165 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/view-matcher.ts +338 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +34 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { DecoderResult } from '@lukso/transaction-decoder'
|
|
2
|
+
import type {
|
|
3
|
+
ViewContext,
|
|
4
|
+
ViewContextRequirements,
|
|
5
|
+
ViewVariant,
|
|
6
|
+
} from '../types/view-contexts'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extended matching that includes context and variant awareness
|
|
10
|
+
*/
|
|
11
|
+
export interface ContextAwareMatchOptions {
|
|
12
|
+
framework: 'vue' | 'react' | 'react-native' | 'lit'
|
|
13
|
+
context: ViewContext // WHERE to display
|
|
14
|
+
variant?: ViewVariant // HOW to display (optional)
|
|
15
|
+
contextRequirements?: Partial<ViewContextRequirements>
|
|
16
|
+
minScore?: number
|
|
17
|
+
maxMatches?: number
|
|
18
|
+
includeFrameworkConfig?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extended criteria weights that include context and variant matching
|
|
23
|
+
*/
|
|
24
|
+
export const CONTEXT_AWARE_WEIGHTS = {
|
|
25
|
+
recordType: 0.2, // What transaction type
|
|
26
|
+
context: 0.15, // WHERE to display (feed, approval, etc.)
|
|
27
|
+
variant: 0.15, // HOW to display (collapsed, minimal, etc.)
|
|
28
|
+
customMatcher: 0.15, // Custom logic (e.g., LSP7 decimals check)
|
|
29
|
+
functionName: 0.12,
|
|
30
|
+
standard: 0.1,
|
|
31
|
+
contractAddress: 0.08,
|
|
32
|
+
contextRequirements: 0.03, // Bonus for meeting specific requirements
|
|
33
|
+
chainId: 0.02,
|
|
34
|
+
} as const
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if context criteria match
|
|
38
|
+
* Context = WHERE to display (feed, approval, list, etc.)
|
|
39
|
+
*/
|
|
40
|
+
export function doesContextMatch(
|
|
41
|
+
requestedContext: ViewContext,
|
|
42
|
+
viewContext: ViewContext | ViewContext[] | undefined
|
|
43
|
+
): boolean {
|
|
44
|
+
if (!viewContext) return false
|
|
45
|
+
|
|
46
|
+
if (Array.isArray(viewContext)) {
|
|
47
|
+
return viewContext.includes(requestedContext)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return viewContext === requestedContext
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if variant criteria match
|
|
55
|
+
* Variant = HOW to display (collapsed, minimal, full, etc.)
|
|
56
|
+
*/
|
|
57
|
+
export function doesVariantMatch(
|
|
58
|
+
requestedVariant: ViewVariant,
|
|
59
|
+
viewVariant: ViewVariant | ViewVariant[] | undefined
|
|
60
|
+
): boolean {
|
|
61
|
+
if (!viewVariant) return false
|
|
62
|
+
|
|
63
|
+
if (Array.isArray(viewVariant)) {
|
|
64
|
+
return viewVariant.includes(requestedVariant)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return viewVariant === requestedVariant
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if context requirements are compatible
|
|
72
|
+
*/
|
|
73
|
+
export function areContextRequirementsCompatible(
|
|
74
|
+
requested: Partial<ViewContextRequirements> = {},
|
|
75
|
+
viewRequirements: Partial<ViewContextRequirements> = {}
|
|
76
|
+
): boolean {
|
|
77
|
+
// Check detail level compatibility
|
|
78
|
+
if (requested.detailLevel && viewRequirements.detailLevel) {
|
|
79
|
+
const detailOrder = ['minimal', 'summary', 'full', 'debug']
|
|
80
|
+
const requestedLevel = detailOrder.indexOf(requested.detailLevel)
|
|
81
|
+
const viewLevel = detailOrder.indexOf(viewRequirements.detailLevel)
|
|
82
|
+
|
|
83
|
+
// View should provide at least the requested detail level
|
|
84
|
+
if (viewLevel < requestedLevel) return false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check height constraints
|
|
88
|
+
if (requested.maxHeight && viewRequirements.maxHeight) {
|
|
89
|
+
if (viewRequirements.maxHeight > requested.maxHeight) return false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check action requirements
|
|
93
|
+
if (
|
|
94
|
+
requested.showActions === true &&
|
|
95
|
+
viewRequirements.showActions === false
|
|
96
|
+
) {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check security emphasis
|
|
101
|
+
if (
|
|
102
|
+
requested.emphasizeSecurity === true &&
|
|
103
|
+
viewRequirements.emphasizeSecurity === false
|
|
104
|
+
) {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return true
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Example usage in actual matching function
|
|
113
|
+
*/
|
|
114
|
+
export function calculateContextAwareScore(
|
|
115
|
+
transaction: DecoderResult,
|
|
116
|
+
view: any, // Extended ViewDefinition with context
|
|
117
|
+
options: ContextAwareMatchOptions
|
|
118
|
+
): { score: number; matchedCriteria: string[] } {
|
|
119
|
+
const { criteria } = view
|
|
120
|
+
const matchedCriteria: string[] = []
|
|
121
|
+
let totalWeight = 0
|
|
122
|
+
let matchedWeight = 0
|
|
123
|
+
|
|
124
|
+
// Standard criteria matching (existing logic)
|
|
125
|
+
// ... recordType, functionName, etc.
|
|
126
|
+
|
|
127
|
+
// Context matching (WHERE to display)
|
|
128
|
+
if (criteria.context !== undefined) {
|
|
129
|
+
totalWeight += CONTEXT_AWARE_WEIGHTS.context
|
|
130
|
+
if (doesContextMatch(options.context, criteria.context)) {
|
|
131
|
+
matchedCriteria.push('context')
|
|
132
|
+
matchedWeight += CONTEXT_AWARE_WEIGHTS.context
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Variant matching (HOW to display)
|
|
137
|
+
if (criteria.variant !== undefined && options.variant !== undefined) {
|
|
138
|
+
totalWeight += CONTEXT_AWARE_WEIGHTS.variant
|
|
139
|
+
if (doesVariantMatch(options.variant, criteria.variant)) {
|
|
140
|
+
matchedCriteria.push('variant')
|
|
141
|
+
matchedWeight += CONTEXT_AWARE_WEIGHTS.variant
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Context requirements matching
|
|
146
|
+
if (criteria.contextRequirements !== undefined) {
|
|
147
|
+
totalWeight += CONTEXT_AWARE_WEIGHTS.contextRequirements
|
|
148
|
+
if (
|
|
149
|
+
areContextRequirementsCompatible(
|
|
150
|
+
options.contextRequirements,
|
|
151
|
+
criteria.contextRequirements
|
|
152
|
+
)
|
|
153
|
+
) {
|
|
154
|
+
matchedCriteria.push('contextRequirements')
|
|
155
|
+
matchedWeight += CONTEXT_AWARE_WEIGHTS.contextRequirements
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (totalWeight === 0) return { score: 0, matchedCriteria: [] }
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
score: matchedWeight / totalWeight,
|
|
163
|
+
matchedCriteria,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import type { DecoderResult } from '@lukso/transaction-decoder'
|
|
2
|
+
import type {
|
|
3
|
+
DecoderRecordForSelection,
|
|
4
|
+
ViewDefinition,
|
|
5
|
+
ViewMatch,
|
|
6
|
+
ViewMatchCriteria,
|
|
7
|
+
ViewMatchOptions,
|
|
8
|
+
} from '../types/view-matching'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Weights for different matching criteria (higher = more important)
|
|
12
|
+
* Context is optional - only weighted when both view and request specify context
|
|
13
|
+
*/
|
|
14
|
+
export const CRITERIA_WEIGHTS = {
|
|
15
|
+
recordType: 0.25, // Most specific to transaction type
|
|
16
|
+
functionName: 0.2, // Function-level specificity
|
|
17
|
+
context: 0.2, // Context specificity (when opt-in)
|
|
18
|
+
standard: 0.15, // LSP standard specificity
|
|
19
|
+
contractAddress: 0.1, // Contract-specific logic
|
|
20
|
+
contextRequirements: 0.05, // Context requirement matching
|
|
21
|
+
chainId: 0.03, // Network-specific behavior
|
|
22
|
+
customMatcher: 0.02, // Custom logic (lowest weight due to unpredictability)
|
|
23
|
+
} as const
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Calculate how well a view matches a transaction and context
|
|
27
|
+
*
|
|
28
|
+
* Context matching is opt-in:
|
|
29
|
+
* - If view specifies context criteria, request MUST provide matching context
|
|
30
|
+
* - If view has no context criteria, it matches any context (universal view)
|
|
31
|
+
* - If request has no context, context-specific views are deprioritized
|
|
32
|
+
*
|
|
33
|
+
* Note: For batch operations (executeBatch, setDataBatch, etc.), this matches
|
|
34
|
+
* against the parent transaction. Future enhancement could include matching
|
|
35
|
+
* against child transactions for more granular view selection.
|
|
36
|
+
*/
|
|
37
|
+
export function calculateMatchScore(
|
|
38
|
+
transaction: DecoderResult,
|
|
39
|
+
view: ViewDefinition,
|
|
40
|
+
options?: Pick<ViewMatchOptions, 'context' | 'contextRequirements'>
|
|
41
|
+
): { score: number; matchedCriteria: (keyof ViewMatchCriteria)[] } {
|
|
42
|
+
const { criteria } = view
|
|
43
|
+
const matchedCriteria: (keyof ViewMatchCriteria)[] = []
|
|
44
|
+
let totalWeight = 0
|
|
45
|
+
let matchedWeight = 0
|
|
46
|
+
|
|
47
|
+
// Check each criterion and accumulate scores
|
|
48
|
+
for (const [criterion, weight] of Object.entries(CRITERIA_WEIGHTS)) {
|
|
49
|
+
const criterionKey = criterion as keyof ViewMatchCriteria
|
|
50
|
+
const criterionValue = criteria[criterionKey]
|
|
51
|
+
|
|
52
|
+
if (criterionValue === undefined) continue
|
|
53
|
+
|
|
54
|
+
totalWeight += weight
|
|
55
|
+
|
|
56
|
+
// Special handling for customMatcher - use match count
|
|
57
|
+
if (
|
|
58
|
+
criterionKey === 'customMatcher' &&
|
|
59
|
+
typeof criterionValue === 'function'
|
|
60
|
+
) {
|
|
61
|
+
try {
|
|
62
|
+
const matchCount = criterionValue(transaction)
|
|
63
|
+
if (matchCount > 0) {
|
|
64
|
+
matchedCriteria.push(criterionKey)
|
|
65
|
+
// Normalize match count to 0-1 range (cap at 10 conditions)
|
|
66
|
+
const normalizedScore = Math.min(matchCount / 10, 1)
|
|
67
|
+
matchedWeight += weight * normalizedScore
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.warn('Custom matcher threw error:', error)
|
|
71
|
+
}
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
doesCriterionMatch(transaction, criterionKey, criterionValue, options)
|
|
77
|
+
) {
|
|
78
|
+
matchedCriteria.push(criterionKey)
|
|
79
|
+
matchedWeight += weight
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Handle context opt-in logic
|
|
84
|
+
const viewHasContext = criteria.context !== undefined
|
|
85
|
+
const requestHasContext = options?.context !== undefined
|
|
86
|
+
|
|
87
|
+
if (viewHasContext && !requestHasContext) {
|
|
88
|
+
// View requires specific context, but none provided - penalize heavily
|
|
89
|
+
matchedWeight *= 0.1 // Reduce score by 90%
|
|
90
|
+
} else if (!viewHasContext && requestHasContext) {
|
|
91
|
+
// Universal view matching specific context - slight penalty to prefer context-specific views
|
|
92
|
+
matchedWeight *= 0.9 // Reduce score by 10%
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// If no criteria are defined, this view doesn't match
|
|
96
|
+
if (totalWeight === 0) return { score: 0, matchedCriteria: [] }
|
|
97
|
+
|
|
98
|
+
// Calculate final score (0-1)
|
|
99
|
+
const score = matchedWeight / totalWeight
|
|
100
|
+
|
|
101
|
+
return { score, matchedCriteria }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if a specific criterion matches the transaction
|
|
106
|
+
*/
|
|
107
|
+
function doesCriterionMatch(
|
|
108
|
+
transaction: DecoderResult,
|
|
109
|
+
criterion: keyof ViewMatchCriteria,
|
|
110
|
+
criterionValue: any,
|
|
111
|
+
options?: Pick<ViewMatchOptions, 'context' | 'contextRequirements'>
|
|
112
|
+
): boolean {
|
|
113
|
+
switch (criterion) {
|
|
114
|
+
case 'recordType':
|
|
115
|
+
return matchesStringOrArray(
|
|
116
|
+
(transaction as DecoderRecordForSelection).recordType,
|
|
117
|
+
criterionValue
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
case 'functionName':
|
|
121
|
+
return matchesStringOrArray(
|
|
122
|
+
(transaction as DecoderRecordForSelection).functionName,
|
|
123
|
+
criterionValue
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
case 'standard':
|
|
127
|
+
return matchesStringOrArray(
|
|
128
|
+
(transaction as DecoderRecordForSelection).standard,
|
|
129
|
+
criterionValue
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
case 'contractAddress':
|
|
133
|
+
return matchesStringOrArray(
|
|
134
|
+
(transaction as DecoderRecordForSelection).contractAddress,
|
|
135
|
+
criterionValue
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
case 'chainId':
|
|
139
|
+
return matchesNumberOrArray(transaction.chainId, criterionValue)
|
|
140
|
+
|
|
141
|
+
case 'context':
|
|
142
|
+
if (!options?.context) return false // No context provided, can't match
|
|
143
|
+
return matchesStringOrArray(options.context, criterionValue)
|
|
144
|
+
|
|
145
|
+
case 'contextRequirements':
|
|
146
|
+
if (!options?.contextRequirements || !criterionValue) return false
|
|
147
|
+
return areContextRequirementsCompatible(
|
|
148
|
+
options.contextRequirements,
|
|
149
|
+
criterionValue
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
case 'customMatcher':
|
|
153
|
+
if (typeof criterionValue === 'function') {
|
|
154
|
+
try {
|
|
155
|
+
const matchCount = criterionValue(transaction)
|
|
156
|
+
// Match if any conditions matched (> 0)
|
|
157
|
+
// The actual count contributes to the score in calculateMatchScore
|
|
158
|
+
return matchCount > 0
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.warn('Custom matcher threw error:', error)
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return false
|
|
165
|
+
|
|
166
|
+
default:
|
|
167
|
+
return false
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Helper to match string values against string or array criteria
|
|
173
|
+
*/
|
|
174
|
+
function matchesStringOrArray(
|
|
175
|
+
value: string | undefined,
|
|
176
|
+
criteria: string | string[]
|
|
177
|
+
): boolean {
|
|
178
|
+
if (!value || !criteria) return false
|
|
179
|
+
|
|
180
|
+
if (Array.isArray(criteria)) {
|
|
181
|
+
return criteria.includes(value)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return value === criteria
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Helper to match number values against number or array criteria
|
|
189
|
+
*/
|
|
190
|
+
function matchesNumberOrArray(
|
|
191
|
+
value: number | undefined,
|
|
192
|
+
criteria: number | number[]
|
|
193
|
+
): boolean {
|
|
194
|
+
if (value === undefined || criteria === undefined) return false
|
|
195
|
+
|
|
196
|
+
if (Array.isArray(criteria)) {
|
|
197
|
+
return criteria.includes(value)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return value === criteria
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check if context requirements are compatible
|
|
205
|
+
*/
|
|
206
|
+
function areContextRequirementsCompatible(
|
|
207
|
+
requested: any,
|
|
208
|
+
viewRequirements: any
|
|
209
|
+
): boolean {
|
|
210
|
+
// Simple compatibility check - can be extended
|
|
211
|
+
if (requested.detailLevel && viewRequirements.detailLevel) {
|
|
212
|
+
const detailOrder = ['minimal', 'summary', 'full', 'debug']
|
|
213
|
+
const requestedLevel = detailOrder.indexOf(requested.detailLevel)
|
|
214
|
+
const viewLevel = detailOrder.indexOf(viewRequirements.detailLevel)
|
|
215
|
+
if (viewLevel < requestedLevel) return false
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (requested.maxHeight && viewRequirements.maxHeight) {
|
|
219
|
+
if (viewRequirements.maxHeight > requested.maxHeight) return false
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return true
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Calculate native framework priority bonus
|
|
227
|
+
*
|
|
228
|
+
* Native framework implementations (Vue, React Native) get priority over Lit fallbacks:
|
|
229
|
+
* - Vue: +0.15 bonus when view has Vue implementation
|
|
230
|
+
* - React Native (rn): +0.15 bonus when view has RN implementation
|
|
231
|
+
* - Lit: NO bonus (Lit is the universal fallback)
|
|
232
|
+
*
|
|
233
|
+
* This ensures that when both native and Lit views exist for the same transaction:
|
|
234
|
+
* - Vue app prefers Vue view (Vue score + 0.15 > Lit score)
|
|
235
|
+
* - React Native app prefers RN view (RN score + 0.15 > Lit score)
|
|
236
|
+
* - Other frameworks use Lit view (universal compatibility)
|
|
237
|
+
*/
|
|
238
|
+
function calculateNativeFrameworkBonus(
|
|
239
|
+
view: ViewDefinition,
|
|
240
|
+
requestedFramework: 'lit' | 'vue' | 'rn'
|
|
241
|
+
): number {
|
|
242
|
+
// Lit gets no bonus - it's the universal fallback
|
|
243
|
+
if (requestedFramework === 'lit') {
|
|
244
|
+
return 0
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Vue gets bonus if view has Vue implementation
|
|
248
|
+
if (requestedFramework === 'vue' && view.frameworks.vue) {
|
|
249
|
+
return 0.15
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// React Native gets bonus if view has RN implementation
|
|
253
|
+
if (requestedFramework === 'rn' && view.frameworks.rn) {
|
|
254
|
+
return 0.15
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return 0
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Find and score all matching views for a transaction
|
|
262
|
+
*
|
|
263
|
+
* Views are matched and sorted by:
|
|
264
|
+
* 1. Match score (how well criteria match the transaction)
|
|
265
|
+
* 2. Native framework bonus (Vue/RN get +0.15, Lit gets 0)
|
|
266
|
+
* 3. View priority (manually set priority)
|
|
267
|
+
*
|
|
268
|
+
* This ensures that native framework views are preferred when available,
|
|
269
|
+
* while Lit serves as a universal fallback.
|
|
270
|
+
*/
|
|
271
|
+
export function findMatchingViews(
|
|
272
|
+
transaction: DecoderResult,
|
|
273
|
+
views: ViewDefinition[],
|
|
274
|
+
options: ViewMatchOptions
|
|
275
|
+
): ViewMatch[] {
|
|
276
|
+
const matches: ViewMatch[] = []
|
|
277
|
+
const minScore = options.minScore ?? 0
|
|
278
|
+
|
|
279
|
+
for (const view of views) {
|
|
280
|
+
// Skip if framework is not supported
|
|
281
|
+
if (!view.frameworks[options.framework]) {
|
|
282
|
+
continue
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const { score, matchedCriteria } = calculateMatchScore(transaction, view, {
|
|
286
|
+
context: options.context,
|
|
287
|
+
contextRequirements: options.contextRequirements,
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
// Skip if score is below threshold
|
|
291
|
+
if (score < minScore) {
|
|
292
|
+
continue
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Apply native framework bonus (Vue/RN only, NOT Lit)
|
|
296
|
+
const frameworkBonus = calculateNativeFrameworkBonus(
|
|
297
|
+
view,
|
|
298
|
+
options.framework
|
|
299
|
+
)
|
|
300
|
+
const finalScore = Math.min(1, score + frameworkBonus)
|
|
301
|
+
|
|
302
|
+
const match: ViewMatch = {
|
|
303
|
+
view,
|
|
304
|
+
score: finalScore,
|
|
305
|
+
matchedCriteria,
|
|
306
|
+
frameworkConfig: options.includeFrameworkConfig
|
|
307
|
+
? view.frameworks[options.framework]
|
|
308
|
+
: undefined,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
matches.push(match)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Sort by score (descending), then by priority (descending)
|
|
315
|
+
matches.sort((a, b) => {
|
|
316
|
+
if (a.score !== b.score) {
|
|
317
|
+
return b.score - a.score
|
|
318
|
+
}
|
|
319
|
+
return b.view.priority - a.view.priority
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
// Limit results if requested
|
|
323
|
+
if (options.maxMatches) {
|
|
324
|
+
return matches.slice(0, options.maxMatches)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return matches
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* TODO: Future enhancement for batch operations
|
|
332
|
+
*
|
|
333
|
+
* For batch operations like executeBatch, setDataBatch, we might want to:
|
|
334
|
+
* 1. Match against the batch container (current behavior)
|
|
335
|
+
* 2. Also match against child transactions for more specific views
|
|
336
|
+
* 3. Allow views to specify whether they handle batches vs individual items
|
|
337
|
+
* 4. Support composite views that render both batch info + child items
|
|
338
|
+
*/
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"allowSyntheticDefaultImports": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"outDir": "./dist",
|
|
13
|
+
"rootDir": "./src",
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["dist", "node_modules", "src/examples"]
|
|
20
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup'
|
|
2
|
+
|
|
3
|
+
export default defineConfig((options) => ({
|
|
4
|
+
entry: {
|
|
5
|
+
index: 'src/index.ts',
|
|
6
|
+
'types/index': 'src/types/index.ts',
|
|
7
|
+
'composables/index': 'src/composables/index.ts',
|
|
8
|
+
'registry/index': 'src/registry/index.ts',
|
|
9
|
+
'utils/index': 'src/utils/index.ts',
|
|
10
|
+
},
|
|
11
|
+
format: ['esm', 'cjs'],
|
|
12
|
+
target: 'es2022',
|
|
13
|
+
dts: true,
|
|
14
|
+
sourcemap: true,
|
|
15
|
+
// Only clean on initial build, not during watch mode
|
|
16
|
+
clean: !options?.watch,
|
|
17
|
+
splitting: false,
|
|
18
|
+
ignoreWatch: [
|
|
19
|
+
'dist/**',
|
|
20
|
+
'node_modules/**',
|
|
21
|
+
'**/.turbo/**',
|
|
22
|
+
'**/*.test.*',
|
|
23
|
+
'**/*.spec.*',
|
|
24
|
+
'**/__tests__/**',
|
|
25
|
+
'**/README.md',
|
|
26
|
+
'**/.git/**',
|
|
27
|
+
],
|
|
28
|
+
external: ['@lukso/transaction-decoder', '@tanstack/query-core'],
|
|
29
|
+
onSuccess: async () => {
|
|
30
|
+
console.log(
|
|
31
|
+
`📦 Build complete ${options?.watch ? '(watch mode - DTS preserved)' : '(build mode)'}`
|
|
32
|
+
)
|
|
33
|
+
},
|
|
34
|
+
}))
|