@lukso/transaction-view-core 0.3.1-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.
Files changed (62) hide show
  1. package/.turbo/turbo-build.log +33 -0
  2. package/CHANGELOG.md +104 -0
  3. package/LICENSE +201 -0
  4. package/README.md +264 -0
  5. package/dist/index.cjs +4573 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +493 -0
  8. package/dist/index.d.ts +493 -0
  9. package/dist/index.js +4534 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/shared/index.cjs +2480 -0
  12. package/dist/shared/index.cjs.map +1 -0
  13. package/dist/shared/index.d.cts +887 -0
  14. package/dist/shared/index.d.ts +887 -0
  15. package/dist/shared/index.js +2437 -0
  16. package/dist/shared/index.js.map +1 -0
  17. package/dist/styles/index.cjs +58 -0
  18. package/dist/styles/index.cjs.map +1 -0
  19. package/dist/styles/index.d.cts +9 -0
  20. package/dist/styles/index.d.ts +9 -0
  21. package/dist/styles/index.js +31 -0
  22. package/dist/styles/index.js.map +1 -0
  23. package/package.json +57 -0
  24. package/publish.log +0 -0
  25. package/src/components/asset-card.ts +426 -0
  26. package/src/components/contract-card.ts +238 -0
  27. package/src/components/description-view.ts +605 -0
  28. package/src/components/index.ts +20 -0
  29. package/src/components/info-panel.ts +437 -0
  30. package/src/components/permissions-block.ts +138 -0
  31. package/src/components/transaction-playback.ts +429 -0
  32. package/src/components/universal-token-view.ts +281 -0
  33. package/src/declaration.d.ts +4 -0
  34. package/src/examples/INTEGRATION_GUIDE.md +240 -0
  35. package/src/examples/TransactionContentWithReplacement.vue +269 -0
  36. package/src/index.ts +38 -0
  37. package/src/registry/index.ts +31 -0
  38. package/src/registry/lit-view-loader.ts +191 -0
  39. package/src/shared/address-display.ts +370 -0
  40. package/src/shared/address-view.ts +385 -0
  41. package/src/shared/custom-select.ts +164 -0
  42. package/src/shared/function-name-view.ts +89 -0
  43. package/src/shared/gas-provider-view.ts +218 -0
  44. package/src/shared/icon-view.ts +288 -0
  45. package/src/shared/image-list.ts +261 -0
  46. package/src/shared/image-loader-controller.ts +160 -0
  47. package/src/shared/index.ts +34 -0
  48. package/src/shared/load-web-components.ts +56 -0
  49. package/src/shared/notification-view.ts +132 -0
  50. package/src/shared/progress-view.ts +86 -0
  51. package/src/shared/safe-custom-element.ts +35 -0
  52. package/src/shared/scroll-to-bottom-button.ts +55 -0
  53. package/src/shared/signal-controller.ts +124 -0
  54. package/src/shared/transaction-view-controller.ts +345 -0
  55. package/src/shared/url-view.ts +129 -0
  56. package/src/shared/value-view.ts +510 -0
  57. package/src/styles/index.ts +28 -0
  58. package/src/styles/styles.css +1 -0
  59. package/src/vue-entry.ts +15 -0
  60. package/tailwind.config.cjs +9 -0
  61. package/tsconfig.json +22 -0
  62. package/tsup.config.ts +90 -0
