@stellar-expert/ui-framework 1.19.2 → 1.20.0

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.
@@ -0,0 +1,140 @@
1
+ import {isNumber} from '../core/utilities'
2
+
3
+ const DAY = 24 * 3600 * 1000
4
+
5
+ function buttonPeriodMs(b) {
6
+ if (b.type === 'month') return (b.count || 1) * 30 * DAY
7
+ if (b.type === 'year') return (b.count || 1) * 365 * DAY
8
+ if (b.type === 'day') return (b.count || 1) * DAY
9
+ return Infinity
10
+ }
11
+
12
+ //Floating range-selector buttons (1m / 3m / 6m / 1y / All) for StockChart, mirroring the explorer's config.
13
+ export class RangeSelector {
14
+ constructor(chart) {
15
+ this.chart = chart
16
+ this.options = chart.options.rangeSelector
17
+ this.selected = 'all' //default view = full range
18
+ //if an initial xAxis.range is set, pre-select the matching button
19
+ const initialRange = chart.xAxis[0] && chart.xAxis[0].options.range
20
+ if (this.options && this.options.buttons && isNumber(initialRange)) {
21
+ let bestIdx = -1
22
+ let bestDiff = Infinity
23
+ this.options.buttons.forEach((b, i) => {
24
+ const diff = Math.abs(buttonPeriodMs(b) - initialRange)
25
+ if (diff < bestDiff) {
26
+ bestDiff = diff
27
+ bestIdx = i
28
+ }
29
+ })
30
+ if (bestIdx >= 0)
31
+ this.selected = bestIdx
32
+ }
33
+ if (this.options && this.options.buttons)
34
+ this.build()
35
+ }
36
+
37
+ build() {
38
+ const chart = this.chart
39
+ //overlay wrapper — visual styling lives in .chart-range-selector (chart.scss); only the dynamic
40
+ //horizontal offset (plotLeft) is set inline in reposition()
41
+ const el = document.createElement('div')
42
+ el.className = 'chart-range-selector'
43
+ this.el = el
44
+ const zoomLabel = document.createElement('span')
45
+ zoomLabel.className = 'zoom-label'
46
+ zoomLabel.textContent = 'Zoom'
47
+ el.appendChild(zoomLabel)
48
+ this.zoomLabel = zoomLabel
49
+
50
+ //a button is disabled when the loaded data doesn't span its period —
51
+ //e.g. only ~1y of data → 'All' and '1y' work, but a hypothetical longer button would be greyed out
52
+ const xa = chart.xAxis[0]
53
+ const dataRange = (xa && isNumber(xa.dataMax) && isNumber(xa.dataMin)) ? xa.dataMax - xa.dataMin : Infinity
54
+
55
+ //controls are rendered as plain spans (NOT <button>/.button) so they keep the charts-lib's own
56
+ //self-contained style and never inherit the global project button look (skew/sizing/focus rules)
57
+ this.buttons = this.options.buttons.map((b, i) => {
58
+ const btn = document.createElement('span')
59
+ btn.className = 'chart-zoom-btn'
60
+ btn.setAttribute('role', 'button')
61
+ btn.textContent = b.text
62
+ //tolerance of one day absorbs the approximate month/year lengths
63
+ const disabled = b.type !== 'all' && buttonPeriodMs(b) > dataRange + DAY
64
+ if (!disabled) {
65
+ btn.tabIndex = 0
66
+ btn.addEventListener('click', () => this.apply(b, i))
67
+ btn.addEventListener('keydown', e => {
68
+ if (e.key === 'Enter' || e.key === ' ') {
69
+ e.preventDefault()
70
+ this.apply(b, i)
71
+ }
72
+ })
73
+ }
74
+ el.appendChild(btn)
75
+ return {btn, def: b, disabled}
76
+ })
77
+ chart.container.appendChild(el)
78
+ this.highlight()
79
+ this.applyResponsiveSizing()
80
+ this.reposition()
81
+ }
82
+
83
+ //drop the "Zoom" label on narrow charts so 1y/All stay fully visible on popular mobile widths
84
+ //instead of clipping at the right edge. Re-run on every redraw via reposition().
85
+ applyResponsiveSizing() {
86
+ const compact = (this.chart.chartWidth || 600) < 420
87
+ if (this.zoomLabel)
88
+ this.zoomLabel.style.display = compact ? 'none' : ''
89
+ }
90
+
91
+ apply(b, i) {
92
+ const xAxis = this.chart.xAxis[0]
93
+ if (b.type === 'all') {
94
+ this.selected = 'all'
95
+ //explicit full extremes so the initial xAxis.range window doesn't re-apply
96
+ xAxis.setExtremes(xAxis.dataMin, xAxis.dataMax)
97
+ } else {
98
+ const count = b.count || 1
99
+ //the window always ENDS at the last data point; going back a calendar
100
+ //month/year/day for the start. Anchoring the end on dataMax keeps the right edge flush with the
101
+ //data instead of snapping forward to the next month boundary (which left a gap on the right).
102
+ const max = xAxis.dataMax
103
+ const d = new Date(max)
104
+ if (b.type === 'year')
105
+ d.setUTCFullYear(d.getUTCFullYear() - count)
106
+ else if (b.type === 'month')
107
+ d.setUTCMonth(d.getUTCMonth() - count)
108
+ else
109
+ d.setUTCDate(d.getUTCDate() - count)
110
+ this.selected = i
111
+ xAxis.setExtremes(d.getTime(), max)
112
+ }
113
+ this.highlight()
114
+ this.reposition()
115
+ }
116
+
117
+ highlight() {
118
+ if (!this.buttons)
119
+ return
120
+ this.buttons.forEach(({btn, def, disabled}, i) => {
121
+ const active = (def.type === 'all' && this.selected === 'all') || this.selected === i
122
+ //charts-lib's own state classes (styled in chart.scss)
123
+ btn.classList.toggle('selected', active)
124
+ btn.classList.toggle('disabled', disabled)
125
+ })
126
+ }
127
+
128
+ //called after each (re)render to sit above the plot area
129
+ reposition() {
130
+ this.applyResponsiveSizing()
131
+ if (this.el)
132
+ this.el.style.left = (this.chart.plotLeft) + 'px'
133
+ }
134
+
135
+ destroy() {
136
+ if (this.el && this.el.parentNode)
137
+ this.el.parentNode.removeChild(this.el)
138
+ this.el = null
139
+ }
140
+ }
@@ -219,7 +219,7 @@ export function EffectDescription({effect, operation}) {
219
219
  case 'contractCodeUploaded':
220
220
  case 'contractCodeRestored':
221
221
  return <>Contract code {getEffectAction(effect, 'contractCode')}
222
- <LedgerKeyHint effect={effect}><ContractCodeWasm wasm={effect.wasm || effect.wasmHash}/></LedgerKeyHint></>
222
+ <LedgerKeyHint effect={effect}><ContractCodeWasm wasm={effect.wasmHash || effect.wasm}/></LedgerKeyHint></>
223
223
  case 'contractCreated':
224
224
  case 'contractUpdated':
225
225
  case 'contractRestored':
package/index.d.ts CHANGED
@@ -1269,3 +1269,39 @@ declare global {
1269
1269
  var explorerApiOrigin: string;
1270
1270
  var horizonOrigin: string;
1271
1271
  }
1272
+
1273
+ export interface ChartProps {
1274
+ /** Chart data and options */
1275
+ options?: Record<string, any>;
1276
+ /** Chart kind */
1277
+ type?: 'Chart' | 'StockChart';
1278
+ /** Chart title */
1279
+ title?: React.ReactNode;
1280
+ /** Render as inline-block (sparkline charts) */
1281
+ inline?: boolean;
1282
+ /** Apply data grouping */
1283
+ grouped?: boolean;
1284
+ /** Date range / range-selector */
1285
+ range?: true | false | 'year' | 'month';
1286
+ /** Hide the legend section */
1287
+ noLegend?: boolean;
1288
+ /** Container CSS class */
1289
+ container?: string;
1290
+ /** Additional CSS classes */
1291
+ className?: string;
1292
+ /** Inline styles */
1293
+ style?: React.CSSProperties;
1294
+ /** Additional engine modules */
1295
+ modules?: Function[];
1296
+ /** Optional header children */
1297
+ children?: React.ReactNode;
1298
+ }
1299
+
1300
+ /** Chart component */
1301
+ export const Chart: React.FC<ChartProps>;
1302
+
1303
+ /** Charting engine namespace (Chart, StockChart, setOptions, getOptions, Color, Axis, merge) */
1304
+ export const ChartEngine: Record<string, any>;
1305
+
1306
+ /** Placeholder shown while a chart's data is loading */
1307
+ export const ChartLoader: React.FC<{ title?: React.ReactNode; unavailable?: boolean; container?: string }>;
package/index.js CHANGED
@@ -93,3 +93,5 @@ export * from './filter/filter-view'
93
93
  export * from './stellar/key-type'
94
94
  export * from './stellar/signature-hint-utils'
95
95
  export * from './contract/sc-val'
96
+ //charts
97
+ export {default as Chart, ChartEngine, ChartLoader} from './charts'
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@stellar-expert/ui-framework",
3
- "version": "1.19.2",
3
+ "version": "1.20.0",
4
4
  "description": "StellarExpert shared UI components library",
5
5
  "main": "index.js",
6
6
  "module": "./index.js",
7
7
  "types": "index.d.ts",
8
8
  "sideEffects": [
9
- "*.scss"
9
+ "*.scss",
10
+ "./charts/chart-default-options.js"
10
11
  ],
11
12
  "keywords": [
12
13
  "StellarExpert",
@@ -48,6 +49,8 @@
48
49
  "api/*",
49
50
  "asset/*",
50
51
  "basic-styles/*",
52
+ "charts/*",
53
+ "charts/**/*",
51
54
  "claimable-balance/*",
52
55
  "contract/*",
53
56
  "controls/*",