@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,437 @@
1
+ import { Task } from '@lit/task'
2
+ import {
3
+ type ResolvedAddress,
4
+ useAddressResolver,
5
+ } from '@lukso/transaction-view-headless'
6
+ import { css, html, LitElement } from 'lit'
7
+ import { property, state } from 'lit/decorators.js'
8
+ import { classMap } from 'lit/directives/class-map.js'
9
+ import { repeat } from 'lit/directives/repeat.js'
10
+ import { when } from 'lit/directives/when.js'
11
+ import { safeCustomElement } from '../shared/safe-custom-element'
12
+ import '../shared/icon-view.js'
13
+ import '../shared/address-view.js'
14
+ import '../shared/image-list.js'
15
+
16
+ /**
17
+ * Expandable info panel for displaying detailed asset information
18
+ * Auto-resolves data from address or address-tokenId
19
+ */
20
+ @safeCustomElement('info-panel')
21
+ export class InfoPanel extends LitElement {
22
+ /**
23
+ * Address or address-tokenId to display info for
24
+ */
25
+ @property({ type: String })
26
+ address = ''
27
+
28
+ @property({ type: Boolean, attribute: 'open' })
29
+ open = false
30
+
31
+ @property({ type: String })
32
+ standard = ''
33
+
34
+ @property({ type: String })
35
+ type: 'transfer' | 'mint' | 'burn' | 'approve' = 'transfer'
36
+
37
+ @state()
38
+ private _isAnimating = false
39
+
40
+ @state()
41
+ private _resolvedData?: ResolvedAddress
42
+
43
+ private _resolutionTask = new Task(this, {
44
+ task: async ([address]) => {
45
+ if (!address) return null
46
+
47
+ const { resolveAddress } = useAddressResolver()
48
+ // Resolve the full address (including tokenId if present) to get token-specific data
49
+ const data = await resolveAddress(address as string)
50
+ this._resolvedData = data
51
+ return data
52
+ },
53
+ args: () => [this.address] as const,
54
+ })
55
+
56
+ private _extractBaseAddress(addressOrComposite: string): string {
57
+ return addressOrComposite.split('-')[0]
58
+ }
59
+
60
+ private _extractTokenId(addressOrComposite: string): string | undefined {
61
+ const parts = addressOrComposite.split('-')
62
+ return parts.length > 1 ? parts[1] : undefined
63
+ }
64
+
65
+ static styles = css`
66
+ :host {
67
+ display: block;
68
+ position: relative;
69
+ font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
70
+ }
71
+
72
+ .panel {
73
+ position: fixed;
74
+ top: 0;
75
+ left: 0;
76
+ right: 0;
77
+ bottom: 0;
78
+ background: rgba(0, 0, 0, 0.5);
79
+ z-index: 1000;
80
+ opacity: 0;
81
+ visibility: hidden;
82
+ transition: all 0.3s ease;
83
+ }
84
+
85
+ .panel.open {
86
+ opacity: 1;
87
+ visibility: visible;
88
+ }
89
+
90
+ .panel-content {
91
+ position: absolute;
92
+ top: 0;
93
+ right: 0;
94
+ width: 100%;
95
+ max-width: 400px;
96
+ height: 100%;
97
+ background: white;
98
+ transform: translateX(100%);
99
+ transition: transform 0.3s ease;
100
+ overflow-y: auto;
101
+ padding: 24px;
102
+ box-sizing: border-box;
103
+ }
104
+
105
+ .panel.open .panel-content {
106
+ transform: translateX(0);
107
+ }
108
+
109
+ .close-button {
110
+ position: absolute;
111
+ top: 20px;
112
+ right: 20px;
113
+ background: none;
114
+ border: none;
115
+ font-size: 24px;
116
+ cursor: pointer;
117
+ color: #6b7280;
118
+ padding: 4px;
119
+ border-radius: 4px;
120
+ transition: background-color 0.2s;
121
+ }
122
+
123
+ .close-button:hover {
124
+ background-color: #f3f4f6;
125
+ }
126
+
127
+ .header {
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ margin-bottom: 24px;
132
+ padding-top: 20px;
133
+ }
134
+
135
+ .header-icon {
136
+ margin-right: 12px;
137
+ }
138
+
139
+ .header-title {
140
+ font-size: 18px;
141
+ font-weight: 600;
142
+ color: #111827;
143
+ text-align: center;
144
+ }
145
+
146
+ .section {
147
+ margin-bottom: 20px;
148
+ padding: 16px;
149
+ background: #f9fafb;
150
+ border-radius: 12px;
151
+ border: 1px solid #e5e7eb;
152
+ }
153
+
154
+ .section-title {
155
+ font-size: 14px;
156
+ font-weight: 600;
157
+ color: #374151;
158
+ margin-bottom: 8px;
159
+ display: flex;
160
+ align-items: center;
161
+ gap: 8px;
162
+ }
163
+
164
+ .section-content {
165
+ font-size: 14px;
166
+ color: #6b7280;
167
+ word-break: break-all;
168
+ }
169
+
170
+ .copy-button {
171
+ background: none;
172
+ border: none;
173
+ cursor: pointer;
174
+ padding: 4px;
175
+ border-radius: 4px;
176
+ color: #6b7280;
177
+ transition: background-color 0.2s;
178
+ }
179
+
180
+ .copy-button:hover {
181
+ background-color: #e5e7eb;
182
+ }
183
+
184
+ .links-list {
185
+ list-style: none;
186
+ padding: 0;
187
+ margin: 0;
188
+ }
189
+
190
+ .link-item {
191
+ margin-bottom: 8px;
192
+ }
193
+
194
+ .link-item:last-child {
195
+ margin-bottom: 0;
196
+ }
197
+
198
+ .external-link {
199
+ display: inline-flex;
200
+ align-items: center;
201
+ gap: 6px;
202
+ color: #3b82f6;
203
+ text-decoration: none;
204
+ font-size: 14px;
205
+ padding: 6px 12px;
206
+ border-radius: 6px;
207
+ transition: background-color 0.2s;
208
+ }
209
+
210
+ .external-link:hover {
211
+ background-color: #eff6ff;
212
+ }
213
+
214
+ .description {
215
+ line-height: 1.6;
216
+ color: #374151;
217
+ }
218
+
219
+ .loading-placeholder {
220
+ width: 96px;
221
+ height: 96px;
222
+ background: #f3f4f6;
223
+ border-radius: 16px;
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ color: #9ca3af;
228
+ font-size: 32px;
229
+ }
230
+
231
+ @media (max-width: 640px) {
232
+ .panel-content {
233
+ max-width: 100%;
234
+ }
235
+ }
236
+ `
237
+
238
+ private _handleClose() {
239
+ this.dispatchEvent(new CustomEvent('close'))
240
+ }
241
+
242
+ private _copyToClipboard(text: string) {
243
+ if (navigator.clipboard) {
244
+ navigator.clipboard.writeText(text)
245
+ // Could dispatch a 'copied' event here for toast notifications
246
+ this.dispatchEvent(new CustomEvent('copied', { detail: { text } }))
247
+ }
248
+ }
249
+
250
+ private _renderHeader() {
251
+ const name =
252
+ this._resolvedData?.lsp4TokenName ||
253
+ this._resolvedData?.name ||
254
+ 'Digital Asset'
255
+
256
+ return html`
257
+ <div class="header">
258
+ ${when(
259
+ this.address,
260
+ () => html`
261
+ <icon-view
262
+ address="${this.address}"
263
+ size="large"
264
+ class="header-icon"
265
+ ></icon-view>
266
+ `,
267
+ () => html`
268
+ <div class="loading-placeholder">🪙</div>
269
+ `
270
+ )}
271
+ <div class="header-title">${name}</div>
272
+ </div>
273
+ `
274
+ }
275
+
276
+ private _renderContractAddress() {
277
+ const contractAddress = this._extractBaseAddress(this.address)
278
+ if (!contractAddress) return ''
279
+
280
+ return html`
281
+ <div class="section">
282
+ <div class="section-title">
283
+ 📄 Contract Address
284
+ <button
285
+ class="copy-button"
286
+ @click=${() => this._copyToClipboard(contractAddress)}
287
+ title="Copy to clipboard"
288
+ >
289
+ 📋
290
+ </button>
291
+ </div>
292
+ <div class="section-content">
293
+ <address-view
294
+ .address=${contractAddress}
295
+ format="truncated"
296
+ ></address-view>
297
+ </div>
298
+ </div>
299
+ `
300
+ }
301
+
302
+ private _renderTokenId() {
303
+ const tokenId = this._extractTokenId(this.address)
304
+ if (!tokenId) return ''
305
+
306
+ // Use formattedTokenId if available from resolved data, otherwise use raw tokenId
307
+ const formattedTokenId = (this._resolvedData as any)?.formattedTokenId
308
+ const displayTokenId = formattedTokenId
309
+ ? `#${formattedTokenId}` // Formatted gets # prefix
310
+ : tokenId || '' // Raw hex without # prefix
311
+
312
+ return html`
313
+ <div class="section">
314
+ <div class="section-title">
315
+ 🏷️ Token ID
316
+ <button
317
+ class="copy-button"
318
+ @click=${() => this._copyToClipboard(tokenId)}
319
+ title="Copy to clipboard"
320
+ >
321
+ 📋
322
+ </button>
323
+ </div>
324
+ <div class="section-content">${displayTokenId}</div>
325
+ </div>
326
+ `
327
+ }
328
+
329
+ private _renderDescription() {
330
+ const description = (this._resolvedData as any)?.description
331
+ if (!description) return ''
332
+
333
+ return html`
334
+ <div class="section">
335
+ <div class="section-title">📝 Description</div>
336
+ <div class="section-content description">${description}</div>
337
+ </div>
338
+ `
339
+ }
340
+
341
+ private _renderLinks() {
342
+ const links = (this._resolvedData as any)?.links || []
343
+ if (!links.length) return ''
344
+
345
+ return html`
346
+ <div class="section">
347
+ <div class="section-title">🔗 Links</div>
348
+ <ul class="links-list">
349
+ ${repeat(
350
+ links,
351
+ (link: any) => link.url,
352
+ (link: any) => html`
353
+ <li class="link-item">
354
+ <a
355
+ class="external-link"
356
+ href="${link.url}"
357
+ target="_blank"
358
+ rel="noopener noreferrer"
359
+ >
360
+ ${link.title || link.url}
361
+ <span>↗</span>
362
+ </a>
363
+ </li>
364
+ `
365
+ )}
366
+ </ul>
367
+ </div>
368
+ `
369
+ }
370
+
371
+ private _renderImages() {
372
+ const images = (this._resolvedData as any)?.images || []
373
+ if (!images.length) return ''
374
+
375
+ return html`
376
+ <div class="section">
377
+ <div class="section-title">🖼️ Images</div>
378
+ <image-list
379
+ address=${this.address}
380
+ image-hint="images"
381
+ size="medium"
382
+ ></image-list>
383
+ </div>
384
+ `
385
+ }
386
+
387
+ private _renderAssets() {
388
+ const assets = (this._resolvedData as any)?.assets || []
389
+ if (!assets.length) return ''
390
+
391
+ return html`
392
+ <div class="section">
393
+ <div class="section-title">📦 Assets</div>
394
+ <image-list
395
+ address=${this.address}
396
+ image-hint="assets"
397
+ size="medium"
398
+ ></image-list>
399
+ </div>
400
+ `
401
+ }
402
+
403
+ private _handleBackdropClick(e: Event) {
404
+ if (e.target === e.currentTarget) {
405
+ this._handleClose()
406
+ }
407
+ }
408
+
409
+ render() {
410
+ const panelClasses = {
411
+ panel: true,
412
+ open: this.open,
413
+ }
414
+
415
+ return html`
416
+ <div class=${classMap(panelClasses)} @click=${this._handleBackdropClick}>
417
+ <div class="panel-content" @click=${(e: Event) => e.stopPropagation()}>
418
+ <button class="close-button" @click=${this._handleClose}>×</button>
419
+
420
+ ${this._renderHeader()}
421
+ ${this._renderContractAddress()}
422
+ ${this._renderTokenId()}
423
+ ${this._renderDescription()}
424
+ ${this._renderLinks()}
425
+ ${this._renderImages()}
426
+ ${this._renderAssets()}
427
+ </div>
428
+ </div>
429
+ `
430
+ }
431
+ }
432
+
433
+ declare global {
434
+ interface HTMLElementTagNameMap {
435
+ 'info-panel': InfoPanel
436
+ }
437
+ }
@@ -0,0 +1,138 @@
1
+ import { html, nothing } from 'lit'
2
+ import { property } from 'lit/decorators.js'
3
+ import { safeCustomElement } from '../shared/safe-custom-element'
4
+ import { CoreLitElement } from '../styles'
5
+ import '../shared/load-web-components'
6
+
7
+ export type PermissionLevels = {
8
+ permissionName: string
9
+ type: string
10
+ icon?: string
11
+ isActive: boolean
12
+ isForward?: boolean
13
+ info: string
14
+ }
15
+
16
+ /**
17
+ * Permissions block card
18
+ *
19
+ * Lit/Web Component equivalent of the provided Vue PermissionsBlock.
20
+ * - Uses Tailwind utility classes for styling
21
+ * - Uses LUKSO web components where applicable (lukso-icon)
22
+ */
23
+ @safeCustomElement('permissions-block')
24
+ export class PermissionsBlock extends CoreLitElement {
25
+ /** Header icon name */
26
+ @property({ type: String }) icon: string = ''
27
+
28
+ /** Optional action icon name (reserved, not displayed yet) */
29
+ @property({ type: String, attribute: 'action-icon' }) actionIcon: string = ''
30
+
31
+ /** CSS color (e.g., var(--blue-75)) applied to icons */
32
+ @property({ type: String }) color: string = 'var(--neutral-60)'
33
+
34
+ /** Title text */
35
+ @property({ type: String }) title: string = ''
36
+
37
+ /** Description text */
38
+ @property({ type: String }) description: string = ''
39
+
40
+ /** Control identifier used by parent (informational only here) */
41
+ @property({ type: String }) control: string = ''
42
+
43
+ /** Permission levels to render */
44
+ @property({ type: Array }) permissions: PermissionLevels[] = []
45
+
46
+ override render() {
47
+ return html`
48
+ <div
49
+ class="mb-6 p-4 bg-[var(--neutral-100)] border border-[var(--neutral-90)] rounded-lg"
50
+ >
51
+ <div class="flex items-center mb-2">
52
+ ${
53
+ this.icon
54
+ ? html`<lukso-icon
55
+ name=${this.icon}
56
+ size="x-small"
57
+ color=${this.color}
58
+ ></lukso-icon>`
59
+ : nothing
60
+ }
61
+ <div class="ml-2 heading-inter-14-bold">${this.title}</div>
62
+ </div>
63
+
64
+ ${
65
+ this.description
66
+ ? html`<div class="text-[var(--neutral-40)] paragraph-inter-12-regular">
67
+ ${this.description}
68
+ </div>`
69
+ : nothing
70
+ }
71
+
72
+ <div class="h-px w-full bg-[var(--neutral-90)] mt-4"></div>
73
+
74
+ <slot></slot>
75
+
76
+ ${(this.permissions || []).map((p) => this.renderPermission(p))}
77
+ </div>
78
+ `
79
+ }
80
+
81
+ private renderPermission(p: PermissionLevels) {
82
+ const nameBase = (p.icon || p.permissionName || '').toLowerCase()
83
+ const activeIcon = `${nameBase}-activated`
84
+ const inactiveIcon = `${nameBase}-inactive`
85
+ return html`
86
+ <div class="mt-4 grid grid-cols-[40px,1fr,20px] gap-x-2 items-start">
87
+ <div class="relative w-10 h-10">
88
+ <svg-icon
89
+ class="active absolute bottom-0 opacity-0 z-[99] transition-opacity duration-200 ${
90
+ p.isActive ? 'opacity-100' : ''
91
+ }"
92
+ name=${activeIcon}
93
+ width="40"
94
+ height="40"
95
+ color=${this.color}
96
+ ></svg-icon>
97
+ <svg-icon
98
+ class="inactive absolute bottom-0 ${p.isActive ? 'opacity-0' : 'opacity-100'}"
99
+ name=${inactiveIcon}
100
+ width="40"
101
+ height="40"
102
+ color=${this.color}
103
+ ></svg-icon>
104
+ </div>
105
+
106
+ <div>
107
+ <span class="paragraph-inter-12-semi-bold ">${p.type}</span>
108
+ <div class="paragraph-inter-12-regular">${p.info}</div>
109
+ </div>
110
+
111
+ ${
112
+ p.isActive
113
+ ? html`<lukso-icon
114
+ name="complete-filled"
115
+ width="20"
116
+ height="20"
117
+ secondary-color="neutral-100"
118
+ color="green-54"
119
+ stroke-width="1.5"
120
+ ></lukso-icon>`
121
+ : html`<lukso-icon
122
+ name="cross-outline"
123
+ width="20"
124
+ height="20"
125
+ color="red-65"
126
+ stroke-width="1.5"
127
+ ></lukso-icon>`
128
+ }
129
+ </div>
130
+ `
131
+ }
132
+ }
133
+
134
+ declare global {
135
+ interface HTMLElementTagNameMap {
136
+ 'permissions-block': PermissionsBlock
137
+ }
138
+ }