@@ -0,0 +1,429 @@
1
+ import type { DecoderResult } from '@lukso/transaction-decoder'
2
+ import {
3
+ createTransactionPlayback,
4
+ type PlaybackIndexResult,
5
+ type TransactionPlaybackState,
6
+ type ViewContext,
7
+ type ViewVariant,
8
+ } from '@lukso/transaction-view-headless'
9
+ import { css, html, LitElement, type PropertyValues } from 'lit'
10
+ import { property, state } from 'lit/decorators.js'
11
+ import { createRef, type Ref, ref } from 'lit/directives/ref.js'
12
+ import { when } from 'lit/directives/when.js'
13
+ import { getGlobalLitLoader } from '../registry/lit-view-loader'
14
+ import { safeCustomElement } from '../shared/safe-custom-element'
15
+
16
+ /**
17
+ * Lit Transaction Playback Component
18
+ *
19
+ * Provides index-based rendering of transactions with support for batch navigation
20
+ * and context-aware view matching.
21
+ *
22
+ * **Single Transaction:**
23
+ * - index="0" → renders the transaction
24
+ * - index="1" → renders the transaction (same)
25
+ * - index="2" → null/error
26
+ *
27
+ * **Batch Transaction:**
28
+ * - index="0" → renders batch summary/expandable view
29
+ * - index="1" → renders first child transaction
30
+ * - index="2" → renders second child transaction
31
+ * - etc.
32
+ *
33
+ * **Context & Variant:**
34
+ * - context: WHERE the transaction is displayed (approval, feed, list, detail, etc.)
35
+ * - variant: HOW the transaction should be rendered (full, collapsed, minimal, etc.)
36
+ *
37
+ * @example
38
+ * ```html
39
+ * <!-- Activity feed: compact view -->
40
+ * <transaction-playback
41
+ * .transaction="${transaction}"
42
+ * index="1"
43
+ * context="feed"
44
+ * variant="compact"
45
+ * ></transaction-playback>
46
+ *
47
+ * <!-- Approval flow: full detailed view -->
48
+ * <transaction-playback
49
+ * .transaction="${transaction}"
50
+ * index="1"
51
+ * context="approval"
52
+ * variant="full"
53
+ * ></transaction-playback>
54
+ *
55
+ * <!-- Batch summary: collapsed view -->
56
+ * <transaction-playback
57
+ * .transaction="${transaction}"
58
+ * index="0"
59
+ * context="batch-summary"
60
+ * variant="collapsed"
61
+ * ></transaction-playback>
62
+ * ```
63
+ */
64
+ @safeCustomElement('transaction-playback')
65
+ export class TransactionPlayback extends LitElement {
66
+ /** The root transaction to display */
67
+ @property({ type: Object })
68
+ transaction?: DecoderResult
69
+
70
+ /** Index to display (0, 1, 2, ...) */
71
+ @property({ type: Number })
72
+ index = 1 // Default to index 1 (main transaction, skips batch summary)
73
+
74
+ /** Fallback view ID if no match found */
75
+ @property({ type: String })
76
+ fallbackViewId = 'universal-execute'
77
+
78
+ /** Context: WHERE the transaction is displayed */
79
+ @property({ type: String })
80
+ context?: ViewContext
81
+
82
+ /** Variant: HOW the transaction should be rendered */
83
+ @property({ type: String })
84
+ variant?: ViewVariant
85
+
86
+ /** Show debug information */
87
+ @property({ type: Boolean })
88
+ debug = false
89
+
90
+ @state()
91
+ private playbackState?: TransactionPlaybackState
92
+
93
+ @state()
94
+ private currentResult?: PlaybackIndexResult
95
+
96
+ @state()
97
+ private isLoading = false
98
+
99
+ @state()
100
+ private error?: string
101
+
102
+ @state()
103
+ private loadedComponent?: HTMLElement
104
+
105
+ private componentContainerRef: Ref<HTMLDivElement> = createRef()
106
+ private loader = getGlobalLitLoader()
107
+
108
+ static styles = css`
109
+ :host {
110
+ display: block;
111
+ font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
112
+ }
113
+
114
+ .playback-container {
115
+ position: relative;
116
+ }
117
+
118
+ .loading-state {
119
+ display: flex;
120
+ flex-direction: column;
121
+ align-items: center;
122
+ justify-content: center;
123
+ padding: 60px 20px;
124
+ color: #6b7280;
125
+ }
126
+
127
+ .loading-spinner {
128
+ font-size: 32px;
129
+ margin-bottom: 16px;
130
+ animation: spin 2s linear infinite;
131
+ }
132
+
133
+ @keyframes spin {
134
+ from {
135
+ transform: rotate(0deg);
136
+ }
137
+ to {
138
+ transform: rotate(360deg);
139
+ }
140
+ }
141
+
142
+ .loading-text {
143
+ font-size: 16px;
144
+ font-weight: 500;
145
+ }
146
+
147
+ .error-state {
148
+ display: flex;
149
+ flex-direction: column;
150
+ align-items: center;
151
+ justify-content: center;
152
+ padding: 60px 20px;
153
+ color: #dc2626;
154
+ background: #fef2f2;
155
+ border: 1px solid #fecaca;
156
+ border-radius: 12px;
157
+ margin: 20px;
158
+ }
159
+
160
+ .error-icon {
161
+ font-size: 48px;
162
+ margin-bottom: 16px;
163
+ }
164
+
165
+ .error-title {
166
+ font-size: 18px;
167
+ font-weight: 600;
168
+ margin-bottom: 8px;
169
+ }
170
+
171
+ .error-message {
172
+ font-size: 14px;
173
+ text-align: center;
174
+ line-height: 1.4;
175
+ }
176
+
177
+ .debug-panel {
178
+ margin-top: 16px;
179
+ padding: 12px;
180
+ background: #f9fafb;
181
+ border: 1px solid #e5e7eb;
182
+ border-radius: 8px;
183
+ font-size: 12px;
184
+ font-family: 'Monaco', 'Menlo', monospace;
185
+ }
186
+
187
+ .debug-title {
188
+ font-weight: 600;
189
+ margin-bottom: 8px;
190
+ color: #374151;
191
+ }
192
+
193
+ .debug-item {
194
+ display: flex;
195
+ gap: 8px;
196
+ margin-bottom: 4px;
197
+ }
198
+
199
+ .debug-label {
200
+ font-weight: 500;
201
+ color: #6b7280;
202
+ }
203
+
204
+ .debug-value {
205
+ color: #111827;
206
+ }
207
+
208
+ .debug-valid {
209
+ color: #059669;
210
+ }
211
+
212
+ .debug-invalid {
213
+ color: #dc2626;
214
+ }
215
+
216
+ /* Component container for dynamically loaded content */
217
+ .component-container {
218
+ display: contents;
219
+ }
220
+ `
221
+
222
+ protected willUpdate(changedProperties: PropertyValues): void {
223
+ // Reload when transaction, index, context, or variant changes
224
+ if (
225
+ changedProperties.has('transaction') ||
226
+ changedProperties.has('index') ||
227
+ changedProperties.has('context') ||
228
+ changedProperties.has('variant')
229
+ ) {
230
+ this.loadView()
231
+ }
232
+ }
233
+
234
+ protected updated(changedProperties: PropertyValues): void {
235
+ // Mount component after render
236
+ if (this.loadedComponent && this.componentContainerRef.value) {
237
+ // Clear existing content
238
+ this.componentContainerRef.value.innerHTML = ''
239
+ // Append the new component
240
+ this.componentContainerRef.value.appendChild(this.loadedComponent)
241
+ }
242
+ }
243
+
244
+ private async loadView() {
245
+ if (!this.transaction) {
246
+ this.error = 'No transaction provided'
247
+ return
248
+ }
249
+
250
+ this.isLoading = true
251
+ this.error = undefined
252
+ this.loadedComponent = undefined
253
+
254
+ try {
255
+ // Create playback state
256
+ this.playbackState = createTransactionPlayback(this.transaction, {
257
+ framework: 'lit',
258
+ fallbackViewId: this.fallbackViewId,
259
+ context: this.context,
260
+ variant: this.variant,
261
+ })
262
+
263
+ // Get view at current index
264
+ this.currentResult = this.playbackState.getViewAtIndex(this.index)
265
+
266
+ // Validate index
267
+ if (!this.currentResult.indexInfo.isValid) {
268
+ throw new Error(
269
+ `Invalid index ${this.index}. Valid indices: ${this.playbackState.batchInfo.availableIndices.join(', ')}`
270
+ )
271
+ }
272
+
273
+ // Check if we have a match
274
+ if (!this.currentResult.match) {
275
+ throw new Error(`No view found for transaction at index ${this.index}`)
276
+ }
277
+
278
+ // Load the component
279
+ const componentClass = await this.loader.loadFromMatch(
280
+ this.currentResult.match
281
+ )
282
+
283
+ // Create component instance with transaction data
284
+ this.loadedComponent = this.loader.createInstance(componentClass, {
285
+ transaction: this.currentResult.transaction,
286
+ address: (this.currentResult.transaction as any).address,
287
+ value: (this.currentResult.transaction as any).value,
288
+ enhancedInfo: (this.currentResult.transaction as any).enhancedInfo,
289
+ title: this.generateTitle(this.currentResult),
290
+ standard: (this.currentResult.transaction as any).standard,
291
+ })
292
+
293
+ this.isLoading = false
294
+ } catch (err) {
295
+ this.error =
296
+ err instanceof Error ? err.message : 'Failed to load transaction view'
297
+ this.isLoading = false
298
+ console.error('Transaction playback error:', err)
299
+ }
300
+ }
301
+
302
+ private generateTitle(result: PlaybackIndexResult): string {
303
+ if (result.indexInfo.isBatchSummary) {
304
+ const childCount = result.batchInfo.childCount
305
+ return `Batch Operation (${childCount} ${childCount === 1 ? 'transaction' : 'transactions'})`
306
+ }
307
+
308
+ const transaction = result.transaction
309
+ if (!transaction) return 'Transaction'
310
+
311
+ const resultType = transaction.resultType
312
+
313
+ if (!resultType) return 'Transaction'
314
+
315
+ switch (resultType) {
316
+ case 'setData':
317
+ case 'setDataBatch':
318
+ return 'Set Data'
319
+ case 'execute':
320
+ case 'executeBatch':
321
+ return 'Execute'
322
+ case 'create':
323
+ return 'Contract Deployment'
324
+ default:
325
+ if (resultType.includes('transfer')) {
326
+ return 'Transfer'
327
+ }
328
+ return resultType.charAt(0).toUpperCase() + resultType.slice(1)
329
+ }
330
+ }
331
+
332
+ private renderDebugPanel() {
333
+ if (!this.debug || !this.playbackState || !this.currentResult) {
334
+ return html``
335
+ }
336
+
337
+ return html`
338
+ <div class="debug-panel">
339
+ <div class="debug-title">🐛 Playback Debug Info</div>
340
+ <div class="debug-item">
341
+ <span class="debug-label">Requested Index:</span>
342
+ <span class="debug-value">${this.currentResult.indexInfo.requested}</span>
343
+ </div>
344
+ <div class="debug-item">
345
+ <span class="debug-label">Is Valid:</span>
346
+ <span class="${this.currentResult.indexInfo.isValid ? 'debug-valid' : 'debug-invalid'}">
347
+ ${this.currentResult.indexInfo.isValid ? '✓ Yes' : '✗ No'}
348
+ </span>
349
+ </div>
350
+ <div class="debug-item">
351
+ <span class="debug-label">Is Batch:</span>
352
+ <span class="debug-value">
353
+ ${this.playbackState.isBatch ? 'Yes' : 'No'}
354
+ </span>
355
+ </div>
356
+ <div class="debug-item">
357
+ <span class="debug-label">Is Batch Summary:</span>
358
+ <span class="debug-value">
359
+ ${this.currentResult.indexInfo.isBatchSummary ? 'Yes' : 'No'}
360
+ </span>
361
+ </div>
362
+ ${when(
363
+ this.currentResult.indexInfo.childIndex != null,
364
+ () => html`
365
+ <div class="debug-item">
366
+ <span class="debug-label">Child Index:</span>
367
+ <span class="debug-value">${this.currentResult!.indexInfo.childIndex}</span>
368
+ </div>
369
+ `
370
+ )}
371
+ <div class="debug-item">
372
+ <span class="debug-label">Available Indices:</span>
373
+ <span class="debug-value">
374
+ [${this.playbackState!.batchInfo.availableIndices.join(', ')}]
375
+ </span>
376
+ </div>
377
+ ${when(
378
+ this.currentResult.match,
379
+ () => html`
380
+ <div class="debug-item">
381
+ <span class="debug-label">Matched View:</span>
382
+ <span class="debug-value">${this.currentResult!.match!.view.id}</span>
383
+ </div>
384
+ `
385
+ )}
386
+ </div>
387
+ `
388
+ }
389
+
390
+ render() {
391
+ // Loading state
392
+ if (this.isLoading) {
393
+ return html`
394
+ <div class="loading-state">
395
+ <div class="loading-spinner">⏳</div>
396
+ <div class="loading-text">Loading transaction view...</div>
397
+ </div>
398
+ `
399
+ }
400
+
401
+ // Error state
402
+ if (this.error) {
403
+ return html`
404
+ <div class="error-state">
405
+ <div class="error-icon">❌</div>
406
+ <div class="error-title">Error</div>
407
+ <div class="error-message">${this.error}</div>
408
+ </div>
409
+ ${this.renderDebugPanel()}
410
+ `
411
+ }
412
+
413
+ // Render loaded component
414
+ return html`
415
+ <div class="playback-container">
416
+ <div class="component-container" ${ref(this.componentContainerRef)}>
417
+ <!-- Component will be mounted here via updated() lifecycle -->
418
+ </div>
419
+ ${this.renderDebugPanel()}
420
+ </div>
421
+ `
422
+ }
423
+ }
424
+
425
+ declare global {
426
+ interface HTMLElementTagNameMap {
427
+ 'transaction-playback': TransactionPlayback
428
+ }
429
+ }
@@ -0,0 +1,281 @@
1
+ import { css, html, LitElement } from 'lit'
2
+ import { property, state } from 'lit/decorators.js'
3
+ import { when } from 'lit/directives/when.js'
4
+ import { safeCustomElement } from '../shared/safe-custom-element'
5
+ import './asset-card.js'
6
+ import './description-view.js'
7
+ import './info-panel.js'
8
+
9
+ // Import EnhancedInfo type
10
+ type EnhancedInfo = {
11
+ address: `0x${string}`
12
+ standard?: string
13
+ tokenId?: `0x${string}`
14
+ name?: string
15
+ __gqltype?: 'Profile' | 'Asset' | 'Token'
16
+ [key: string]: unknown
17
+ }
18
+
19
+ export interface UniversalTokenViewData {
20
+ /** Main transaction address key (ADDRESS or ADDRESS-TOKENID) */
21
+ address?: string
22
+ /** Value being transferred */
23
+ value?: bigint | string
24
+ /** Raw transaction object (DecoderResult) */
25
+ transaction?: any
26
+ /** Enhanced info from resolver */
27
+ enhancedInfo?: EnhancedInfo
28
+ /** Transaction type */
29
+ type?: 'transfer' | 'mint' | 'burn' | 'approve' | 'execute' | 'deploy'
30
+ /** Title text (e.g., "Transfer Token", "Mint Token") */
31
+ title?: string
32
+ /** Title icon name */
33
+ icon?: string
34
+ /** Standard tag text (e.g., "LSP7-DigitalAsset", "LSP8-IdentifiableDigitalAsset") */
35
+ standard?: string
36
+ /** Loading states */
37
+ loading?: {
38
+ asset?: boolean
39
+ metadata?: boolean
40
+ transaction?: boolean
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Universal token view component that combines all core components
46
+ * Can replace most individual Vue transaction views
47
+ */
48
+ @safeCustomElement('universal-token-view')
49
+ export class UniversalTokenView extends LitElement {
50
+ @property({ type: Object })
51
+ data: UniversalTokenViewData = {}
52
+
53
+ @state()
54
+ private _infoPanelOpen = false
55
+
56
+ static styles = css`
57
+ :host {
58
+ display: block;
59
+ font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
60
+ }
61
+
62
+ /* Match the original LSP7Transfer.vue structure exactly */
63
+ .universal-view {
64
+ padding: 16px; /* matches p-4 in Tailwind */
65
+ }
66
+
67
+ /* Title wrapper - matches original layout */
68
+ .title-wrapper {
69
+ display: flex;
70
+ justify-content: space-between;
71
+ }
72
+
73
+ /* Title section with icon - replicates Title.vue */
74
+ .title-section {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 4px;
78
+ font-size: 12px;
79
+ font-weight: 500;
80
+ color: var(--neutral-20, #374151);
81
+ }
82
+
83
+ .title-icon {
84
+ width: 16px;
85
+ height: 16px;
86
+ flex-shrink: 0;
87
+ margin-right: 4px;
88
+ }
89
+
90
+ /* LSP tag - matches original styling exactly */
91
+ .lsp-tag {
92
+ background: #eedde4;
93
+ border-radius: 8px;
94
+ height: 28px;
95
+ padding: 8px;
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ font-size: 12px;
100
+ font-weight: 500;
101
+ color: #374151;
102
+ }
103
+
104
+ /* Info panel spacing - matches mt-4 */
105
+ .info-panel {
106
+ margin-top: 16px;
107
+ }
108
+
109
+ .loading-state {
110
+ display: flex;
111
+ flex-direction: column;
112
+ align-items: center;
113
+ justify-content: center;
114
+ padding: 60px 20px;
115
+ color: #6b7280;
116
+ }
117
+
118
+ .loading-spinner {
119
+ font-size: 32px;
120
+ margin-bottom: 16px;
121
+ animation: spin 2s linear infinite;
122
+ }
123
+
124
+ @keyframes spin {
125
+ from { transform: rotate(0deg); }
126
+ to { transform: rotate(360deg); }
127
+ }
128
+
129
+ .loading-text {
130
+ font-size: 16px;
131
+ font-weight: 500;
132
+ }
133
+
134
+ .error-state {
135
+ display: flex;
136
+ flex-direction: column;
137
+ align-items: center;
138
+ justify-content: center;
139
+ padding: 60px 20px;
140
+ color: #dc2626;
141
+ background: #fef2f2;
142
+ border: 1px solid #fecaca;
143
+ border-radius: 12px;
144
+ margin: 20px;
145
+ }
146
+
147
+ .error-icon {
148
+ font-size: 48px;
149
+ margin-bottom: 16px;
150
+ }
151
+
152
+ .error-title {
153
+ font-size: 18px;
154
+ font-weight: 600;
155
+ margin-bottom: 8px;
156
+ }
157
+
158
+ .error-message {
159
+ font-size: 14px;
160
+ text-align: center;
161
+ line-height: 1.4;
162
+ }
163
+ `
164
+
165
+ private _handleInfoPanelToggle() {
166
+ this._infoPanelOpen = !this._infoPanelOpen
167
+ }
168
+
169
+ private _handleInfoPanelClose() {
170
+ this._infoPanelOpen = false
171
+ }
172
+
173
+ private _buildAssetCardData() {
174
+ const [contractAddress, tokenId] = (this.data.address || '').split('-')
175
+
176
+ return {
177
+ name: this.data.enhancedInfo?.name,
178
+ symbol:
179
+ (this.data.enhancedInfo as any)?.symbol ||
180
+ (this.data.enhancedInfo as any)?.lsp4TokenSymbol,
181
+ icon:
182
+ (this.data.enhancedInfo as any)?.icon ||
183
+ (this.data.enhancedInfo as any)?.images?.[0]?.url,
184
+ address: contractAddress,
185
+ tokenId: tokenId,
186
+ formattedTokenId: tokenId ? `${parseInt(tokenId, 16)}` : undefined,
187
+ amount: this.data.value?.toString(),
188
+ decimals: (this.data.enhancedInfo as any)?.decimals,
189
+ loadingImages: this.data.loading?.asset,
190
+ creator: (this.data.enhancedInfo as any)?.creator,
191
+ }
192
+ }
193
+
194
+ private _renderLoadingState() {
195
+ return html`
196
+ <div class="loading-state">
197
+ <div class="loading-spinner">⏳</div>
198
+ <div class="loading-text">Loading transaction details...</div>
199
+ </div>
200
+ `
201
+ }
202
+
203
+ private _renderErrorState(message: string) {
204
+ return html`
205
+ <div class="error-state">
206
+ <div class="error-icon">❌</div>
207
+ <div class="error-title">Error Loading Transaction</div>
208
+ <div class="error-message">${message}</div>
209
+ </div>
210
+ `
211
+ }
212
+
213
+ render() {
214
+ // Handle loading states
215
+ const isLoading =
216
+ this.data.loading?.transaction ||
217
+ (this.data.loading?.metadata && !this.data.enhancedInfo) ||
218
+ (this.data.loading?.asset && !this.data.address)
219
+
220
+ if (isLoading) {
221
+ return this._renderLoadingState()
222
+ }
223
+
224
+ // Handle error states
225
+ if (
226
+ !this.data.address &&
227
+ !this.data.enhancedInfo &&
228
+ !this.data.transaction
229
+ ) {
230
+ return this._renderErrorState('No transaction data provided')
231
+ }
232
+
233
+ // Replicate the exact structure from LSP7Transfer.vue
234
+ return html`
235
+ <div class="universal-view">
236
+ <!-- Title wrapper with title and LSP tag - exact match -->
237
+ <div class="title-wrapper">
238
+ <div class="title-section">
239
+ ${when(
240
+ this.data.icon,
241
+ () => html`
242
+ <svg class="title-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
243
+ <path d="M7 16l-4-4 4-4M17 8l4 4-4 4" />
244
+ </svg>
245
+ `
246
+ )}
247
+ ${this.data.title || 'Transaction'}
248
+ </div>
249
+ ${when(
250
+ this.data.standard,
251
+ () => html`<div class="lsp-tag">${this.data.standard}</div>`
252
+ )}
253
+ </div>
254
+
255
+ <!-- AssetCard replaces DigitalAssetInfo - exact same functionality -->
256
+ <asset-card
257
+ .data=${this._buildAssetCardData()}
258
+ size="medium"
259
+ ?show-info-button=${true}
260
+ @info-click=${this._handleInfoPanelToggle}
261
+ ></asset-card>
262
+
263
+ <!-- InfoPanel replaces BackPanelAsset with class="mt-4" spacing -->
264
+ <info-panel
265
+ class="info-panel"
266
+ address=${this.data.address || ''}
267
+ ?open=${this._infoPanelOpen}
268
+ standard=${this.data.standard || this.data.enhancedInfo?.standard || ''}
269
+ type=${this.data.type || 'transfer'}
270
+ @close=${this._handleInfoPanelClose}
271
+ ></info-panel>
272
+ </div>
273
+ `
274
+ }
275
+ }
276
+
277
+ declare global {
278
+ interface HTMLElementTagNameMap {
279
+ 'universal-token-view': UniversalTokenView
280
+ }
281
+ }
@@ -0,0 +1,4 @@
1
+ declare module '*?raw' {
2
+ const content: string
3
+ export default content
4
+ }