@lukso/transaction-view-core 0.5.0-dev.5ea12c5 → 0.5.0-dev.c21633f

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lukso/transaction-view-core",
3
- "version": "0.5.0-dev.5ea12c5",
3
+ "version": "0.5.0-dev.c21633f",
4
4
  "description": "Core components and hooks for LUKSO transaction display",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -35,8 +35,8 @@
35
35
  "@preact/signals-core": "^1.12.1",
36
36
  "ethereum-blockies-base64": "^1.0.2",
37
37
  "lit": "^3.3.1",
38
- "@lukso/transaction-decoder": "1.3.0-dev.5ea12c5",
39
- "@lukso/transaction-view-headless": "0.4.0-dev.5ea12c5"
38
+ "@lukso/transaction-view-headless": "0.4.0-dev.c21633f",
39
+ "@lukso/transaction-decoder": "1.3.0-dev.c21633f"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@tailwindcss/typography": "^0.5.19",
@@ -16,21 +16,7 @@ import {
16
16
  } from '@lukso/transaction-view-headless'
17
17
  import { safeCustomElement } from '../shared/safe-custom-element.js'
18
18
  import { CoreLitElement } from '../styles'
19
-
20
- // Value formatting utilities
21
- function formatUnits(value: bigint, decimals = 18): string {
22
- const divisor = 10n ** BigInt(decimals)
23
- const quotient = value / divisor
24
- const remainder = value % divisor
25
-
26
- if (remainder === 0n) {
27
- return quotient.toString()
28
- }
29
-
30
- const remainderStr = remainder.toString().padStart(decimals, '0')
31
- const trimmed = remainderStr.replace(/0+$/, '')
32
- return `${quotient}.${trimmed}`
33
- }
19
+ import { formatUnits, parseBigInt } from '../utils.js'
34
20
 
