@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,191 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FrameworkViewConfig,
|
|
3
|
+
ViewMatch,
|
|
4
|
+
} from '@lukso/transaction-view-headless'
|
|
5
|
+
import { ViewLoadingError } from '@lukso/transaction-view-headless'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Lit component view loader
|
|
9
|
+
* Handles loading of Lit web components for transaction views
|
|
10
|
+
*/
|
|
11
|
+
export class LitViewLoader {
|
|
12
|
+
private loadedComponents = new Map<string, any>()
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Load a component from a view match
|
|
16
|
+
*/
|
|
17
|
+
async loadFromMatch(match: ViewMatch): Promise<any> {
|
|
18
|
+
if (!match.frameworkConfig) {
|
|
19
|
+
throw new ViewLoadingError(
|
|
20
|
+
`No framework config for view "${match.view.id}"`,
|
|
21
|
+
{ type: 'custom-element', tagName: '' },
|
|
22
|
+
{} as FrameworkViewConfig
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return this.loadComponent(match.frameworkConfig)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Load a component based on framework config
|
|
31
|
+
*/
|
|
32
|
+
async loadComponent(config: FrameworkViewConfig): Promise<any> {
|
|
33
|
+
const { loader, component } = config
|
|
34
|
+
|
|
35
|
+
// Check if already loaded
|
|
36
|
+
if (this.loadedComponents.has(component)) {
|
|
37
|
+
return this.loadedComponents.get(component)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
let loadedComponent: any
|
|
42
|
+
|
|
43
|
+
switch (loader.type) {
|
|
44
|
+
case 'custom-element':
|
|
45
|
+
loadedComponent = await this.loadCustomElement(loader.tagName)
|
|
46
|
+
break
|
|
47
|
+
case 'dynamic-import':
|
|
48
|
+
loadedComponent = await this.loadDynamicImport(loader.path)
|
|
49
|
+
break
|
|
50
|
+
case 'static-import':
|
|
51
|
+
loadedComponent = await this.loadStaticImport(loader.path)
|
|
52
|
+
break
|
|
53
|
+
case 'custom':
|
|
54
|
+
loadedComponent = await loader.loader(config)
|
|
55
|
+
break
|
|
56
|
+
default:
|
|
57
|
+
throw new ViewLoadingError(
|
|
58
|
+
`Unsupported loader type: ${(loader as any).type}`,
|
|
59
|
+
loader,
|
|
60
|
+
config
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Cache the loaded component
|
|
65
|
+
this.loadedComponents.set(component, loadedComponent)
|
|
66
|
+
return loadedComponent
|
|
67
|
+
} catch (error) {
|
|
68
|
+
throw new ViewLoadingError(
|
|
69
|
+
`Failed to load component "${component}"`,
|
|
70
|
+
loader,
|
|
71
|
+
config,
|
|
72
|
+
error instanceof Error ? error : new Error(String(error))
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Load a custom element (Web Component)
|
|
79
|
+
*/
|
|
80
|
+
private async loadCustomElement(tagName: string): Promise<any> {
|
|
81
|
+
// Check if the custom element is already defined
|
|
82
|
+
if (customElements.get(tagName)) {
|
|
83
|
+
return customElements.get(tagName)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// For our core components, they should be imported already
|
|
87
|
+
// This is a fallback to wait for definition
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const timeout = setTimeout(() => {
|
|
90
|
+
reject(
|
|
91
|
+
new Error(`Custom element "${tagName}" not found after 5s timeout`)
|
|
92
|
+
)
|
|
93
|
+
}, 5000)
|
|
94
|
+
|
|
95
|
+
customElements
|
|
96
|
+
.whenDefined(tagName)
|
|
97
|
+
.then(() => {
|
|
98
|
+
clearTimeout(timeout)
|
|
99
|
+
resolve(customElements.get(tagName))
|
|
100
|
+
})
|
|
101
|
+
.catch(reject)
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Load via dynamic import
|
|
107
|
+
*/
|
|
108
|
+
private async loadDynamicImport(
|
|
109
|
+
path: string | (() => Promise<any>)
|
|
110
|
+
): Promise<any> {
|
|
111
|
+
// Handle callback functions (lazy loading)
|
|
112
|
+
if (typeof path === 'function') {
|
|
113
|
+
const module = await path()
|
|
114
|
+
return module.default || module
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Handle string paths
|
|
118
|
+
const module = await import(/* @vite-ignore */ path)
|
|
119
|
+
return module.default || module
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Load via static import (not really possible at runtime, but for completeness)
|
|
124
|
+
*/
|
|
125
|
+
private async loadStaticImport(
|
|
126
|
+
path: string | (() => Promise<any>)
|
|
127
|
+
): Promise<any> {
|
|
128
|
+
// This would typically be handled at build time
|
|
129
|
+
// For runtime, fallback to dynamic import
|
|
130
|
+
return this.loadDynamicImport(path)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Create a component instance with data
|
|
135
|
+
*/
|
|
136
|
+
createInstance(componentClass: any, data: any): HTMLElement {
|
|
137
|
+
const element = new componentClass()
|
|
138
|
+
|
|
139
|
+
// Set data properties
|
|
140
|
+
Object.keys(data).forEach((key) => {
|
|
141
|
+
if (key in element) {
|
|
142
|
+
element[key] = data[key]
|
|
143
|
+
} else {
|
|
144
|
+
element.setAttribute(
|
|
145
|
+
key,
|
|
146
|
+
typeof data[key] === 'object'
|
|
147
|
+
? JSON.stringify(data[key])
|
|
148
|
+
: String(data[key])
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
return element
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Clear component cache
|
|
158
|
+
*/
|
|
159
|
+
clearCache(): void {
|
|
160
|
+
this.loadedComponents.clear()
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Global Lit view loader instance
|
|
166
|
+
*/
|
|
167
|
+
let globalLitLoader: LitViewLoader | null = null
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get the global Lit view loader
|
|
171
|
+
*/
|
|
172
|
+
export function getGlobalLitLoader(): LitViewLoader {
|
|
173
|
+
if (!globalLitLoader) {
|
|
174
|
+
globalLitLoader = new LitViewLoader()
|
|
175
|
+
}
|
|
176
|
+
return globalLitLoader
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Set a custom global Lit loader (useful for testing)
|
|
181
|
+
*/
|
|
182
|
+
export function setGlobalLitLoader(loader: LitViewLoader): void {
|
|
183
|
+
globalLitLoader = loader
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Reset the global Lit loader (mainly for testing)
|
|
188
|
+
*/
|
|
189
|
+
export function resetGlobalLitLoader(): void {
|
|
190
|
+
globalLitLoader = null
|
|
191
|
+
}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { Task } from '@lit/task'
|
|
2
|
+
import {
|
|
3
|
+
useAddressResolver,
|
|
4
|
+
useImageLoader,
|
|
5
|
+
} from '@lukso/transaction-view-headless'
|
|
6
|
+
import { css, html, LitElement } from 'lit'
|
|
7
|
+
import { customElement, property, state } from 'lit/decorators.js'
|
|
8
|
+
import { safeCustomElement } from './safe-custom-element'
|
|
9
|
+
|
|
10
|
+
// LUKSO web components are loaded dynamically via plugin
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Address data interface for display
|
|
14
|
+
*/
|
|
15
|
+
export interface AddressData {
|
|
16
|
+
address: string
|
|
17
|
+
name?: string
|
|
18
|
+
lsp4TokenName?: string
|
|
19
|
+
lsp4TokenSymbol?: string
|
|
20
|
+
baseAsset?: {
|
|
21
|
+
lsp4TokenName?: string
|
|
22
|
+
lsp4TokenSymbol?: string
|
|
23
|
+
}
|
|
24
|
+
profileImages?: any[]
|
|
25
|
+
avatar?: any[]
|
|
26
|
+
icon?: any[]
|
|
27
|
+
images?: any[]
|
|
28
|
+
__gqltype?: string
|
|
29
|
+
__identity?: string
|
|
30
|
+
identity?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Universal address display component with profile resolution
|
|
35
|
+
*
|
|
36
|
+
* Displays an address with profile image and name, supporting both
|
|
37
|
+
* Universal Profiles and contract addresses with proper styling.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```html
|
|
41
|
+
* <address-display
|
|
42
|
+
* address="0x1234..."
|
|
43
|
+
* .resolved="${resolvedData}"
|
|
44
|
+
* size="medium"
|
|
45
|
+
* show-copy-button
|
|
46
|
+
* ></address-display>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
@safeCustomElement('address-display')
|
|
50
|
+
export class AddressDisplay extends LitElement {
|
|
51
|
+
/**
|
|
52
|
+
* The address to display (required)
|
|
53
|
+
*/
|
|
54
|
+
@property() value!: string
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @deprecated Use `value` instead
|
|
58
|
+
*/
|
|
59
|
+
@property() address?: string
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolved address data (profile, images, etc.)
|
|
63
|
+
*/
|
|
64
|
+
@property({ type: Object }) resolved?: AddressData
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Display size
|
|
68
|
+
*/
|
|
69
|
+
@property() size: 'x-small' | 'small' | 'medium' | 'large' = 'small'
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Optional label to display above the address
|
|
73
|
+
*/
|
|
74
|
+
@property() label?: string
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether to show the copy button
|
|
78
|
+
*/
|
|
79
|
+
@property({ type: Boolean, attribute: 'show-copy-button' }) showCopyButton =
|
|
80
|
+
false
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Whether to show the full address or just the name
|
|
84
|
+
*/
|
|
85
|
+
@property({ type: Boolean, attribute: 'name-only' }) nameOnly = false
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Maximum width for the name display (in pixels)
|
|
89
|
+
*/
|
|
90
|
+
@property({ type: Number, attribute: 'max-width' }) maxWidth = 200
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get the effective address value (supports both `value` and legacy `address` props)
|
|
94
|
+
*/
|
|
95
|
+
private get effectiveAddress(): string {
|
|
96
|
+
return this.value || this.address || ''
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Internal state
|
|
100
|
+
@state() private imageData?: { src: string }
|
|
101
|
+
@state() private isLoadingImage = false
|
|
102
|
+
|
|
103
|
+
// Composables
|
|
104
|
+
private imageLoader = useImageLoader()
|
|
105
|
+
private addressResolver = useAddressResolver()
|
|
106
|
+
|
|
107
|
+
// Task for loading profile image
|
|
108
|
+
private imageTask = new Task(this, {
|
|
109
|
+
task: async ([resolved], { signal }) => {
|
|
110
|
+
if (!resolved) return null
|
|
111
|
+
|
|
112
|
+
this.isLoadingImage = true
|
|
113
|
+
try {
|
|
114
|
+
const image = await this.imageLoader.loadProfileImage(resolved, {
|
|
115
|
+
width: this.getSizePixels(),
|
|
116
|
+
height: this.getSizePixels(),
|
|
117
|
+
dpr: window.devicePixelRatio || 1,
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
return image || this.imageLoader.getFallbackImage(resolved)
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.warn('Failed to load address image:', error)
|
|
123
|
+
return this.imageLoader.getFallbackImage(resolved || {})
|
|
124
|
+
} finally {
|
|
125
|
+
this.isLoadingImage = false
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
args: () => [this.resolved],
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get pixel size for the current size setting
|
|
133
|
+
*/
|
|
134
|
+
private getSizePixels(): number {
|
|
135
|
+
switch (this.size) {
|
|
136
|
+
case 'x-small':
|
|
137
|
+
return 16
|
|
138
|
+
case 'small':
|
|
139
|
+
return 24
|
|
140
|
+
case 'medium':
|
|
141
|
+
return 32
|
|
142
|
+
case 'large':
|
|
143
|
+
return 48
|
|
144
|
+
default:
|
|
145
|
+
return 24
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get display name for the address
|
|
151
|
+
*/
|
|
152
|
+
private getDisplayName(): string {
|
|
153
|
+
if (!this.resolved) return 'Anonymous'
|
|
154
|
+
|
|
155
|
+
const computedName = this.addressResolver.formatDisplayName(this.resolved)
|
|
156
|
+
return computedName || 'Anonymous'
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Check if this is a contract address (shows square icon)
|
|
161
|
+
*/
|
|
162
|
+
private get isContract(): boolean {
|
|
163
|
+
return this.resolved
|
|
164
|
+
? this.imageLoader.isContractAddress(this.resolved)
|
|
165
|
+
: false
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get prefix for the address display
|
|
170
|
+
*/
|
|
171
|
+
private get addressPrefix(): string {
|
|
172
|
+
return this.resolved
|
|
173
|
+
? this.addressResolver.getAddressPrefix(this.resolved)
|
|
174
|
+
: '@'
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Check if should show name colors
|
|
179
|
+
*/
|
|
180
|
+
private get shouldShowNameColor(): boolean {
|
|
181
|
+
return this.resolved
|
|
182
|
+
? this.addressResolver.shouldShowNameColor(this.resolved)
|
|
183
|
+
: true
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Handle copy button click
|
|
188
|
+
*/
|
|
189
|
+
private handleCopy() {
|
|
190
|
+
if (navigator.clipboard) {
|
|
191
|
+
navigator.clipboard.writeText(this.effectiveAddress)
|
|
192
|
+
// Dispatch copy event for parent components
|
|
193
|
+
this.dispatchEvent(
|
|
194
|
+
new CustomEvent('address-copied', {
|
|
195
|
+
detail: { address: this.effectiveAddress },
|
|
196
|
+
bubbles: true,
|
|
197
|
+
})
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Handle address click
|
|
204
|
+
*/
|
|
205
|
+
private handleAddressClick() {
|
|
206
|
+
this.dispatchEvent(
|
|
207
|
+
new CustomEvent('address-click', {
|
|
208
|
+
detail: {
|
|
209
|
+
address: this.effectiveAddress,
|
|
210
|
+
resolved: this.resolved,
|
|
211
|
+
},
|
|
212
|
+
bubbles: true,
|
|
213
|
+
})
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
override render() {
|
|
218
|
+
const imageResult = this.imageTask.value
|
|
219
|
+
|
|
220
|
+
return html`
|
|
221
|
+
<div class="address-container">
|
|
222
|
+
${this.label ? html`<div class="label">${this.label}</div>` : ''}
|
|
223
|
+
|
|
224
|
+
<div class="address-display" @click=${this.handleAddressClick}>
|
|
225
|
+
<!-- Identity prefix (if present) -->
|
|
226
|
+
${
|
|
227
|
+
this.resolved?.__identity
|
|
228
|
+
? html`
|
|
229
|
+
<span class="identity">
|
|
230
|
+
${this.resolved.__identity}
|
|
231
|
+
<lukso-username
|
|
232
|
+
address=${this.resolved.identity}
|
|
233
|
+
size=${this.size}
|
|
234
|
+
max-width="200"
|
|
235
|
+
has-name-color="false"
|
|
236
|
+
has-address-color="false"
|
|
237
|
+
></lukso-username>
|
|
238
|
+
of
|
|
239
|
+
</span>
|
|
240
|
+
`
|
|
241
|
+
: ''
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
<!-- Profile image and name -->
|
|
245
|
+
<lukso-profile
|
|
246
|
+
size=${this.size}
|
|
247
|
+
profile-address=${this.effectiveAddress}
|
|
248
|
+
profile-url=${imageResult?.src || ''}
|
|
249
|
+
has-identicon
|
|
250
|
+
?is-square=${this.isContract}
|
|
251
|
+
></lukso-profile>
|
|
252
|
+
|
|
253
|
+
<!-- Address/Name display -->
|
|
254
|
+
${
|
|
255
|
+
this.nameOnly
|
|
256
|
+
? html`
|
|
257
|
+
<lukso-username
|
|
258
|
+
name=${this.getDisplayName()}
|
|
259
|
+
size=${this.size}
|
|
260
|
+
max-width=${this.maxWidth}
|
|
261
|
+
prefix=${this.addressPrefix}
|
|
262
|
+
?has-name-color=${this.shouldShowNameColor}
|
|
263
|
+
has-address-color="false"
|
|
264
|
+
></lukso-username>
|
|
265
|
+
`
|
|
266
|
+
: html`
|
|
267
|
+
<lukso-username
|
|
268
|
+
name=${this.getDisplayName()}
|
|
269
|
+
address=${!this.isContract ? this.effectiveAddress : undefined}
|
|
270
|
+
size=${this.size}
|
|
271
|
+
max-width=${this.maxWidth}
|
|
272
|
+
prefix=${this.addressPrefix}
|
|
273
|
+
?has-name-color=${this.shouldShowNameColor}
|
|
274
|
+
?has-address-color=${this.shouldShowNameColor}
|
|
275
|
+
></lukso-username>
|
|
276
|
+
`
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
<!-- Copy button -->
|
|
281
|
+
${
|
|
282
|
+
this.showCopyButton
|
|
283
|
+
? html`
|
|
284
|
+
<button
|
|
285
|
+
class="copy-button"
|
|
286
|
+
@click=${this.handleCopy}
|
|
287
|
+
title="Copy address"
|
|
288
|
+
>
|
|
289
|
+
📋
|
|
290
|
+
</button>
|
|
291
|
+
`
|
|
292
|
+
: ''
|
|
293
|
+
}
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
`
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
static override styles = css`
|
|
300
|
+
:host {
|
|
301
|
+
display: inline-block;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.address-container {
|
|
305
|
+
display: flex;
|
|
306
|
+
flex-direction: column;
|
|
307
|
+
gap: 4px;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.label {
|
|
311
|
+
font-size: 12px;
|
|
312
|
+
color: var(--neutral-60, #666);
|
|
313
|
+
font-weight: 500;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.address-display {
|
|
317
|
+
display: inline-flex;
|
|
318
|
+
align-items: center;
|
|
319
|
+
gap: 8px;
|
|
320
|
+
cursor: pointer;
|
|
321
|
+
padding: 4px;
|
|
322
|
+
border-radius: 4px;
|
|
323
|
+
transition: background-color 0.2s;
|
|
324
|
+
min-width: 200px;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.address-display:hover {
|
|
328
|
+
background-color: var(--neutral-95, #f5f5f5);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.identity {
|
|
332
|
+
font-size: 12px;
|
|
333
|
+
color: var(--neutral-60, #666);
|
|
334
|
+
margin-right: 4px;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.copy-button {
|
|
338
|
+
background: none;
|
|
339
|
+
border: 1px solid var(--neutral-80, #ccc);
|
|
340
|
+
border-radius: 4px;
|
|
341
|
+
padding: 2px 4px;
|
|
342
|
+
cursor: pointer;
|
|
343
|
+
font-size: 12px;
|
|
344
|
+
opacity: 0;
|
|
345
|
+
transition: opacity 0.2s;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.copy-button:hover {
|
|
349
|
+
background-color: var(--neutral-90, #eee);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.address-display:hover .copy-button {
|
|
353
|
+
opacity: 1;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
/* Ensure LUKSO web components are properly styled */
|
|
358
|
+
lukso-profile,
|
|
359
|
+
lukso-username {
|
|
360
|
+
display: inline-flex;
|
|
361
|
+
align-items: center;
|
|
362
|
+
}
|
|
363
|
+
`
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
declare global {
|
|
367
|
+
interface HTMLElementTagNameMap {
|
|
368
|
+
'address-display': AddressDisplay
|
|
369
|
+
}
|
|
370
|
+
}
|