@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,129 @@
|
|
|
1
|
+
import { html } from 'lit'
|
|
2
|
+
import { customElement, property, state } from 'lit/decorators.js'
|
|
3
|
+
import { safeCustomElement } from './safe-custom-element'
|
|
4
|
+
import './load-web-components'
|
|
5
|
+
import { CoreLitElement } from '../styles'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* UrlView - display a URL with favicon/logo and human-friendly name
|
|
9
|
+
*
|
|
10
|
+
* Props:
|
|
11
|
+
* - url (required): full URL like https://example.com/path
|
|
12
|
+
* - size: x-small | small | medium | large
|
|
13
|
+
* - label?: optional label displayed above
|
|
14
|
+
* - showLogo: whether to show favicon/logo (default true)
|
|
15
|
+
* - logoUrl?: explicit logo URL to use instead of favicon
|
|
16
|
+
* - maxWidth: name truncation width in px
|
|
17
|
+
*/
|
|
18
|
+
@safeCustomElement('url-view')
|
|
19
|
+
export class UrlView extends CoreLitElement {
|
|
20
|
+
@property() url!: string
|
|
21
|
+
@property() size: 'x-small' | 'small' | 'medium' | 'large' = 'small'
|
|
22
|
+
@property() label?: string
|
|
23
|
+
@property({ type: Boolean, attribute: 'show-logo' }) showLogo = true
|
|
24
|
+
@property({ attribute: 'logo-url' }) logoUrl?: string
|
|
25
|
+
@property({ type: Number, attribute: 'max-width' }) maxWidth = 240
|
|
26
|
+
|
|
27
|
+
@state() private favicon?: string
|
|
28
|
+
@state() private displayName = ''
|
|
29
|
+
private faviconCandidates: string[] = []
|
|
30
|
+
private faviconIndex = 0
|
|
31
|
+
|
|
32
|
+
protected willUpdate(changed: Map<string, unknown>) {
|
|
33
|
+
if (changed.has('url') || changed.has('label') || changed.has('logoUrl')) {
|
|
34
|
+
this.computeMeta()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private computeMeta() {
|
|
39
|
+
try {
|
|
40
|
+
const u = new URL(this.url)
|
|
41
|
+
const origin = u.origin
|
|
42
|
+
this.faviconCandidates = []
|
|
43
|
+
if (this.logoUrl) this.faviconCandidates.push(this.logoUrl)
|
|
44
|
+
this.faviconCandidates.push(
|
|
45
|
+
`${origin}/favicon.ico`,
|
|
46
|
+
`${origin}/favicon.png`
|
|
47
|
+
)
|
|
48
|
+
this.faviconIndex = 0
|
|
49
|
+
this.favicon = this.faviconCandidates[this.faviconIndex]
|
|
50
|
+
const hostname = u.hostname.replace(/^www\./, '')
|
|
51
|
+
const hostnamePretty = hostname
|
|
52
|
+
.split('.')
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.slice(-2)
|
|
55
|
+
.join(' ')
|
|
56
|
+
.replace(/-/g, ' ')
|
|
57
|
+
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
58
|
+
|
|
59
|
+
if (this.label && this.label.trim()) {
|
|
60
|
+
this.displayName = this.label.trim()
|
|
61
|
+
} else {
|
|
62
|
+
this.displayName = hostnamePretty || this.url
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
this.favicon = undefined
|
|
66
|
+
// Fallback to raw string if URL parse fails
|
|
67
|
+
this.displayName = this.label && this.label.trim() ? this.label : this.url
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private handleImageError() {
|
|
72
|
+
this.faviconIndex += 1
|
|
73
|
+
if (this.faviconIndex < this.faviconCandidates.length) {
|
|
74
|
+
this.favicon = this.faviconCandidates[this.faviconIndex]
|
|
75
|
+
} else {
|
|
76
|
+
this.favicon = undefined
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private getSizePixels(): number {
|
|
81
|
+
switch (this.size) {
|
|
82
|
+
case 'x-small':
|
|
83
|
+
return 14
|
|
84
|
+
case 'small':
|
|
85
|
+
return 16
|
|
86
|
+
case 'medium':
|
|
87
|
+
return 20
|
|
88
|
+
case 'large':
|
|
89
|
+
return 24
|
|
90
|
+
default:
|
|
91
|
+
return 16
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
override render() {
|
|
96
|
+
const sizePx = this.getSizePixels()
|
|
97
|
+
return html`
|
|
98
|
+
<div class="inline-flex flex-col gap-1">
|
|
99
|
+
<div class="inline-flex items-center gap-2 px-2.5 py-1.5 text-neutral-20 leading-tight font-semibold">
|
|
100
|
+
${
|
|
101
|
+
this.showLogo
|
|
102
|
+
? html`
|
|
103
|
+
<div class="inline-flex items-center p-1 justify-center rounded-lg bg-neutral-100 border border-neutral-90 overflow-hidden flex-none">
|
|
104
|
+
${
|
|
105
|
+
this.favicon
|
|
106
|
+
? html`<img
|
|
107
|
+
src=${this.favicon}
|
|
108
|
+
style="width:${sizePx}px;height:${sizePx}px;"
|
|
109
|
+
alt="favicon"
|
|
110
|
+
@error=${this.handleImageError}
|
|
111
|
+
/>`
|
|
112
|
+
: html`<lukso-icon name="globe" size=${this.size} color="blue-85"></lukso-icon>`
|
|
113
|
+
}
|
|
114
|
+
</div>
|
|
115
|
+
`
|
|
116
|
+
: ''
|
|
117
|
+
}
|
|
118
|
+
<span class="whitespace-nowrap overflow-hidden text-ellipsis leading-tight" style=${`max-width:${this.maxWidth}px;font-size: ${sizePx}px;`}>${this.displayName}</span>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
`
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
declare global {
|
|
126
|
+
interface HTMLElementTagNameMap {
|
|
127
|
+
'url-view': UrlView
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import { Task } from '@lit/task'
|
|
2
|
+
import { useAddressResolver } from '@lukso/transaction-view-headless'
|
|
3
|
+
import { css, html } from 'lit'
|
|
4
|
+
import { property, state } from 'lit/decorators.js'
|
|
5
|
+
import { when } from 'lit/directives/when.js'
|
|
6
|
+
import { safeCustomElement } from './safe-custom-element'
|
|
7
|
+
|
|
8
|
+
// Import LUKSO web components
|
|
9
|
+
import './load-web-components'
|
|
10
|
+
import { CoreLitElement } from '../styles'
|
|
11
|
+
|
|
12
|
+
// Value formatting utilities
|
|
13
|
+
function formatUnits(value: bigint, decimals = 18): string {
|
|
14
|
+
const divisor = 10n ** BigInt(decimals)
|
|
15
|
+
const quotient = value / divisor
|
|
16
|
+
const remainder = value % divisor
|
|
17
|
+
|
|
18
|
+
if (remainder === 0n) {
|
|
19
|
+
return quotient.toString()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const remainderStr = remainder.toString().padStart(decimals, '0')
|
|
23
|
+
const trimmed = remainderStr.replace(/0+$/, '')
|
|
24
|
+
return `${quotient}.${trimmed}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Value display component
|
|
29
|
+
*
|
|
30
|
+
* Displays various types of values with appropriate formatting and styling.
|
|
31
|
+
* Supports numbers, strings, booleans, BigInt values, and special handling
|
|
32
|
+
* for Ethereum values with currency symbols.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```html
|
|
36
|
+
* <value-view
|
|
37
|
+
* value="1000000000000000000"
|
|
38
|
+
* type="currency"
|
|
39
|
+
* currency-symbol="LYX"
|
|
40
|
+
* decimals="18"
|
|
41
|
+
* ></value-view>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
@safeCustomElement('value-view')
|
|
45
|
+
export class ValueView extends CoreLitElement {
|
|
46
|
+
/**
|
|
47
|
+
* The value to display (required)
|
|
48
|
+
* Can be string, number, boolean, or bigint
|
|
49
|
+
*/
|
|
50
|
+
@property() value?: string | number | boolean | bigint | null
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The key name (used for special formatting logic)
|
|
54
|
+
*/
|
|
55
|
+
@property({ attribute: 'key-name' }) keyName?: string
|
|
56
|
+
|
|
57
|
+
@property({ type: String, attribute: 'container-class-names' })
|
|
58
|
+
containerClassNames?: string
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Value type for formatting
|
|
62
|
+
*/
|
|
63
|
+
@property() type:
|
|
64
|
+
| 'string'
|
|
65
|
+
| 'number'
|
|
66
|
+
| 'boolean'
|
|
67
|
+
| 'currency'
|
|
68
|
+
| 'bigint'
|
|
69
|
+
| 'address'
|
|
70
|
+
| 'auto' = 'auto'
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Currency symbol for currency type
|
|
74
|
+
*/
|
|
75
|
+
@property({ attribute: 'currency-symbol' }) currencySymbol = 'LYX'
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Number of decimals for currency formatting
|
|
79
|
+
*/
|
|
80
|
+
@property({ type: Number }) decimals = 18
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Display size
|
|
84
|
+
*/
|
|
85
|
+
@property() size: 'x-small' | 'small' | 'medium' | 'large' = 'medium'
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Whether to show copy button
|
|
89
|
+
*/
|
|
90
|
+
@property({ type: Boolean, attribute: 'show-copy-button' }) showCopyButton =
|
|
91
|
+
false
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Render as a card with bordered container and label
|
|
95
|
+
*/
|
|
96
|
+
@property({ type: Boolean, attribute: 'is-card-view' }) isCardView = false
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Optional label to show in card header
|
|
100
|
+
*/
|
|
101
|
+
@property() label?: string
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Maximum width for truncation
|
|
105
|
+
*/
|
|
106
|
+
@property({ type: Number, attribute: 'max-width' }) maxWidth?: number
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Token/contract address for automatic metadata resolution
|
|
110
|
+
* When provided, ValueView will automatically resolve token symbol and decimals
|
|
111
|
+
*/
|
|
112
|
+
@property() address?: string
|
|
113
|
+
|
|
114
|
+
// Internal state for resolved metadata
|
|
115
|
+
@state() private resolvedMetadata?: {
|
|
116
|
+
symbol?: string
|
|
117
|
+
decimals?: number
|
|
118
|
+
name?: string
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Tracks whether the component has any slotted content
|
|
122
|
+
@state() private _hasSlotContent = false
|
|
123
|
+
|
|
124
|
+
private _onSlotChange(e: Event) {
|
|
125
|
+
const slot = e.target as HTMLSlotElement
|
|
126
|
+
const assigned = slot?.assignedNodes({ flatten: true }) ?? []
|
|
127
|
+
this._hasSlotContent = assigned.length > 0
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
protected override firstUpdated(): void {
|
|
131
|
+
const slot = this.renderRoot.querySelector('slot') as HTMLSlotElement | null
|
|
132
|
+
if (slot) {
|
|
133
|
+
const assigned = slot.assignedNodes({ flatten: true })
|
|
134
|
+
this._hasSlotContent = assigned.length > 0
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Address resolution utilities (lazy initialization)
|
|
139
|
+
private get addressResolver() {
|
|
140
|
+
try {
|
|
141
|
+
return useAddressResolver()
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.warn('ValueView: Address resolver not available:', error)
|
|
144
|
+
return {
|
|
145
|
+
resolveAddress: async () => null,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Task for resolving address metadata
|
|
151
|
+
private addressTask = new Task(this, {
|
|
152
|
+
task: async ([address]) => {
|
|
153
|
+
if (!address) return null
|
|
154
|
+
try {
|
|
155
|
+
return this.addressResolver.resolveAddress(address)
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.warn('ValueView: Failed to resolve address metadata:', error)
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
args: () => [this.address] as const,
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get the effective type for the value
|
|
166
|
+
*/
|
|
167
|
+
private getEffectiveType(): string {
|
|
168
|
+
if (this.type !== 'auto') return this.type
|
|
169
|
+
|
|
170
|
+
const value = this.value
|
|
171
|
+
|
|
172
|
+
if (value === null || value === undefined) return 'null'
|
|
173
|
+
if (typeof value === 'string') return 'string'
|
|
174
|
+
if (typeof value === 'number') return 'number'
|
|
175
|
+
if (typeof value === 'boolean') return 'boolean'
|
|
176
|
+
if (typeof value === 'bigint') {
|
|
177
|
+
// Special handling for 'value' key - treat as currency
|
|
178
|
+
if (this.keyName === 'value') return 'currency'
|
|
179
|
+
return 'bigint'
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return 'string'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get CSS class for the value type
|
|
187
|
+
*/
|
|
188
|
+
private getValueClass(): string {
|
|
189
|
+
const effectiveType = this.getEffectiveType()
|
|
190
|
+
|
|
191
|
+
switch (effectiveType) {
|
|
192
|
+
case 'string':
|
|
193
|
+
return 'text-success-60'
|
|
194
|
+
case 'number':
|
|
195
|
+
return 'text-info-60'
|
|
196
|
+
case 'boolean':
|
|
197
|
+
return 'text-error-60'
|
|
198
|
+
case 'currency':
|
|
199
|
+
return 'text-warning-60 font-medium'
|
|
200
|
+
case 'bigint':
|
|
201
|
+
return 'text-secondary-60'
|
|
202
|
+
case 'address':
|
|
203
|
+
return 'text-info-60 font-mono'
|
|
204
|
+
case 'null':
|
|
205
|
+
return 'text-neutral-70 italic'
|
|
206
|
+
default:
|
|
207
|
+
return 'text-neutral-10'
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Format the value for display
|
|
213
|
+
*/
|
|
214
|
+
private getFormattedValue(): string {
|
|
215
|
+
const value = this.value
|
|
216
|
+
const effectiveType = this.getEffectiveType()
|
|
217
|
+
|
|
218
|
+
if (value === null) return 'null'
|
|
219
|
+
if (value === undefined) return 'undefined'
|
|
220
|
+
|
|
221
|
+
switch (effectiveType) {
|
|
222
|
+
case 'currency':
|
|
223
|
+
const { numeric, symbol } = this.getCurrencyParts()
|
|
224
|
+
return `${numeric} ${symbol}`
|
|
225
|
+
|
|
226
|
+
case 'bigint':
|
|
227
|
+
if (typeof value === 'bigint') {
|
|
228
|
+
return value.toString() + 'n'
|
|
229
|
+
}
|
|
230
|
+
return String(value) + 'n'
|
|
231
|
+
|
|
232
|
+
case 'boolean':
|
|
233
|
+
return value ? 'true' : 'false'
|
|
234
|
+
|
|
235
|
+
case 'number':
|
|
236
|
+
if (typeof value === 'number') {
|
|
237
|
+
// Format large numbers with commas
|
|
238
|
+
return value.toLocaleString()
|
|
239
|
+
}
|
|
240
|
+
return String(value)
|
|
241
|
+
|
|
242
|
+
case 'string':
|
|
243
|
+
return String(value)
|
|
244
|
+
|
|
245
|
+
case 'null':
|
|
246
|
+
return 'null'
|
|
247
|
+
|
|
248
|
+
default:
|
|
249
|
+
return String(value)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Compute currency display parts: numeric portion and symbol
|
|
255
|
+
*/
|
|
256
|
+
private getCurrencyParts(): { numeric: string; symbol: string } {
|
|
257
|
+
const value = this.value
|
|
258
|
+
const resolvedData: any = this.addressTask.value
|
|
259
|
+
const effectiveDecimals = resolvedData?.lsp4TokenSymbol
|
|
260
|
+
? (resolvedData.baseAsset?.decimals ??
|
|
261
|
+
resolvedData.decimals ??
|
|
262
|
+
this.decimals)
|
|
263
|
+
: this.decimals
|
|
264
|
+
const effectiveSymbol =
|
|
265
|
+
resolvedData?.lsp4TokenSymbol ||
|
|
266
|
+
resolvedData?.baseAsset?.lsp4TokenSymbol ||
|
|
267
|
+
this.currencySymbol
|
|
268
|
+
|
|
269
|
+
let numeric = String(value ?? '')
|
|
270
|
+
try {
|
|
271
|
+
if (typeof value === 'bigint') {
|
|
272
|
+
numeric = formatUnits(value, effectiveDecimals)
|
|
273
|
+
} else if (typeof value === 'string' && !isNaN(Number(value))) {
|
|
274
|
+
const bigintValue = BigInt(value)
|
|
275
|
+
numeric = formatUnits(bigintValue, effectiveDecimals)
|
|
276
|
+
}
|
|
277
|
+
} catch {
|
|
278
|
+
// fallback to raw string
|
|
279
|
+
numeric = String(value ?? '')
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return { numeric, symbol: effectiveSymbol }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Typography class for numeric value based on size */
|
|
286
|
+
private getNumericClass(): string {
|
|
287
|
+
switch (this.size) {
|
|
288
|
+
case 'x-small':
|
|
289
|
+
return 'paragraph-ptmono-12-bold'
|
|
290
|
+
case 'small':
|
|
291
|
+
return 'paragraph-ptmono-14-bold'
|
|
292
|
+
case 'medium':
|
|
293
|
+
return 'paragraph-ptmono-16-bold'
|
|
294
|
+
case 'large':
|
|
295
|
+
return 'paragraph-ptmono-24-bold'
|
|
296
|
+
default:
|
|
297
|
+
return 'paragraph-ptmono-16-bold'
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Typography class for currency symbol based on size */
|
|
302
|
+
private getCurrencyClass(): string {
|
|
303
|
+
switch (this.size) {
|
|
304
|
+
case 'x-small':
|
|
305
|
+
return 'paragraph-inter-12-regular'
|
|
306
|
+
case 'small':
|
|
307
|
+
return 'paragraph-inter-14-regular'
|
|
308
|
+
case 'medium':
|
|
309
|
+
return 'paragraph-inter-16-regular'
|
|
310
|
+
case 'large':
|
|
311
|
+
return 'paragraph-inter-20-regular'
|
|
312
|
+
default:
|
|
313
|
+
return 'paragraph-inter-16-regular'
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get size-appropriate font size
|
|
319
|
+
*/
|
|
320
|
+
private getContainerTextClass(): string {
|
|
321
|
+
switch (this.size) {
|
|
322
|
+
case 'x-small':
|
|
323
|
+
return 'text-[12px]'
|
|
324
|
+
case 'small':
|
|
325
|
+
return 'text-[14px]'
|
|
326
|
+
case 'medium':
|
|
327
|
+
return 'text-[16px]'
|
|
328
|
+
case 'large':
|
|
329
|
+
return 'text-[18px]'
|
|
330
|
+
default:
|
|
331
|
+
return 'text-[16px]'
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Handle copy to clipboard
|
|
337
|
+
*/
|
|
338
|
+
private async handleCopy(e?: Event) {
|
|
339
|
+
if (e) e.stopPropagation()
|
|
340
|
+
const textToCopy = String(this.value)
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
await navigator.clipboard.writeText(textToCopy)
|
|
344
|
+
|
|
345
|
+
// Dispatch copy event
|
|
346
|
+
this.dispatchEvent(
|
|
347
|
+
new CustomEvent('value-copied', {
|
|
348
|
+
detail: { value: textToCopy },
|
|
349
|
+
bubbles: true,
|
|
350
|
+
})
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
// Value copied successfully - UI feedback handled by event
|
|
354
|
+
} catch (error) {
|
|
355
|
+
console.error('Failed to copy value:', error)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Handle value click
|
|
361
|
+
*/
|
|
362
|
+
private handleClick() {
|
|
363
|
+
this.dispatchEvent(
|
|
364
|
+
new CustomEvent('value-click', {
|
|
365
|
+
detail: {
|
|
366
|
+
value: this.value,
|
|
367
|
+
formattedValue: this.getFormattedValue(),
|
|
368
|
+
type: this.getEffectiveType(),
|
|
369
|
+
},
|
|
370
|
+
bubbles: true,
|
|
371
|
+
})
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
override render() {
|
|
376
|
+
const formattedValue = this.getFormattedValue()
|
|
377
|
+
const valueClass = this.getValueClass()
|
|
378
|
+
|
|
379
|
+
// Card view
|
|
380
|
+
if (this.isCardView) {
|
|
381
|
+
const effectiveType = this.getEffectiveType()
|
|
382
|
+
let headerText = this.label || ''
|
|
383
|
+
let contentTemplate = html`
|
|
384
|
+
<span class="inline-block text-wrap min-w-0 text-center">${formattedValue}</span>
|
|
385
|
+
`
|
|
386
|
+
|
|
387
|
+
if (effectiveType === 'currency') {
|
|
388
|
+
const { numeric, symbol } = this.getCurrencyParts()
|
|
389
|
+
if (this.label && this.label.trim() !== '') {
|
|
390
|
+
headerText = this.label
|
|
391
|
+
contentTemplate = html`
|
|
392
|
+
<span class="inline-block overflow-hidden text-ellipsis whitespace-nowrap min-w-0 ${this.getNumericClass()}">${numeric}</span>
|
|
393
|
+
<span class="inline-block ${this.getCurrencyClass()}"> ${symbol}</span>
|
|
394
|
+
`
|
|
395
|
+
} else {
|
|
396
|
+
headerText = symbol
|
|
397
|
+
contentTemplate = html`
|
|
398
|
+
<span class="inline-block overflow-hidden text-ellipsis whitespace-nowrap min-w-0 ${this.getNumericClass()}">${numeric}</span>
|
|
399
|
+
`
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return html`
|
|
404
|
+
<div
|
|
405
|
+
class="block border border-neutral-90 rounded-xl bg-white py-3 px-4 min-w-[320px] ${this.containerClassNames || ''}"
|
|
406
|
+
@click=${this.handleClick}
|
|
407
|
+
style="${this.maxWidth ? `max-width: ${this.maxWidth}px` : ''}"
|
|
408
|
+
>
|
|
409
|
+
<div class="flex items-center justify-between mb-2">
|
|
410
|
+
${
|
|
411
|
+
headerText
|
|
412
|
+
? html`<div class="${this.getLabelClass()} text-neutral-20 overflow-hidden text-ellipsis whitespace-nowrap">${headerText}</div>`
|
|
413
|
+
: html``
|
|
414
|
+
}
|
|
415
|
+
${
|
|
416
|
+
this.showCopyButton
|
|
417
|
+
? html`
|
|
418
|
+
<button
|
|
419
|
+
class="bg-white rounded-lg w-6 h-6 flex items-center justify-center cursor-pointer text-neutral-20 hover:bg-neutral-95"
|
|
420
|
+
@click=${(e: Event) => this.handleCopy(e)}
|
|
421
|
+
title="Copy value"
|
|
422
|
+
aria-label="Copy"
|
|
423
|
+
>
|
|
424
|
+
<lukso-icon name="copy" size="small"></lukso-icon>
|
|
425
|
+
</button>
|
|
426
|
+
`
|
|
427
|
+
: html``
|
|
428
|
+
}
|
|
429
|
+
</div>
|
|
430
|
+
${when(
|
|
431
|
+
!this._hasSlotContent,
|
|
432
|
+
() => html`
|
|
433
|
+
<div class="font-semibold text-neutral-10 break-words flex items-center gap-2 text-wrap ${
|
|
434
|
+
this.getUseMonospace() ? 'font-mono' : ''
|
|
435
|
+
}">
|
|
436
|
+
${contentTemplate}
|
|
437
|
+
</div>
|
|
438
|
+
`
|
|
439
|
+
)}
|
|
440
|
+
<slot @slotchange=${this._onSlotChange}></slot>
|
|
441
|
+
</div>
|
|
442
|
+
`
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Inline view (default)
|
|
446
|
+
return html`
|
|
447
|
+
<div
|
|
448
|
+
class="inline-flex items-center gap-1.5 cursor-pointer transition-all duration-200 rounded px-1 py-0.5 hover:bg-neutral-95 min-w-[320px] ${this.getContainerTextClass()} ${this.containerClassNames || ''}"
|
|
449
|
+
@click=${this.handleClick}
|
|
450
|
+
style="${this.maxWidth ? `max-width: ${this.maxWidth}px` : ''}"
|
|
451
|
+
>
|
|
452
|
+
<span class="inline-block overflow-hidden text-ellipsis whitespace-nowrap min-w-0 ${valueClass}">
|
|
453
|
+
${formattedValue}
|
|
454
|
+
</span>
|
|
455
|
+
${
|
|
456
|
+
this.showCopyButton
|
|
457
|
+
? html`
|
|
458
|
+
<button
|
|
459
|
+
class="inline-flex items-center justify-center bg-transparent border-0 cursor-pointer text-neutral-70 p-0.5 rounded transition-all duration-200 hover:text-primary-50 hover:bg-neutral-90 flex-shrink-0"
|
|
460
|
+
@click=${(e: Event) => this.handleCopy(e)}
|
|
461
|
+
title="Copy value"
|
|
462
|
+
aria-label="Copy value to clipboard"
|
|
463
|
+
>
|
|
464
|
+
<lukso-icon name="copy" size="small"></lukso-icon>
|
|
465
|
+
</button>
|
|
466
|
+
`
|
|
467
|
+
: ''
|
|
468
|
+
}
|
|
469
|
+
</div>
|
|
470
|
+
`
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** Whether to use monospace font for the value content */
|
|
474
|
+
private getUseMonospace(): boolean {
|
|
475
|
+
const t = this.getEffectiveType()
|
|
476
|
+
if (
|
|
477
|
+
t === 'address' ||
|
|
478
|
+
t === 'bigint' ||
|
|
479
|
+
this.value?.toString().startsWith('0x')
|
|
480
|
+
)
|
|
481
|
+
return true
|
|
482
|
+
if (t === 'string') {
|
|
483
|
+
const s = String(this.value ?? '')
|
|
484
|
+
return /^0x[0-9a-fA-F]+$/.test(s) || s.length > 32
|
|
485
|
+
}
|
|
486
|
+
return false
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Map size to a typography class for the label */
|
|
490
|
+
private getLabelClass(): string {
|
|
491
|
+
switch (this.size) {
|
|
492
|
+
case 'x-small':
|
|
493
|
+
return 'paragraph-inter-12-semi-bold'
|
|
494
|
+
case 'small':
|
|
495
|
+
return 'heading-inter-14-bold'
|
|
496
|
+
case 'medium':
|
|
497
|
+
return 'heading-inter-17-semi-bold'
|
|
498
|
+
case 'large':
|
|
499
|
+
return 'heading-inter-21-semi-bold'
|
|
500
|
+
default:
|
|
501
|
+
return 'heading-inter-17-semi-bold'
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
declare global {
|
|
507
|
+
interface HTMLElementTagNameMap {
|
|
508
|
+
'value-view': ValueView
|
|
509
|
+
}
|
|
510
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Export the built CSS as a string for components that need to inject styles
|
|
2
|
+
// Tailwind + web-components CSS is compiled into this file by our PostCSS pipeline
|
|
3
|
+
// esbuild-raw-plugin ensures importing CSS returns its raw contents as a string
|
|
4
|
+
|
|
5
|
+
import output from 'styles.css?raw'
|
|
6
|
+
import { css, LitElement, unsafeCSS } from 'lit'
|
|
7
|
+
|
|
8
|
+
const style = css`
|
|
9
|
+
${unsafeCSS(output)}
|
|
10
|
+
|
|
11
|
+
:host([is-inline]) {
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
:host([is-full-width]) {
|
|
17
|
+
width: 100%;
|
|
18
|
+
flex: 1;
|
|
19
|
+
display: flex;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
`
|
|
23
|
+
|
|
24
|
+
export class CoreLitElement extends LitElement {
|
|
25
|
+
static styles = [style]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default style
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "@lukso/web-components/dist/styles/main.css";
|
package/src/vue-entry.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue-specific entry point for transaction view core
|
|
3
|
+
* Import this when you need Vue integration, to avoid Vue dependency issues in other frameworks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Re-export types
|
|
7
|
+
export type { DecoderResult } from '@lukso/transaction-decoder'
|
|
8
|
+
export type {
|
|
9
|
+
DecoderRecordForSelection,
|
|
10
|
+
ViewContext,
|
|
11
|
+
} from '@lukso/transaction-view-headless'
|
|
12
|
+
// Also re-export the core functionality
|
|
13
|
+
export * from './registry/index.js'
|
|
14
|
+
export * from './shared/index.js'
|
|
15
|
+
export * from './vue/index.js'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind v3 config for @lukso/transaction-view-core
|
|
3
|
+
* Minimal theme extensions to support @lukso/web-components CSS apply tokens
|
|
4
|
+
* like text-48, leading-58, font-400, font-inter, etc.
|
|
5
|
+
*/
|
|
6
|
+
module.exports = {
|
|
7
|
+
content: ['./src/**/*.{ts,tsx,js,jsx,vue,html}'],
|
|
8
|
+
presets: [require('@lukso/web-components/tailwind.config')],
|
|
9
|
+
}
|