35
21
  /**
36
22
  * Asset card component for displaying token/NFT information
@@ -58,9 +44,6 @@ export class AssetCard extends CoreLitElement {
58
44
  @property({ type: String, attribute: 'function-icon' })
59
45
  functionIcon: string = 'code-outline'
60
46
 
61
- @property({ type: String })
62
- size: 'small' | 'medium' | 'large' = 'medium'
63
-
64
47
  @property({ type: Boolean, attribute: 'show-info-button' })
65
48
  showInfoButton = true
66
49
 
@@ -211,9 +194,7 @@ export class AssetCard extends CoreLitElement {
211
194
  const displayTokenId = formattedTokenId ? `${formattedTokenId}` : ''
212
195
  if (displayTokenId) return displayTokenId
213
196
  // If its a collection, do not show tokenId
214
- return this.tokenId && !this._resolvedData?.isCollection
215
- ? `${parseInt(this.tokenId, 16)}`
216
- : undefined
197
+ return this.tokenId ? `${parseInt(this.tokenId, 16)}` : undefined
217
198
  }
218
199
 
219
200
  private _renderTokenId() {
@@ -278,6 +259,7 @@ export class AssetCard extends CoreLitElement {
278
259
  private _renderImages() {
279
260
  const images = (this._resolvedData as any)?.images || []
280
261
  if (images.length === 0) return ''
262
+ if (!this.address) return ''
281
263
 
282
264
  return html`
283
265
  <image-list
@@ -292,6 +274,7 @@ export class AssetCard extends CoreLitElement {
292
274
  private _renderAssets() {
293
275
  const assets = (this._resolvedData as any)?.assets || []
294
276
  if (assets.length === 0) return ''
277
+ if (!this.address) return ''
295
278
 
296
279
  return html`
297
280
  <div class="section">
@@ -309,11 +292,11 @@ export class AssetCard extends CoreLitElement {
309
292
  return html`
310
293
  <div class="flex justify-between items-center my-4">
311
294
  <function-name-view
312
- label=${this.functionName}
313
- size="small"
314
- icon=${this.functionIcon ? this.functionIcon : 'code-outline'}
295
+ label=${this.functionName || 'Execute'}
296
+ size="small"
297
+ icon=${this.functionIcon ? this.functionIcon : 'code-outline'}
315
298
  ></function-name-view>
316
- <function-name-view label=${this._resolvedData?.standard} size="small"></function-name-view>
299
+ <function-name-view label=${this._resolvedData?.standard ?? 'Unknown'} size="small"></function-name-view>
317
300
  </div>
318
301
  `
319
302
  }
@@ -334,6 +317,15 @@ export class AssetCard extends CoreLitElement {
334
317
  `
335
318
  }
336
319
 
320
+ private _formattedAmount() {
321
+ const data = this._resolutionTask.value
322
+ const amt = this.amount
323
+ if (amt === undefined || amt === null || amt === '') return undefined
324
+ const bigintAmount = parseBigInt(amt as any)
325
+ if (bigintAmount === undefined) return undefined
326
+ return formatUnits(bigintAmount, data?.decimals)
327
+ }
328
+
337
329
  private renderFormattedTokenIdBadge() {
338
330
  if (!this.displayedTokenId) return ''
339
331
  return html`
@@ -345,30 +337,47 @@ export class AssetCard extends CoreLitElement {
345
337
  return html`
346
338
  <div >
347
339
  ${this._renderStandards()}
348
- <div class="mt-6 flex-col">
349
- <div class="flex items-center justify-center gap-4">
350
- <div class="flex justify-end w-[48%]">
351
- <div class="border relative border-neutral-90 rounded-xl">
352
- ${this.renderFormattedTokenIdBadge()}
353
- <div class="p-2 relative">
354
- <icon-view
355
- address="${this.address}"
356
- size="2x-large"
357
- .showName=${false}
358
- ></icon-view>
359
- </div>
360
- <div style="height: 1px;" class="bg-neutral-90 w-full"></div>
361
- <div class="px-2">
362
- ${this._renderAddressSection()}
340
+ <div class="mt-6 flex-col">
341
+ <div class="flex items-center justify-center gap-4">
342
+ <div class="flex justify-end w-[48%]">
343
+ <div class="border relative border-neutral-90 rounded-xl">
344
+ ${this.renderFormattedTokenIdBadge()}
345
+ <div class="p-2 relative">
346
+ ${when(
347
+ this.address,
348
+ () => html`
349
+ <icon-view
350
+ address=${this.address ?? ''}
351
+ size="2x-large"
352
+ .showName=${false}
353
+ ></icon-view>
354
+ `
355
+ )}
356
+ </div>
357
+ <div style="height: 1px;" class="bg-neutral-90 w-full"></div>
358
+ <div class="px-2">
359
+ ${this._renderAddressSection()}
360
+ </div>
363
361
  </div>
364
362
  </div>
365
- </div>
366
363
 
367
- <div class="flex flex-col gap-1 w-[51%]">
368
- <div class="text-[14px] font-bold text-neutral-20">
369
- ${this._resolvedData?.lsp4TokenName || 'UNKNOWN'}
370
- </div>
371
- <div class="text-[17px] font-semibold text-neutral-20">
364
+ <div class="flex flex-col gap-1 w-[51%]">
365
+ <div class="text-[14px] font-bold text-neutral-20">
366
+ ${this._resolvedData?.lsp4TokenName || 'UNKNOWN'}
367
+ </div>
368
+ <div class="text-[17px] font-semibold text-neutral-20">
369
+ ${when(
370
+ this.amount,
371
+ () => html`
372
+ <span class="">${this._formattedAmount() ?? ''}</span>
373
+ <span class="ml-1 text-neutral-60 max-w-[60px] whitespace-nowrap overflow-hidden text-ellipsis text-[14px] font-semibold">${
374
+ this._resolvedData?.lsp4TokenSymbol || ''
375
+ }</span>
376
+ `,
377
+ () => html`${this._resolvedData?.lsp4TokenSymbol || ''}`
378
+ )}
379
+ </div>
380
+
372
381
  ${when(
373
382
  this.amount,
374
383
  () => html`
@@ -379,20 +388,14 @@ export class AssetCard extends CoreLitElement {
379
388
  `,
380
389
  () => html`${this._resolvedData?.lsp4TokenSymbol || ''}`
381
390
  )}
382
- </div>
383
-
384
- ${when(
385
- this.displayedTokenId,
386
- () =>
387
- html`<div class="text-[14px] font-medium text-neutral-60"><span class="font-bold">#${this.displayedTokenId}</span></div>`
388
- )}
389
- <div class="mt-4">
390
- ${when(this.showCreator, () => this._renderCreatorSection())}
391
+ <div class="mt-4">
392
+ ${when(this.showCreator, () => this._renderCreatorSection())}
393
+ </div>
391
394
  </div>
392
395
  </div>
393
- </div>
394
- <div class="border-neutral-90 pt-2">
395
- ${this._renderTokenDetails()}
396
+ <div class="border-neutral-90 pt-2">
397
+ ${this._renderTokenDetails()}
398
+ </div>
396
399
  </div>
397
400
  </div>
398
401
  `
@@ -79,13 +79,15 @@ export class PermissionsBlock extends CoreLitElement {
79
79
  }
80
80
 
81
81
  private renderPermission(p: PermissionLevels) {
82
- const nameBase = (p.icon || p.permissionName || '').toLowerCase()
82
+ const nameBase = (p.icon || p.permissionName || '')
83
+ .toLowerCase()
84
+ .replace(/_/g, '-')
83
85
  const activeIcon = `${nameBase}-activated`
84
86
  const inactiveIcon = `${nameBase}-inactive`
85
87
  return html`
86
88
  <div class="mt-4 grid grid-cols-[40px,1fr,20px] gap-x-2 items-start">
87
89
  <div class="relative w-10 h-10">
88
- <svg-icon
90
+ <lukso-icon
89
91
  class="active absolute bottom-0 opacity-0 z-[99] transition-opacity duration-200 ${
90
92
  p.isActive ? 'opacity-100' : ''
91
93
  }"
@@ -93,14 +95,14 @@ export class PermissionsBlock extends CoreLitElement {
93
95
  width="40"
94
96
  height="40"
95
97
  color=${this.color}
96
- ></svg-icon>
97
- <svg-icon
98
+ ></lukso-icon>
99
+ <lukso-icon
98
100
  class="inactive absolute bottom-0 ${p.isActive ? 'opacity-0' : 'opacity-100'}"
99
101
  name=${inactiveIcon}
100
102
  width="40"
101
103
  height="40"
102
104
  color=${this.color}
103
- ></svg-icon>
105
+ ></lukso-icon>
104
106
  </div>
105
107
 
106
108
  <div>
@@ -237,7 +237,6 @@ export class ImageList extends CoreLitElement {
237
237
  </div>
238
238
  `
239
239
  }
240
-
241
240
  const grid = html`
242
241
  <div class="grid grid-cols-[repeat(auto-fill,minmax(${this.getImageMinSizePx()}px,300px))] gap-3 p-2">
243
242
  ${repeat(
@@ -58,7 +58,7 @@ export class NotificationView extends CoreLitElement {
58
58
  return html`
59
59
  <div class="grid grid-cols-[24px_1fr] items-start gap-3 p-3 rounded-xl text-neutral-10 leading-snug ${this.getVariantBackgroundClass()}">
60
60
  <div class="flex items-center justify-center text-[16px] text-neutral-20 self-center" aria-hidden="true">
61
- <lukso-icon name="${this.renderIcon()}" size="large" color="${this.getVariantColor()}"></lukso-icon>
61
+ <lukso-icon name="${this.renderIcon()}" size="small" color="${this.getVariantColor()}"></lukso-icon>
62
62
  </div>
63
63
  <div class="text-[14px] flex flex-col gap-1.5">
64
64
  ${
@@ -66,7 +66,7 @@ export class NotificationView extends CoreLitElement {
66
66
  ? html`<div class="font-semibold text-neutral-20 paragraph-inter-14-semi-bold">${this.title}</div>`
67
67
  : ''
68
68
  }
69
- <div class="text-neutral-30"><slot>${this.message}</slot></div>
69
+ <div class="text-neutral-20"><slot>${this.message}</slot></div>
70
70
  ${
71
71
  this.acknowledgeable
72
72
  ? html`
@@ -1,6 +1,6 @@
1
1
  import { Task } from '@lit/task'
2
2
  import { useAddressResolver } from '@lukso/transaction-view-headless'
3
- import { html } from 'lit'
3
+ import { html, type TemplateResult } from 'lit'
4
4
  import { property, state } from 'lit/decorators.js'
5
5
  import { when } from 'lit/directives/when.js'
6
6
  import { safeCustomElement } from './safe-custom-element'
@@ -8,21 +8,7 @@ import { safeCustomElement } from './safe-custom-element'
8
8
  // Import LUKSO web components
9
9
  import './load-web-components'
10
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
- }
11
+ import { formatUnits } from '../utils'
26
12
 
27
13
  /**
28
14
  * Value display component
@@ -47,7 +33,7 @@ export class ValueView extends CoreLitElement {
47
33
  * The value to display (required)
48
34
  * Can be string, number, boolean, or bigint
49
35
  */
