@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.
- package/.turbo/turbo-build.log +33 -0
- package/CHANGELOG.md +104 -0
- package/LICENSE +201 -0
- package/README.md +264 -0
- package/dist/index.cjs +4573 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +493 -0
- package/dist/index.d.ts +493 -0
- package/dist/index.js +4534 -0
- package/dist/index.js.map +1 -0
- package/dist/shared/index.cjs +2480 -0
- package/dist/shared/index.cjs.map +1 -0
- package/dist/shared/index.d.cts +887 -0
- package/dist/shared/index.d.ts +887 -0
- package/dist/shared/index.js +2437 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/styles/index.cjs +58 -0
- package/dist/styles/index.cjs.map +1 -0
- package/dist/styles/index.d.cts +9 -0
- package/dist/styles/index.d.ts +9 -0
- package/dist/styles/index.js +31 -0
- package/dist/styles/index.js.map +1 -0
- package/package.json +57 -0
- package/publish.log +0 -0
- package/src/components/asset-card.ts +426 -0
- package/src/components/contract-card.ts +238 -0
- package/src/components/description-view.ts +605 -0
- package/src/components/index.ts +20 -0
- package/src/components/info-panel.ts +437 -0
- package/src/components/permissions-block.ts +138 -0
- package/src/components/transaction-playback.ts +429 -0
- package/src/components/universal-token-view.ts +281 -0
- package/src/declaration.d.ts +4 -0
- package/src/examples/INTEGRATION_GUIDE.md +240 -0
- package/src/examples/TransactionContentWithReplacement.vue +269 -0
- package/src/index.ts +38 -0
- package/src/registry/index.ts +31 -0
- package/src/registry/lit-view-loader.ts +191 -0
- package/src/shared/address-display.ts +370 -0
- package/src/shared/address-view.ts +385 -0
- package/src/shared/custom-select.ts +164 -0
- package/src/shared/function-name-view.ts +89 -0
- package/src/shared/gas-provider-view.ts +218 -0
- package/src/shared/icon-view.ts +288 -0
- package/src/shared/image-list.ts +261 -0
- package/src/shared/image-loader-controller.ts +160 -0
- package/src/shared/index.ts +34 -0
- package/src/shared/load-web-components.ts +56 -0
- package/src/shared/notification-view.ts +132 -0
- package/src/shared/progress-view.ts +86 -0
- package/src/shared/safe-custom-element.ts +35 -0
- package/src/shared/scroll-to-bottom-button.ts +55 -0
- package/src/shared/signal-controller.ts +124 -0
- package/src/shared/transaction-view-controller.ts +345 -0
- package/src/shared/url-view.ts +129 -0
- package/src/shared/value-view.ts +510 -0
- package/src/styles/index.ts +28 -0
- package/src/styles/styles.css +1 -0
- package/src/vue-entry.ts +15 -0
- package/tailwind.config.cjs +9 -0
- package/tsconfig.json +22 -0
- package/tsup.config.ts +90 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { html } from 'lit'
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
3
|
+
import { CoreLitElement } from '../styles'
|
|
4
|
+
import { safeCustomElement } from './safe-custom-element'
|
|
5
|
+
|
|
6
|
+
export type ProgressSegment = {
|
|
7
|
+
value: number
|
|
8
|
+
color?: string
|
|
9
|
+
title?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@safeCustomElement('progress-view')
|
|
13
|
+
export class ProgressView extends CoreLitElement {
|
|
14
|
+
@property({ type: Array }) data: ProgressSegment[] = []
|
|
15
|
+
@property() label?: string
|
|
16
|
+
@property() unit?: string
|
|
17
|
+
@property() address?: string
|
|
18
|
+
@property({ attribute: 'label-position' }) labelPosition: 'left' | 'right' =
|
|
19
|
+
'right'
|
|
20
|
+
|
|
21
|
+
private renderGasProviderLabel() {
|
|
22
|
+
return html`
|
|
23
|
+
${
|
|
24
|
+
this.label
|
|
25
|
+
? html`
|
|
26
|
+
<span class="progress-label-value">${this.label}</span>
|
|
27
|
+
${
|
|
28
|
+
this.unit
|
|
29
|
+
? html`<span class="progress-label-unit" data-testid="unit">${this.unit}</span>`
|
|
30
|
+
: ''
|
|
31
|
+
}
|
|
32
|
+
`
|
|
33
|
+
: html`
|
|
34
|
+
<slot></slot>
|
|
35
|
+
${
|
|
36
|
+
this.unit
|
|
37
|
+
? html`<span class="progress-label-unit" data-testid="unit">${this.unit}</span>`
|
|
38
|
+
: ''
|
|
39
|
+
}
|
|
40
|
+
`
|
|
41
|
+
}
|
|
42
|
+
`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private renderAddressLabel() {
|
|
46
|
+
return html`
|
|
47
|
+
<value-view
|
|
48
|
+
.value=${this.address}
|
|
49
|
+
.unit=${this.unit}
|
|
50
|
+
></value-view>
|
|
51
|
+
`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override render() {
|
|
55
|
+
const isError = (this.className || '').split(/\s+/).includes('error')
|
|
56
|
+
return html`
|
|
57
|
+
<div class="relative grid w-full ${this.className || ''}">
|
|
58
|
+
<div class="flex bg-neutral-95 h-6 rounded-lg relative col-[1] row-[1] overflow-hidden" style=${isError ? 'background-color: var(--red-65);' : ''} role="progressbar">
|
|
59
|
+
${this.data.map((seg, index) => {
|
|
60
|
+
const width = `${Math.max(0, Math.min(100, Number(seg.value || 0)))}%`
|
|
61
|
+
const backgroundColor = seg.color || 'var(--neutral-90)'
|
|
62
|
+
const title = seg.title || ''
|
|
63
|
+
return html`<div
|
|
64
|
+
class="h-full flex items-center justify-center text-[10px] font-semibold"
|
|
65
|
+
style=${`width:${width};background-color:${backgroundColor};`}
|
|
66
|
+
data-testid=${`value-${index}`}
|
|
67
|
+
>${title}</div>`
|
|
68
|
+
})}
|
|
69
|
+
</div>
|
|
70
|
+
<div class=${`flex items-center absolute h-6 rounded-lg overflow-visible w-full col-[1] row-[1] top-0 left-0 ${
|
|
71
|
+
this.labelPosition === 'left' ? 'justify-start' : 'justify-end'
|
|
72
|
+
}`} data-testid="progress-labels">
|
|
73
|
+
<div class="text-[13px] font-semibold text-white mix-blend-difference h-inherit flex items-center p-0 right-4 absolute" data-testid="label">
|
|
74
|
+
${this.renderGasProviderLabel()}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
`
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
declare global {
|
|
83
|
+
interface HTMLElementTagNameMap {
|
|
84
|
+
'progress-view': ProgressView
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSR-safe custom element decorator that prevents duplicate registration errors.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* The standard `@customElement()` decorator from Lit will throw an error if
|
|
7
|
+
* a component is registered twice. This can happen in SSR environments where:
|
|
8
|
+
* - Components are dynamically imported via multiple paths
|
|
9
|
+
* - HMR causes modules to be re-executed during development
|
|
10
|
+
* - Server and client bundles both try to register components
|
|
11
|
+
*
|
|
12
|
+
* ## Usage
|
|
13
|
+
*
|
|
14
|
+
* Use this instead of `@customElement()`:
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { safeCustomElement } from './safe-custom-element'
|
|
17
|
+
*
|
|
18
|
+
* @safeCustomElement('my-component')
|
|
19
|
+
* export class MyComponent extends LitElement {
|
|
20
|
+
* // ...
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @param tagName - The custom element tag name (must contain a hyphen)
|
|
25
|
+
* @returns A class decorator that safely registers the element
|
|
26
|
+
*/
|
|
27
|
+
export function safeCustomElement(tagName: string) {
|
|
28
|
+
return <T extends CustomElementConstructor>(classOrDescriptor: T): T => {
|
|
29
|
+
// Only register if not already defined
|
|
30
|
+
if (typeof customElements !== 'undefined' && !customElements.get(tagName)) {
|
|
31
|
+
customElements.define(tagName, classOrDescriptor)
|
|
32
|
+
}
|
|
33
|
+
return classOrDescriptor
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { html } from 'lit'
|
|
2
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
3
|
+
import { CoreLitElement } from '../styles'
|
|
4
|
+
import { safeCustomElement } from './safe-custom-element'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ScrollToBottomButton (core) - reusable scroll helper.
|
|
8
|
+
* Emits `scroll-to-bottom` when clicked.
|
|
9
|
+
*/
|
|
10
|
+
@safeCustomElement('scroll-to-bottom-button')
|
|
11
|
+
export class ScrollToBottomButton extends CoreLitElement {
|
|
12
|
+
/** Controls visibility */
|
|
13
|
+
@property({ type: Boolean, attribute: 'show' }) show = false
|
|
14
|
+
|
|
15
|
+
/** Icon color (CSS var or token) */
|
|
16
|
+
@property() color: string = 'neutral-20'
|
|
17
|
+
|
|
18
|
+
/** Icon size token */
|
|
19
|
+
@property({ attribute: 'icon-size' }) iconSize:
|
|
20
|
+
| 'x-small'
|
|
21
|
+
| 'small'
|
|
22
|
+
| 'medium'
|
|
23
|
+
| 'large' = 'medium'
|
|
24
|
+
|
|
25
|
+
/** Icon name (attribute: icon-name) */
|
|
26
|
+
@property({ attribute: 'icon-name' }) iconName: string = 'arrow-down-lg'
|
|
27
|
+
|
|
28
|
+
private handleClick() {
|
|
29
|
+
this.dispatchEvent(new CustomEvent('scroll-to-bottom', { bubbles: true }))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override render() {
|
|
33
|
+
return html`
|
|
34
|
+
<div class="h-14 w-[140px] self-center rounded-t-[120px] bg-[radial-gradient(ellipse_75px_52px_at_bottom_center,rgba(255,255,255,0.78)_0%,rgba(255,255,255,0)_112.5%)] flex justify-center items-center transition-opacity duration-700 ease-in-out ${
|
|
35
|
+
this.show ? 'opacity-100 z-10' : 'opacity-0 -z-10'
|
|
36
|
+
}">
|
|
37
|
+
<div class="flex justify-center items-center w-10 h-10 rounded-full border border-neutral-90 shadow-[0_0_16px_rgba(189,206,219,0.4)] bg-neutral-100 cursor-pointer animate-bounce" @click=${
|
|
38
|
+
this.handleClick
|
|
39
|
+
}>
|
|
40
|
+
<lukso-icon
|
|
41
|
+
name=${this.iconName}
|
|
42
|
+
size=${this.iconSize}
|
|
43
|
+
color=${this.color}
|
|
44
|
+
></lukso-icon>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
`
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
declare global {
|
|
52
|
+
interface HTMLElementTagNameMap {
|
|
53
|
+
'scroll-to-bottom-button': ScrollToBottomButton
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { Signal } from '@preact/signals-core'
|
|
2
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lit Reactive Controller for @preact/signals-core integration
|
|
6
|
+
* This controller allows Lit elements to reactively update when signals change
|
|
7
|
+
*/
|
|
8
|
+
export class SignalController<T = any> implements ReactiveController {
|
|
9
|
+
private host: ReactiveControllerHost
|
|
10
|
+
private signal: Signal<T>
|
|
11
|
+
private unsubscribe?: () => void
|
|
12
|
+
|
|
13
|
+
constructor(host: ReactiveControllerHost, signal: Signal<T>) {
|
|
14
|
+
this.host = host
|
|
15
|
+
this.signal = signal
|
|
16
|
+
host.addController(this)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
hostConnected() {
|
|
20
|
+
// In a real implementation, you would use signal.subscribe()
|
|
21
|
+
// For now, we'll use a polling approach
|
|
22
|
+
let lastValue = this.signal.value
|
|
23
|
+
|
|
24
|
+
const check = () => {
|
|
25
|
+
const currentValue = this.signal.value
|
|
26
|
+
if (currentValue !== lastValue) {
|
|
27
|
+
lastValue = currentValue
|
|
28
|
+
this.host.requestUpdate()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Set up polling
|
|
33
|
+
const interval = setInterval(check, 50) // 20fps updates
|
|
34
|
+
|
|
35
|
+
this.unsubscribe = () => clearInterval(interval)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
hostDisconnected() {
|
|
39
|
+
if (this.unsubscribe) {
|
|
40
|
+
this.unsubscribe()
|
|
41
|
+
this.unsubscribe = undefined
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get value(): T {
|
|
46
|
+
return this.signal.value
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create a new SignalController for a different signal
|
|
51
|
+
*/
|
|
52
|
+
static create<U>(
|
|
53
|
+
host: ReactiveControllerHost,
|
|
54
|
+
signal: Signal<U>
|
|
55
|
+
): SignalController<U> {
|
|
56
|
+
return new SignalController(host, signal)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Lit Reactive Controller for multiple signals
|
|
62
|
+
* Updates the host when any of the tracked signals change
|
|
63
|
+
*/
|
|
64
|
+
export class MultiSignalController implements ReactiveController {
|
|
65
|
+
private host: ReactiveControllerHost
|
|
66
|
+
private signals: Signal<any>[]
|
|
67
|
+
private unsubscribes: (() => void)[] = []
|
|
68
|
+
|
|
69
|
+
constructor(host: ReactiveControllerHost, signals: Signal<any>[]) {
|
|
70
|
+
this.host = host
|
|
71
|
+
this.signals = signals
|
|
72
|
+
host.addController(this)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
hostConnected() {
|
|
76
|
+
this.signals.forEach((signal, index) => {
|
|
77
|
+
let lastValue = signal.value
|
|
78
|
+
|
|
79
|
+
const check = () => {
|
|
80
|
+
const currentValue = signal.value
|
|
81
|
+
if (currentValue !== lastValue) {
|
|
82
|
+
lastValue = currentValue
|
|
83
|
+
this.host.requestUpdate()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const interval = setInterval(check, 50)
|
|
88
|
+
this.unsubscribes[index] = () => clearInterval(interval)
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
hostDisconnected() {
|
|
93
|
+
this.unsubscribes.forEach((unsubscribe) => unsubscribe())
|
|
94
|
+
this.unsubscribes = []
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getValue(index: number): any {
|
|
98
|
+
return this.signals[index]?.value
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getValues(): any[] {
|
|
102
|
+
return this.signals.map((signal) => signal.value)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Utility function to create a signal controller
|
|
108
|
+
*/
|
|
109
|
+
export function useSignal<T>(
|
|
110
|
+
host: ReactiveControllerHost,
|
|
111
|
+
signal: Signal<T>
|
|
112
|
+
): SignalController<T> {
|
|
113
|
+
return new SignalController(host, signal)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Utility function to create a multi-signal controller
|
|
118
|
+
*/
|
|
119
|
+
export function useSignals(
|
|
120
|
+
host: ReactiveControllerHost,
|
|
121
|
+
signals: Signal<any>[]
|
|
122
|
+
): MultiSignalController {
|
|
123
|
+
return new MultiSignalController(host, signals)
|
|
124
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import type { DecoderResult } from '@lukso/transaction-decoder'
|
|
2
|
+
import {
|
|
3
|
+
findAllTransactionViews,
|
|
4
|
+
findBestTransactionView,
|
|
5
|
+
type UseTransactionViewOptions,
|
|
6
|
+
useTransactionView as useTransactionViewHeadless,
|
|
7
|
+
type ViewMatch,
|
|
8
|
+
} from '@lukso/transaction-view-headless'
|
|
9
|
+
import type { Signal } from '@preact/signals-core'
|
|
10
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Lit controller state for transaction view
|
|
14
|
+
*/
|
|
15
|
+
export interface TransactionViewState {
|
|
16
|
+
matches: ViewMatch[]
|
|
17
|
+
bestMatch: ViewMatch | null
|
|
18
|
+
component: any
|
|
19
|
+
loading: boolean
|
|
20
|
+
error: Error | null
|
|
21
|
+
isResolvingAddresses: boolean
|
|
22
|
+
resolvedAddresses: Record<string, any>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Lit Reactive Controller for transaction view functionality
|
|
27
|
+
* Integrates the headless transaction view system with Lit elements
|
|
28
|
+
*/
|
|
29
|
+
export class TransactionViewController implements ReactiveController {
|
|
30
|
+
private host: ReactiveControllerHost
|
|
31
|
+
private headlessInstance: any = null
|
|
32
|
+
|
|
33
|
+
// Internal state
|
|
34
|
+
private _state: TransactionViewState = {
|
|
35
|
+
matches: [],
|
|
36
|
+
bestMatch: null,
|
|
37
|
+
component: null,
|
|
38
|
+
loading: false,
|
|
39
|
+
error: null,
|
|
40
|
+
isResolvingAddresses: false,
|
|
41
|
+
resolvedAddresses: {},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private syncInterval?: number
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
host: ReactiveControllerHost,
|
|
48
|
+
private transaction: DecoderResult,
|
|
49
|
+
private options: UseTransactionViewOptions
|
|
50
|
+
) {
|
|
51
|
+
this.host = host
|
|
52
|
+
host.addController(this)
|
|
53
|
+
this.initialize()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
hostConnected() {
|
|
57
|
+
// Set up periodic sync with headless instance
|
|
58
|
+
this.setupSync()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
hostDisconnected() {
|
|
62
|
+
// Cleanup sync
|
|
63
|
+
if (this.syncInterval) {
|
|
64
|
+
clearInterval(this.syncInterval)
|
|
65
|
+
this.syncInterval = undefined
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get state(): TransactionViewState {
|
|
70
|
+
return this._state
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get matches(): ViewMatch[] {
|
|
74
|
+
return this._state.matches
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get bestMatch(): ViewMatch | null {
|
|
78
|
+
return this._state.bestMatch
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get component(): any {
|
|
82
|
+
return this._state.component
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get isLoading(): boolean {
|
|
86
|
+
return this._state.loading
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get error(): Error | null {
|
|
90
|
+
return this._state.error
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get isResolvingAddresses(): boolean {
|
|
94
|
+
return this._state.isResolvingAddresses
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get resolvedAddresses(): Record<string, any> {
|
|
98
|
+
return this._state.resolvedAddresses
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Load a specific match
|
|
103
|
+
*/
|
|
104
|
+
async loadMatch(match: ViewMatch): Promise<any> {
|
|
105
|
+
if (!this.headlessInstance) {
|
|
106
|
+
throw new Error('Transaction view not initialized')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const result = await this.headlessInstance.loadMatch(match)
|
|
111
|
+
this.syncState()
|
|
112
|
+
return result
|
|
113
|
+
} catch (err) {
|
|
114
|
+
this.syncState()
|
|
115
|
+
throw err
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Load the best match
|
|
121
|
+
*/
|
|
122
|
+
async loadBestMatch(): Promise<any> {
|
|
123
|
+
if (!this.headlessInstance) {
|
|
124
|
+
throw new Error('Transaction view not initialized')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const result = await this.headlessInstance.loadBestMatch()
|
|
129
|
+
this.syncState()
|
|
130
|
+
return result
|
|
131
|
+
} catch (err) {
|
|
132
|
+
this.syncState()
|
|
133
|
+
throw err
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Refresh matches
|
|
139
|
+
*/
|
|
140
|
+
refreshMatches(): void {
|
|
141
|
+
if (!this.headlessInstance) {
|
|
142
|
+
console.warn('Transaction view not initialized, cannot refresh matches')
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.headlessInstance.refreshMatches()
|
|
147
|
+
this.syncState()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Resolve addresses
|
|
152
|
+
*/
|
|
153
|
+
async resolveAddresses(): Promise<void> {
|
|
154
|
+
if (!this.headlessInstance) {
|
|
155
|
+
throw new Error('Transaction view not initialized')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
await this.headlessInstance.resolveAddresses()
|
|
160
|
+
this.syncState()
|
|
161
|
+
} catch (err) {
|
|
162
|
+
this.syncState()
|
|
163
|
+
throw err
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Update transaction and options
|
|
169
|
+
*/
|
|
170
|
+
update(transaction: DecoderResult, options: UseTransactionViewOptions): void {
|
|
171
|
+
this.transaction = transaction
|
|
172
|
+
this.options = options
|
|
173
|
+
this.initialize()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get the best matching view without loading
|
|
178
|
+
*/
|
|
179
|
+
static findBestMatch(
|
|
180
|
+
transaction: DecoderResult,
|
|
181
|
+
framework: 'lit' | 'rn' = 'lit'
|
|
182
|
+
): ViewMatch | null {
|
|
183
|
+
return findBestTransactionView(transaction, framework)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get all matching views without loading
|
|
188
|
+
*/
|
|
189
|
+
static findAllMatches(
|
|
190
|
+
transaction: DecoderResult,
|
|
191
|
+
framework: 'lit' | 'rn' = 'lit'
|
|
192
|
+
): ViewMatch[] {
|
|
193
|
+
return findAllTransactionViews(transaction, framework)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private initialize(): void {
|
|
197
|
+
try {
|
|
198
|
+
this.headlessInstance = useTransactionViewHeadless(
|
|
199
|
+
this.transaction,
|
|
200
|
+
this.options
|
|
201
|
+
)
|
|
202
|
+
this.syncState()
|
|
203
|
+
} catch (err) {
|
|
204
|
+
this.updateState({
|
|
205
|
+
error:
|
|
206
|
+
err instanceof Error
|
|
207
|
+
? err
|
|
208
|
+
: new Error('Failed to initialize transaction view'),
|
|
209
|
+
})
|
|
210
|
+
console.error('Failed to initialize transaction view:', err)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private setupSync(): void {
|
|
215
|
+
// Sync immediately
|
|
216
|
+
this.syncState()
|
|
217
|
+
|
|
218
|
+
// Set up periodic sync
|
|
219
|
+
this.syncInterval = setInterval(() => {
|
|
220
|
+
this.syncState()
|
|
221
|
+
}, 100) as any // Type assertion for browser/node compatibility
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private syncState(): void {
|
|
225
|
+
if (!this.headlessInstance) return
|
|
226
|
+
|
|
227
|
+
this.updateState({
|
|
228
|
+
matches: this.headlessInstance.matches,
|
|
229
|
+
bestMatch: this.headlessInstance.bestMatch,
|
|
230
|
+
component: this.headlessInstance.component,
|
|
231
|
+
loading: this.headlessInstance.isLoading,
|
|
232
|
+
error: this.headlessInstance.error,
|
|
233
|
+
isResolvingAddresses: this.headlessInstance.isResolvingAddresses,
|
|
234
|
+
resolvedAddresses: this.headlessInstance.resolvedAddresses,
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private updateState(updates: Partial<TransactionViewState>): void {
|
|
239
|
+
this._state = { ...this._state, ...updates }
|
|
240
|
+
this.host.requestUpdate()
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Utility function to create a transaction view controller
|
|
246
|
+
*/
|
|
247
|
+
export function useTransactionView(
|
|
248
|
+
host: ReactiveControllerHost,
|
|
249
|
+
transaction: DecoderResult,
|
|
250
|
+
options: UseTransactionViewOptions
|
|
251
|
+
): TransactionViewController {
|
|
252
|
+
return new TransactionViewController(host, transaction, options)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Controller for working with transaction signals from the decoder
|
|
257
|
+
*/
|
|
258
|
+
export class TransactionSignalController<T = any>
|
|
259
|
+
implements ReactiveController
|
|
260
|
+
{
|
|
261
|
+
private host: ReactiveControllerHost
|
|
262
|
+
private signal: Signal<{
|
|
263
|
+
data: DecoderResult
|
|
264
|
+
loading: boolean
|
|
265
|
+
error?: string
|
|
266
|
+
}>
|
|
267
|
+
private unsubscribe?: () => void
|
|
268
|
+
|
|
269
|
+
private _data: DecoderResult | null = null
|
|
270
|
+
private _loading = false
|
|
271
|
+
private _error: string | null = null
|
|
272
|
+
|
|
273
|
+
constructor(
|
|
274
|
+
host: ReactiveControllerHost,
|
|
275
|
+
signal: Signal<{ data: DecoderResult; loading: boolean; error?: string }>
|
|
276
|
+
) {
|
|
277
|
+
this.host = host
|
|
278
|
+
this.signal = signal
|
|
279
|
+
host.addController(this)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
hostConnected() {
|
|
283
|
+
this.setupSignalSync()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
hostDisconnected() {
|
|
287
|
+
if (this.unsubscribe) {
|
|
288
|
+
this.unsubscribe()
|
|
289
|
+
this.unsubscribe = undefined
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
get data(): DecoderResult | null {
|
|
294
|
+
return this._data
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
get loading(): boolean {
|
|
298
|
+
return this._loading
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
get error(): string | null {
|
|
302
|
+
return this._error
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
get isReady(): boolean {
|
|
306
|
+
return !this._loading && this._data !== null && !this._error
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private setupSignalSync(): void {
|
|
310
|
+
const sync = () => {
|
|
311
|
+
const signalValue = this.signal.value
|
|
312
|
+
const newData = signalValue?.data || null
|
|
313
|
+
const newLoading = signalValue?.loading || false
|
|
314
|
+
const newError = signalValue?.error || null
|
|
315
|
+
|
|
316
|
+
if (
|
|
317
|
+
newData !== this._data ||
|
|
318
|
+
newLoading !== this._loading ||
|
|
319
|
+
newError !== this._error
|
|
320
|
+
) {
|
|
321
|
+
this._data = newData
|
|
322
|
+
this._loading = newLoading
|
|
323
|
+
this._error = newError
|
|
324
|
+
this.host.requestUpdate()
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Initial sync
|
|
329
|
+
sync()
|
|
330
|
+
|
|
331
|
+
// Set up periodic sync
|
|
332
|
+
const interval = setInterval(sync, 50)
|
|
333
|
+
this.unsubscribe = () => clearInterval(interval)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Utility function to create a transaction signal controller
|
|
339
|
+
*/
|
|
340
|
+
export function useTransactionSignal(
|
|
341
|
+
host: ReactiveControllerHost,
|
|
342
|
+
signal: Signal<{ data: DecoderResult; loading: boolean; error?: string }>
|
|
343
|
+
): TransactionSignalController {
|
|
344
|
+
return new TransactionSignalController(host, signal)
|
|
345
|
+
}
|