50
- @property() value?: string | number | boolean | bigint | null
36
+ @property() value?: TemplateResult | string | number | boolean | bigint | null
51
37
 
52
38
  /**
53
39
  * The key name (used for special formatting logic)
@@ -98,7 +84,7 @@ export class ValueView extends CoreLitElement {
98
84
  /**
99
85
  * Optional label to show in card header
100
86
  */
101
- @property() label?: string
87
+ @property() label?: string | TemplateResult
102
88
 
103
89
  /**
104
90
  * Maximum width for truncation
@@ -380,7 +366,7 @@ export class ValueView extends CoreLitElement {
380
366
 
381
367
  if (effectiveType === 'currency') {
382
368
  const { numeric, symbol } = this.getCurrencyParts()
383
- if (this.label && this.label.trim() !== '') {
369
+ if (this.label) {
384
370
  headerText = this.label
385
371
  contentTemplate = html`
386
372
  <span class="inline-block overflow-hidden text-ellipsis whitespace-nowrap min-w-0 ${this.getNumericClass()}">${numeric}</span>
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { formatUnits, parseBigInt } from './utils.js'
3
+
4
+ describe('formatUnits', () => {
5
+ it('returns integer string when remainder is zero', () => {
6
+ expect(formatUnits(1000000000000000000n)).toBe('1')
7
+ expect(formatUnits(5000n, 3)).toBe('5')
8
+ })
9
+
10
+ it('formats values with fractional part respecting decimals', () => {
11
+ expect(formatUnits(123000000000000000n)).toBe('0.123')
12
+ expect(formatUnits(987654321n, 6)).toBe('987.654321')
13
+ })
14
+
15
+ it('trims trailing zeros in fractional part', () => {
16
+ expect(formatUnits(120n, 2)).toBe('1.2')
17
+ expect(formatUnits(1001000n, 6)).toBe('1.001')
18
+ })
19
+
20
+ it('handles zero value', () => {
21
+ expect(formatUnits(0n)).toBe('0')
22
+ })
23
+ })
24
+
25
+ describe('parseBigInt', () => {
26
+ it('parses decimal and hex strings with optional suffix', () => {
27
+ expect(parseBigInt('42')).toBe(42n)
28
+ expect(parseBigInt('0x10')).toBe(16n)
29
+ expect(parseBigInt('21n')).toBe(21n)
30
+ expect(parseBigInt(' 0xFFn ')).toBe(255n)
31
+ })
32
+
33
+ it('returns bigint directly or from integer numbers', () => {
34
+ const value = 123n
35
+ expect(parseBigInt(value)).toBe(value)
36
+ expect(parseBigInt(9007199254740991)).toBe(9007199254740991n)
37
+ })
38
+
39
+ it('returns undefined for invalid inputs', () => {
40
+ expect(parseBigInt('')).toBeUndefined()
41
+ expect(parseBigInt('1.5')).toBeUndefined()
42
+ expect(parseBigInt('0xG')).toBeUndefined()
43
+ expect(parseBigInt(1.1)).toBeUndefined()
44
+ expect(parseBigInt(null)).toBeUndefined()
45
+ expect(parseBigInt(undefined)).toBeUndefined()
46
+ })
47
+ })
package/src/utils.ts ADDED
@@ -0,0 +1,82 @@
1
+ // Value formatting utilities
2
+ /**
3
+ * Convert a token amount from base units (bigint) to a decimal string.
4
+ *
5
+ * - Handles arbitrary `decimals` without floating point precision loss.
6
+ * - Trims trailing zeros in the fractional part.
7
+ * - Returns only the integer part when remainder is zero.
8
+ *
9
+ * @param value - The amount in base units as a bigint (e.g., wei for ETH).
10
+ * @param decimals - Number of decimal places the token uses (default 18).
11
+ * @returns A human-readable decimal string.
12
+ *
13
+ * @example
14
+ * // 1 ether in wei
15
+ * formatUnits(1000000000000000000n) // '1'
16
+ *
17
+ * @example
18
+ * // 1.23 tokens with 2 decimals
19
+ * formatUnits(123n, 2) // '1.23'
20
+ *
21
+ * @example
22
+ * // No remainder: return integer part only
23
+ * formatUnits(2000000000000000000n) // '2'
24
+ */
25
+ export function formatUnits(value: bigint, decimals = 18): string {
26
+ const divisor = 10n ** BigInt(decimals)
27
+ const quotient = value / divisor
28
+ const remainder = value % divisor
29
+
30
+ if (remainder === 0n) {
31
+ return quotient.toString()
32
+ }
33
+
34
+ const remainderStr = remainder.toString().padStart(decimals, '0')
35
+ const trimmed = remainderStr.replace(/0+$/, '')
36
+ return `${quotient}.${trimmed}`
37
+ }
38
+
39
+ function isBigIntString(str: string): boolean {
40
+ const trimmed = str.trim()
41
+ if (trimmed.endsWith('n'))
42
+ return /^[0-9]+n$/.test(trimmed) || /^0x[0-9a-fA-F]+n$/.test(trimmed)
43
+ return /^[0-9]+$/.test(trimmed) || /^0x[0-9a-fA-F]+$/.test(trimmed)
44
+ }
45
+
46
+ /**
47
+ * Parse loose bigint inputs coming from attributes, JSON, or user input.
48
+ *
49
+ * - Accepts bigint literals (`123n`), decimal/hex strings, and integers.
50
+ * - Trims whitespace and optional trailing `n` suffix.
51
+ * - Returns `undefined` for invalid formats (floats, empty strings, non-numeric text).
52
+ *
53
+ * @param value - Source value to parse (string | number | bigint | null | undefined).
54
+ * @returns BigInt when parsing succeeds, otherwise `undefined`.
55
+ *
56
+ * @example
57
+ * parseBigInt('42') // 42n
58
+ * @example
59
+ * parseBigInt('0x10n') // 16n
60
+ * @example
61
+ * parseBigInt(1000n) // 1000n (passthrough)
62
+ * @example
63
+ * parseBigInt('1.5') // undefined (decimals not allowed)
64
+ */
65
+ export function parseBigInt(
66
+ value: string | number | bigint | null | undefined
67
+ ): bigint | undefined {
68
+ if (!value) return undefined
69
+ if (typeof value === 'bigint') return value
70
+ if (typeof value === 'number') {
71
+ if (!Number.isInteger(value)) return undefined
72
+ return BigInt(value)
73
+ }
74
+ const trimmed = value.trim()
75
+ const cleaned = trimmed.endsWith('n') ? trimmed.slice(0, -1) : trimmed
76
+ if (!isBigIntString(cleaned)) return undefined
77
+ try {
78
+ return BigInt(cleaned)
79
+ } catch {
80
+ return undefined
81
+ }
82
+ }