react-klinecharts-ui 0.1.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.
package/README.md ADDED
@@ -0,0 +1,1132 @@
1
+ # react-klinecharts-ui — Library Reference
2
+
3
+ **react-klinecharts-ui** is a headless React library for building financial trading terminals on top of [klinecharts](https://github.com/liihuu/KLineChart). It provides a state provider, a set of hooks, and overlay templates. No UI components are included — use any UI framework you prefer.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Installation](#installation)
10
+ 2. [Concept](#concept)
11
+ 3. [KlinechartsUIProvider](#klinechartsui-provider)
12
+ 4. [Types](#types)
13
+ - [Datafeed](#datafeed)
14
+ - [PartialSymbolInfo](#partialsymbolinfo)
15
+ - [KlinechartsUIState](#klinechartsuistate)
16
+ - [KlinechartsUIAction](#klinechartsuiaction)
17
+ 5. [Hooks](#hooks)
18
+ - [useKlinechartsUI](#useklinechartsui)
19
+ - [useKlinechartsUITheme](#useklinechartsui-theme)
20
+ - [useKlinechartsUILoading](#useklinechartsui-loading)
21
+ - [usePeriods](#useperiods)
22
+ - [useTimezone](#usetimezone)
23
+ - [useSymbolSearch](#usesymbolsearch)
24
+ - [useIndicators](#useindicators)
25
+ - [useDrawingTools](#usedrawingtools)
26
+ - [useKlinechartsUISettings](#useklinechartsuisettings)
27
+ - [useScreenshot](#usescreenshot)
28
+ - [useFullscreen](#usefullscreen)
29
+ - [useOrderLines](#useorderlines)
30
+ 6. [Utilities](#utilities)
31
+ - [createDataLoader](#createdataloader)
32
+ 7. [Data & Constants](#data--constants)
33
+ 8. [Drawing Overlays](#drawing-overlays)
34
+ 9. [Extensions](#extensions)
35
+ 10. [State Callbacks](#state-callbacks)
36
+ 11. [Full Export List](#full-export-list)
37
+
38
+ ---
39
+
40
+ ## Installation
41
+
42
+ > **Note:** `react-klinecharts` is not yet published to the npm registry. Install it directly from GitHub:
43
+
44
+ ```bash
45
+ npm install react-klinecharts-ui github:NemeZZiZZ/react-klinecharts
46
+ # or with pnpm
47
+ pnpm add react-klinecharts-ui github:NemeZZiZZ/react-klinecharts
48
+ # or with yarn
49
+ yarn add react-klinecharts-ui github:NemeZZiZZ/react-klinecharts
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Concept
55
+
56
+ The library follows a **headless** pattern — all UI is written by the consumer. The library is responsible for:
57
+
58
+ - **State management** — current symbol, period, theme, indicators, timezone, screenshots
59
+ - **Datafeed integration** — abstract interface for loading historical data and subscribing to real-time updates
60
+ - **klinecharts overlay management** — indicators, drawing tools, order lines
61
+ - **Utilities** — `createDataLoader`, overlay templates
62
+
63
+ All hooks must be called inside `<KlinechartsUIProvider>`.
64
+
65
+ ---
66
+
67
+ ## KlinechartsUIProvider
68
+
69
+ The root provider. Wraps the application and supplies the context.
70
+
71
+ ```tsx
72
+ import { KlinechartsUIProvider } from "react-klinecharts-ui";
73
+
74
+ <KlinechartsUIProvider
75
+ datafeed={myDatafeed}
76
+ defaultSymbol={{ ticker: "BTCUSDT", pricePrecision: 2 }}
77
+ defaultTheme="dark"
78
+ overlays={[orderLine]}
79
+ onSymbolChange={(symbol) => saveToStorage("symbol", symbol)}
80
+ >
81
+ <App />
82
+ </KlinechartsUIProvider>;
83
+ ```
84
+
85
+ ### Props
86
+
87
+ | Prop | Type | Default | Description |
88
+ | ------------------------ | ---------------------------------------------- | ------------------ | ---------------------------------------------------------------- |
89
+ | `datafeed` | `Datafeed` | — | **Required.** Datafeed interface implementation |
90
+ | `defaultSymbol` | `PartialSymbolInfo` | `null` | Initial trading instrument |
91
+ | `defaultPeriod` | `TerminalPeriod` | First in `periods` | Initial timeframe |
92
+ | `defaultTheme` | `string` | `"light"` | Initial theme (`"light"` or `"dark"`) |
93
+ | `defaultTimezone` | `string` | `"Asia/Shanghai"` | Initial timezone (IANA) |
94
+ | `defaultMainIndicators` | `string[]` | `["MA"]` | Indicators on the main chart at startup |
95
+ | `defaultSubIndicators` | `string[]` | `["VOL"]` | Indicators on sub-panels at startup |
96
+ | `defaultLocale` | `string` | `"en-US"` | Locale passed to klinecharts |
97
+ | `periods` | `TerminalPeriod[]` | `DEFAULT_PERIODS` | List of available timeframes |
98
+ | `styles` | `DeepPartial<Styles>` | — | Custom klinecharts styles (applied when the chart is ready) |
99
+ | `registerExtensions` | `boolean` | `true` | Whether to register built-in drawing overlays |
100
+ | `overlays` | `OverlayTemplate[]` | — | Additional overlay templates (e.g. `orderLine`, custom overlays) |
101
+ | `onStateChange` | `(action, nextState, prevState) => void` | — | Called synchronously on every dispatched action |
102
+ | `onSymbolChange` | `(symbol) => void` | — | Called when the symbol changes |
103
+ | `onPeriodChange` | `(period) => void` | — | Called when the period changes |
104
+ | `onThemeChange` | `(theme) => void` | — | Called when the theme changes |
105
+ | `onTimezoneChange` | `(timezone) => void` | — | Called when the timezone changes |
106
+ | `onMainIndicatorsChange` | `(indicators: string[]) => void` | — | Called when main indicators change |
107
+ | `onSubIndicatorsChange` | `(indicators: Record<string, string>) => void` | — | Called when sub-indicators change |
108
+ | `onSettingsChange` | `(settings: Record<string, unknown>) => void` | — | Called when settings change via `useKlinechartsUISettings` |
109
+
110
+ ### Overlay registration
111
+
112
+ The provider registers overlays once on mount via `useRef` — passing an inline array is safe and does not cause re-registration:
113
+
114
+ ```tsx
115
+ // Safe — does not re-register on every render
116
+ <KlinechartsUIProvider overlays={[orderLine, myCustomOverlay]}>
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Types
122
+
123
+ ### Datafeed
124
+
125
+ Data interface implemented by the consumer.
126
+
127
+ ```typescript
128
+ interface Datafeed {
129
+ /**
130
+ * Search symbols by a query string.
131
+ * signal — AbortSignal to cancel the request when a newer query is typed.
132
+ */
133
+ searchSymbols(
134
+ search: string,
135
+ signal?: AbortSignal,
136
+ ): Promise<PartialSymbolInfo[]>;
137
+
138
+ /**
139
+ * Load historical bars.
140
+ * from/to — timestamps in milliseconds.
141
+ * When from=0, load the most recent available data.
142
+ */
143
+ getHistoryKLineData(
144
+ symbol: SymbolInfo,
145
+ period: TerminalPeriod,
146
+ from: number,
147
+ to: number,
148
+ ): Promise<KLineData[]>;
149
+
150
+ /**
151
+ * Subscribe to real-time updates.
152
+ * callback is called for every new bar.
153
+ */
154
+ subscribe(
155
+ symbol: SymbolInfo,
156
+ period: TerminalPeriod,
157
+ callback: (data: KLineData) => void,
158
+ ): void;
159
+
160
+ /** Unsubscribe from real-time updates. */
161
+ unsubscribe(symbol: SymbolInfo, period: TerminalPeriod): void;
162
+ }
163
+ ```
164
+
165
+ ### PartialSymbolInfo
166
+
167
+ Minimal description of a trading instrument.
168
+
169
+ ```typescript
170
+ interface PartialSymbolInfo {
171
+ ticker: string; // e.g. "BTCUSDT", "AAPL", "EUR/USD"
172
+ pricePrecision?: number; // Decimal places for price display
173
+ volumePrecision?: number; // Decimal places for volume display
174
+ [key: string]: unknown; // Any additional fields
175
+ }
176
+ ```
177
+
178
+ ### KlinechartsUIState
179
+
180
+ Complete provider state. Accessible via `useKlinechartsUI().state`.
181
+
182
+ ```typescript
183
+ interface KlinechartsUIState {
184
+ chart: Chart | null; // klinecharts Chart instance (null before onReady)
185
+ datafeed: Datafeed; // The datafeed passed to the provider
186
+ symbol: PartialSymbolInfo | null; // Current symbol
187
+ period: TerminalPeriod; // Current timeframe
188
+ theme: string; // Current theme: "light" | "dark"
189
+ timezone: string; // Current timezone (IANA)
190
+ isLoading: boolean; // true while data is loading
191
+ locale: string; // klinecharts locale ("en-US")
192
+ periods: TerminalPeriod[]; // List of available timeframes
193
+ mainIndicators: string[]; // Active main chart indicators
194
+ subIndicators: Record<string, string>; // Active sub-indicators: { name → paneId }
195
+ styles: DeepPartial<Styles> | undefined; // Custom klinecharts styles
196
+ screenshotUrl: string | null; // URL of the last screenshot
197
+ }
198
+ ```
199
+
200
+ ### KlinechartsUIAction
201
+
202
+ Union type of all possible actions for `dispatch`.
203
+
204
+ ```typescript
205
+ type KlinechartsUIAction =
206
+ | { type: "SET_CHART"; chart: Chart }
207
+ | { type: "SET_SYMBOL"; symbol: PartialSymbolInfo }
208
+ | { type: "SET_PERIOD"; period: TerminalPeriod }
209
+ | { type: "SET_THEME"; theme: string }
210
+ | { type: "SET_TIMEZONE"; timezone: string }
211
+ | { type: "SET_LOADING"; isLoading: boolean }
212
+ | { type: "SET_MAIN_INDICATORS"; indicators: string[] }
213
+ | { type: "SET_SUB_INDICATORS"; indicators: Record<string, string> }
214
+ | { type: "SET_STYLES"; styles: DeepPartial<Styles> | undefined }
215
+ | { type: "SET_LOCALE"; locale: string }
216
+ | { type: "SET_SCREENSHOT_URL"; url: string | null };
217
+ ```
218
+
219
+ ---
220
+
221
+ ## Hooks
222
+
223
+ ### useKlinechartsUI
224
+
225
+ The primary hook — returns the full context: state, dispatch, datafeed, and fullscreen ref.
226
+
227
+ ```typescript
228
+ const { state, dispatch, datafeed, onSettingsChange, fullscreenContainerRef } =
229
+ useKlinechartsUI();
230
+ ```
231
+
232
+ Return value:
233
+
234
+ ```typescript
235
+ interface KlinechartsUIContextValue {
236
+ state: KlinechartsUIState;
237
+ dispatch: Dispatch<KlinechartsUIAction>; // enhancedDispatch with synchronous callbacks
238
+ datafeed: Datafeed;
239
+ onSettingsChange?: (settings: Record<string, unknown>) => void;
240
+ fullscreenContainerRef: RefObject<HTMLElement | null>;
241
+ }
242
+ ```
243
+
244
+ > **Note:** `dispatch` is `enhancedDispatch`. It synchronously computes the new state by calling the pure reducer directly and immediately invokes `onStateChange` / individual callbacks — without waiting for a React re-render.
245
+
246
+ ---
247
+
248
+ ### useKlinechartsUITheme
249
+
250
+ Manage the chart theme.
251
+
252
+ ```typescript
253
+ const { theme, setTheme, toggleTheme } = useKlinechartsUITheme();
254
+ ```
255
+
256
+ | Field | Type | Description |
257
+ | ------------- | ------------------------- | ------------------------------------- |
258
+ | `theme` | `string` | Current theme: `"light"` or `"dark"` |
259
+ | `setTheme` | `(theme: string) => void` | Set a specific theme |
260
+ | `toggleTheme` | `() => void` | Toggle between `"light"` and `"dark"` |
261
+
262
+ ```tsx
263
+ const { theme, toggleTheme } = useKlinechartsUITheme();
264
+
265
+ <button onClick={toggleTheme}>
266
+ {theme === "dark" ? <SunIcon /> : <MoonIcon />}
267
+ </button>;
268
+ ```
269
+
270
+ ---
271
+
272
+ ### useKlinechartsUILoading
273
+
274
+ Loading state of chart data.
275
+
276
+ ```typescript
277
+ const { isLoading } = useKlinechartsUILoading();
278
+ ```
279
+
280
+ | Field | Type | Description |
281
+ | ----------- | --------- | ------------------------------------------------ |
282
+ | `isLoading` | `boolean` | `true` while `createDataLoader` is fetching bars |
283
+
284
+ ```tsx
285
+ const { isLoading } = useKlinechartsUILoading();
286
+
287
+ {
288
+ isLoading && <div className="spinner" />;
289
+ }
290
+ ```
291
+
292
+ ---
293
+
294
+ ### usePeriods
295
+
296
+ Manage timeframes.
297
+
298
+ ```typescript
299
+ const { periods, activePeriod, setPeriod } = usePeriods();
300
+ ```
301
+
302
+ | Field | Type | Description |
303
+ | -------------- | ---------------------------------- | --------------------------------- |
304
+ | `periods` | `TerminalPeriod[]` | Full list of available timeframes |
305
+ | `activePeriod` | `TerminalPeriod` | Currently selected timeframe |
306
+ | `setPeriod` | `(period: TerminalPeriod) => void` | Change the timeframe |
307
+
308
+ `TerminalPeriod` extends `KlinechartsPeriod` with a `label: string` field.
309
+
310
+ **Default timeframes:** 1m, 5m, 15m, 1H, 2H, 4H, D, W, M, Y
311
+
312
+ ```tsx
313
+ const { periods, activePeriod, setPeriod } = usePeriods();
314
+
315
+ <div className="flex gap-1">
316
+ {periods.map((p) => (
317
+ <button
318
+ key={p.label}
319
+ onClick={() => setPeriod(p)}
320
+ className={activePeriod.label === p.label ? "active" : ""}
321
+ >
322
+ {p.label}
323
+ </button>
324
+ ))}
325
+ </div>;
326
+ ```
327
+
328
+ ---
329
+
330
+ ### useTimezone
331
+
332
+ Manage the chart timezone.
333
+
334
+ ```typescript
335
+ const { timezones, activeTimezone, setTimezone } = useTimezone();
336
+ ```
337
+
338
+ | Field | Type | Description |
339
+ | ---------------- | ---------------------------- | ------------------------- |
340
+ | `timezones` | `TimezoneItem[]` | Full list of timezones |
341
+ | `activeTimezone` | `string` | Current IANA timezone key |
342
+ | `setTimezone` | `(timezone: string) => void` | Change the timezone |
343
+
344
+ ```typescript
345
+ interface TimezoneItem {
346
+ key: string; // IANA: "Europe/London", "America/New_York", "UTC"
347
+ localeKey: string; // Short name: "london", "new_york", "utc"
348
+ }
349
+ ```
350
+
351
+ **Available timezones:**
352
+ `UTC`, `Pacific/Honolulu`, `America/Juneau`, `America/Los_Angeles`, `America/Chicago`, `America/Toronto`, `America/Sao_Paulo`, `Europe/London`, `Europe/Berlin`, `Asia/Bahrain`, `Asia/Dubai`, `Asia/Ashkhabad`, `Asia/Almaty`, `Asia/Bangkok`, `Asia/Shanghai`, `Asia/Tokyo`, `Australia/Sydney`, `Pacific/Norfolk`
353
+
354
+ ```tsx
355
+ const { timezones, activeTimezone, setTimezone } = useTimezone();
356
+
357
+ <select value={activeTimezone} onChange={(e) => setTimezone(e.target.value)}>
358
+ {timezones.map((tz) => (
359
+ <option key={tz.key} value={tz.key}>
360
+ {tz.key}
361
+ </option>
362
+ ))}
363
+ </select>;
364
+ ```
365
+
366
+ ---
367
+
368
+ ### useSymbolSearch
369
+
370
+ Search and select a trading instrument.
371
+
372
+ ```typescript
373
+ const {
374
+ query,
375
+ results,
376
+ isSearching,
377
+ activeSymbol,
378
+ setQuery,
379
+ selectSymbol,
380
+ clearResults,
381
+ } = useSymbolSearch(debounceMs);
382
+ ```
383
+
384
+ | Parameter | Type | Default | Description |
385
+ | ------------ | -------- | ------- | --------------------------------------------- |
386
+ | `debounceMs` | `number` | `300` | Delay before calling `datafeed.searchSymbols` |
387
+
388
+ | Field | Type | Description |
389
+ | -------------- | ------------------------------------- | --------------------------------------------- |
390
+ | `query` | `string` | Current search query |
391
+ | `results` | `PartialSymbolInfo[]` | Results from the last search |
392
+ | `isSearching` | `boolean` | `true` while the request is in flight |
393
+ | `activeSymbol` | `PartialSymbolInfo \| null` | Currently selected symbol from `state.symbol` |
394
+ | `setQuery` | `(q: string) => void` | Update the query (triggers debounced search) |
395
+ | `selectSymbol` | `(symbol: PartialSymbolInfo) => void` | Select a symbol — dispatches `SET_SYMBOL` |
396
+ | `clearResults` | `() => void` | Clear search results |
397
+
398
+ The hook automatically cancels in-flight requests via `AbortController` on every new keystroke and on unmount.
399
+
400
+ ```tsx
401
+ const { query, results, isSearching, selectSymbol, setQuery } =
402
+ useSymbolSearch(300);
403
+
404
+ <input
405
+ value={query}
406
+ onChange={(e) => setQuery(e.target.value)}
407
+ placeholder="Search..."
408
+ />;
409
+ {
410
+ isSearching && <Spinner />;
411
+ }
412
+ {
413
+ results.map((sym) => (
414
+ <button key={sym.ticker} onClick={() => selectSymbol(sym)}>
415
+ {sym.ticker}
416
+ </button>
417
+ ));
418
+ }
419
+ ```
420
+
421
+ ---
422
+
423
+ ### useIndicators
424
+
425
+ Full indicator management: add/remove, visibility, parameters, move between panes.
426
+
427
+ ```typescript
428
+ const {
429
+ mainIndicators,
430
+ subIndicators,
431
+ activeMainIndicators,
432
+ activeSubIndicators,
433
+ availableMainIndicators,
434
+ availableSubIndicators,
435
+ addMainIndicator,
436
+ removeMainIndicator,
437
+ addSubIndicator,
438
+ removeSubIndicator,
439
+ toggleMainIndicator,
440
+ toggleSubIndicator,
441
+ moveToMain,
442
+ moveToSub,
443
+ setIndicatorVisible,
444
+ updateIndicatorParams,
445
+ getIndicatorParams,
446
+ isMainIndicatorActive,
447
+ isSubIndicatorActive,
448
+ } = useIndicators();
449
+ ```
450
+
451
+ #### Fields
452
+
453
+ | Field | Type | Description |
454
+ | ------------------------- | ------------------------ | ------------------------------------------ |
455
+ | `mainIndicators` | `IndicatorInfo[]` | All main indicators with `isActive` flag |
456
+ | `subIndicators` | `IndicatorInfo[]` | All sub-indicators with `isActive` flag |
457
+ | `activeMainIndicators` | `string[]` | Active main indicator names only |
458
+ | `activeSubIndicators` | `Record<string, string>` | Active sub-indicators: `{ name → paneId }` |
459
+ | `availableMainIndicators` | `string[]` | Full list from `MAIN_INDICATORS` |
460
+ | `availableSubIndicators` | `string[]` | Full list from `SUB_INDICATORS` |
461
+
462
+ ```typescript
463
+ interface IndicatorInfo {
464
+ name: string; // "MA", "MACD", "RSI", etc.
465
+ isActive: boolean; // Whether it is currently on the chart
466
+ }
467
+ ```
468
+
469
+ #### Methods
470
+
471
+ | Method | Description |
472
+ | --------------------------------------------- | ------------------------------------------------------------- |
473
+ | `addMainIndicator(name)` | Creates the indicator on `candle_pane` with id `main_${name}` |
474
+ | `removeMainIndicator(name)` | Removes the indicator and updates state |
475
+ | `addSubIndicator(name)` | Creates the indicator on a new sub-pane with id `sub_${name}` |
476
+ | `removeSubIndicator(name)` | Removes the sub-indicator and its pane |
477
+ | `toggleMainIndicator(name)` | `add` if inactive, `remove` if active |
478
+ | `toggleSubIndicator(name)` | Same for sub-indicators |
479
+ | `moveToMain(name)` | Moves from sub-pane to `candle_pane` |
480
+ | `moveToSub(name)` | Moves from `candle_pane` to a new sub-pane |
481
+ | `setIndicatorVisible(name, isMain, visible)` | Show/hide indicator via `chart.overrideIndicator` |
482
+ | `updateIndicatorParams(name, paneId, params)` | Update `calcParams` via `chart.overrideIndicator` |
483
+ | `getIndicatorParams(name)` | Returns `[{ label, defaultValue }]` or `[]` if no parameters |
484
+ | `isMainIndicatorActive(name)` | Quick active check |
485
+ | `isSubIndicatorActive(name)` | Quick active check |
486
+
487
+ #### Available indicators
488
+
489
+ **Main chart (`MAIN_INDICATORS`):** MA, EMA, SMA, BOLL, SAR, BBI
490
+
491
+ **Sub-panes (`SUB_INDICATORS`):** MA, EMA, VOL, MACD, BOLL, KDJ, RSI, BIAS, BRAR, CCI, DMI, CR, PSY, DMA, TRIX, OBV, VR, WR, MTM, EMV, SAR, SMA, ROC, PVT, BBI, AO
492
+
493
+ **Indicators with configurable parameters:** SMA, BOLL, SAR, BBI, MACD, KDJ, BRAR, CCI, DMI, CR, PSY, DMA, TRIX, OBV, VR, MTM, EMV, ROC, AO and others.
494
+
495
+ ---
496
+
497
+ ### useDrawingTools
498
+
499
+ Manage drawing tools (chart overlays).
500
+
501
+ ```typescript
502
+ const {
503
+ categories,
504
+ activeTool,
505
+ magnetMode,
506
+ isLocked,
507
+ isVisible,
508
+ selectTool,
509
+ clearActiveTool,
510
+ setMagnetMode,
511
+ toggleLock,
512
+ toggleVisibility,
513
+ removeAllDrawings,
514
+ } = useDrawingTools();
515
+ ```
516
+
517
+ | Field/Method | Type | Description |
518
+ | --------------------- | -------------------------------- | ------------------------------------------------------- |
519
+ | `categories` | `DrawingCategoryItem[]` | Tool categories with nested tools |
520
+ | `activeTool` | `string \| null` | Name of the last selected tool |
521
+ | `magnetMode` | `"normal" \| "weak" \| "strong"` | Snap-to-OHLC mode |
522
+ | `isLocked` | `boolean` | Whether all drawings are locked |
523
+ | `isVisible` | `boolean` | Whether all drawings are visible |
524
+ | `selectTool(name)` | — | Start drawing via `chart.createOverlay` |
525
+ | `clearActiveTool()` | — | Deselect tool (local state only) |
526
+ | `setMagnetMode(mode)` | — | Change magnet mode for all existing and future drawings |
527
+ | `toggleLock()` | — | Toggle lock on all drawings |
528
+ | `toggleVisibility()` | — | Show/hide all drawings |
529
+ | `removeAllDrawings()` | — | Remove all drawings in the `drawing_tools` group |
530
+
531
+ ```typescript
532
+ interface DrawingToolItem {
533
+ name: string; // klinecharts overlay name, e.g. "arrow", "fibonacciLine"
534
+ localeKey: string; // Localization key
535
+ }
536
+
537
+ interface DrawingCategoryItem {
538
+ key: string; // "singleLine" | "moreLine" | "polygon" | "fibonacci" | "wave"
539
+ tools: DrawingToolItem[];
540
+ }
541
+ ```
542
+
543
+ **Categories and tools:**
544
+
545
+ | Category (`key`) | Tools |
546
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
547
+ | `singleLine` | horizontalStraightLine, horizontalRayLine, horizontalSegment, verticalStraightLine, verticalRayLine, verticalSegment, straightLine, rayLine, segment, arrow, priceLine |
548
+ | `moreLine` | priceChannelLine, parallelStraightLine |
549
+ | `polygon` | circle, rect, parallelogram, triangle |
550
+ | `fibonacci` | fibonacciLine, fibonacciSegment, fibonacciCircle, fibonacciSpiral, fibonacciSpeedResistanceFan, fibonacciExtension, gannBox |
551
+ | `wave` | xabcd, abcd, threeWaves, fiveWaves, eightWaves, anyWaves |
552
+
553
+ ---
554
+
555
+ ### useKlinechartsUISettings
556
+
557
+ Manage chart appearance: candle type, colors, price marks, axes, grid, crosshair, tooltips.
558
+
559
+ ```typescript
560
+ const settings = useKlinechartsUISettings();
561
+ ```
562
+
563
+ #### State
564
+
565
+ | Field | Type | Default | Description |
566
+ | ------------------------ | --------------- | ---------------- | ------------------------- |
567
+ | `candleType` | `string` | `"candle_solid"` | Candle display type |
568
+ | `candleUpColor` | `string` | `"#2DC08E"` | Bullish candle color |
569
+ | `candleDownColor` | `string` | `"#F92855"` | Bearish candle color |
570
+ | `showLastPrice` | `boolean` | `true` | Last price mark on Y-axis |
571
+ | `showHighPrice` | `boolean` | `true` | High price mark |
572
+ | `showLowPrice` | `boolean` | `true` | Low price mark |
573
+ | `showIndicatorLastValue` | `boolean` | `true` | Indicator last value mark |
574
+ | `priceAxisType` | `PriceAxisType` | `"normal"` | Y-axis scale type |
575
+ | `reverseCoordinate` | `boolean` | `false` | Invert Y-axis |
576
+ | `showTimeAxis` | `boolean` | `true` | Show X-axis |
577
+ | `showGrid` | `boolean` | `true` | Show grid |
578
+ | `showCrosshair` | `boolean` | `true` | Show crosshair |
579
+ | `showCandleTooltip` | `boolean` | `true` | OHLCV tooltip |
580
+ | `showIndicatorTooltip` | `boolean` | `true` | Indicator tooltip |
581
+
582
+ **Candle types:** `candle_solid`, `candle_stroke`, `candle_up_stroke`, `candle_down_stroke`, `ohlc`, `area`
583
+
584
+ **Price axis types:** `"normal"`, `"percentage"`, `"log"`
585
+
586
+ #### Extra fields
587
+
588
+ | Field | Type | Description |
589
+ | ---------------- | --------------------------------------------- | ----------------------------------------------------- |
590
+ | `candleTypes` | `CandleTypeItem[]` | List of `{ key, localeKey }` for rendering a selector |
591
+ | `priceAxisTypes` | `{ key: PriceAxisType; localeKey: string }[]` | List for rendering a selector |
592
+
593
+ #### Setters
594
+
595
+ Each field has a corresponding setter: `setCandleType`, `setCandleUpColor`, `setCandleDownColor`, `setShowLastPrice`, `setShowHighPrice`, `setShowLowPrice`, `setShowIndicatorLastValue`, `setPriceAxisType`, `setReverseCoordinate`, `setShowTimeAxis`, `setShowGrid`, `setShowCrosshair`, `setShowCandleTooltip`, `setShowIndicatorTooltip`.
596
+
597
+ All setters immediately apply changes via `chart.setStyles(...)`.
598
+
599
+ | Method | Description |
600
+ | ------------------- | ----------------------------------------------------------- |
601
+ | `resetToDefaults()` | Reset all settings to defaults via `chart.setStyles(theme)` |
602
+
603
+ > **Important:** Settings are stored in local `useState` inside the hook — they are not part of the provider's reducer state. The hook must be called unconditionally (not only when a dialog is open) to preserve settings across dialog open/close cycles. Changes trigger `onSettingsChange` from the provider.
604
+
605
+ ---
606
+
607
+ ### useScreenshot
608
+
609
+ Capture and download a chart screenshot.
610
+
611
+ ```typescript
612
+ const { screenshotUrl, capture, download, clear } = useScreenshot();
613
+ ```
614
+
615
+ | Field/Method | Type | Description |
616
+ | --------------------- | ----------------------------- | ------------------------------------------------------ |
617
+ | `screenshotUrl` | `string \| null` | JPEG data URL of the last screenshot |
618
+ | `capture()` | `() => void` | Capture the current chart state |
619
+ | `download(filename?)` | `(filename?: string) => void` | Download as a file (default: `"chart-screenshot.jpg"`) |
620
+ | `clear()` | `() => void` | Clear `screenshotUrl` from state |
621
+
622
+ Screenshot is created via `chart.getConvertPictureUrl(true, "jpeg", bgColor)`. Background depends on the current theme: `#151517` for dark, `#ffffff` for light.
623
+
624
+ ```tsx
625
+ const { screenshotUrl, capture, download, clear } = useScreenshot();
626
+
627
+ <button onClick={capture}>Capture</button>;
628
+ {
629
+ screenshotUrl && (
630
+ <>
631
+ <img src={screenshotUrl} alt="chart" />
632
+ <button onClick={() => download()}>Download</button>
633
+ <button onClick={clear}>Close</button>
634
+ </>
635
+ );
636
+ }
637
+ ```
638
+
639
+ ---
640
+
641
+ ### useFullscreen
642
+
643
+ Toggle fullscreen mode. Uses `fullscreenContainerRef` from the provider.
644
+
645
+ ```typescript
646
+ const { isFullscreen, toggle, enter, exit, containerRef } = useFullscreen();
647
+ ```
648
+
649
+ | Field/Method | Type | Description |
650
+ | -------------- | -------------------------------- | ------------------------------ |
651
+ | `isFullscreen` | `boolean` | Current fullscreen state |
652
+ | `toggle()` | `() => void` | Toggle |
653
+ | `enter()` | `() => void` | Enter fullscreen |
654
+ | `exit()` | `() => void` | Exit fullscreen |
655
+ | `containerRef` | `RefObject<HTMLElement \| null>` | The same ref from the provider |
656
+
657
+ Supports cross-browser vendor prefixes (`webkit`, `ms`).
658
+
659
+ **Important:** `containerRef` must be assigned to the container element that should occupy the full screen (typically the root layout element):
660
+
661
+ ```tsx
662
+ const { containerRef, toggle, isFullscreen } = useFullscreen();
663
+
664
+ <div ref={containerRef as React.RefObject<HTMLDivElement>}>
665
+ <button onClick={toggle}>{isFullscreen ? "Exit" : "Fullscreen"}</button>
666
+ <ChartView />
667
+ </div>;
668
+ ```
669
+
670
+ ---
671
+
672
+ ### useOrderLines
673
+
674
+ Create and manage horizontal price level lines (order lines).
675
+
676
+ > **Requirement:** The `orderLine` overlay must be registered via `overlays={[orderLine]}` on the provider.
677
+
678
+ ```typescript
679
+ const {
680
+ createOrderLine,
681
+ updateOrderLine,
682
+ removeOrderLine,
683
+ removeAllOrderLines,
684
+ } = useOrderLines();
685
+ ```
686
+
687
+ #### createOrderLine
688
+
689
+ ```typescript
690
+ createOrderLine(options: OrderLineOptions): string | null
691
+ ```
692
+
693
+ Returns the `id` of the created line, or `null` if the chart is not ready.
694
+
695
+ ```typescript
696
+ interface OrderLineOptions extends OrderLineExtendData {
697
+ id?: string; // Auto-generated if omitted
698
+ price: number; // Price level
699
+ draggable?: boolean; // Allow drag to change price. Default: false
700
+ onPriceChange?: (price: number) => void; // Called when drag ends
701
+ }
702
+ ```
703
+
704
+ All `OrderLineExtendData` fields (see [orderLine extension](#orderline)) are accepted directly — they flow through as overlay `extendData`.
705
+
706
+ #### updateOrderLine
707
+
708
+ ```typescript
709
+ updateOrderLine(id: string, options: Partial<Omit<OrderLineOptions, "id">>): void
710
+ ```
711
+
712
+ Updates an existing line. Only pass the fields you want to change.
713
+
714
+ #### removeOrderLine / removeAllOrderLines
715
+
716
+ ```typescript
717
+ removeOrderLine(id: string): void
718
+ removeAllOrderLines(): void // Removes all overlays with name="orderLine"
719
+ ```
720
+
721
+ ```tsx
722
+ const { createOrderLine, updateOrderLine, removeOrderLine } = useOrderLines();
723
+
724
+ const id = createOrderLine({
725
+ price: 45000,
726
+ color: "#ff9900",
727
+ text: "Target",
728
+ line: { style: "dashed" },
729
+ mark: { bg: "#ff9900", color: "#fff" },
730
+ draggable: true,
731
+ onPriceChange: (newPrice) => console.log("Moved to:", newPrice),
732
+ });
733
+
734
+ updateOrderLine(id!, { color: "#00ff00" });
735
+ removeOrderLine(id!);
736
+ ```
737
+
738
+ ---
739
+
740
+ ## Utilities
741
+
742
+ ### createDataLoader
743
+
744
+ Creates a klinecharts `DataLoader` from a `Datafeed` instance.
745
+
746
+ ```typescript
747
+ function createDataLoader(
748
+ datafeed: Datafeed,
749
+ dispatch: Dispatch<KlinechartsUIAction>,
750
+ ): DataLoader;
751
+ ```
752
+
753
+ This bridges the `Datafeed` interface to the klinecharts native `DataLoader` format.
754
+
755
+ **Behavior:**
756
+
757
+ | klinecharts request type | Action |
758
+ | ------------------------ | ------------------------------------------------------------------------------------------------------- |
759
+ | `"init"` | Loads the latest ~1000 bars (`to=Date.now(), from=0`), saves `oldestTimestamp`, increments `currentGen` |
760
+ | `"forward"` | Loads bars older than `oldestTimestamp - 1` for left-scroll pagination |
761
+ | `"backward"` | Ignored (real-time handled via `subscribeBar`) |
762
+
763
+ **Race condition protection:** each `"init"` increments `currentGen`. If a `"forward"` request completes after a new `"init"` has started, the result is discarded.
764
+
765
+ ```tsx
766
+ function ChartView() {
767
+ const { state, dispatch, datafeed } = useKlinechartsUI();
768
+
769
+ const dataLoader = useMemo(
770
+ () => createDataLoader(datafeed, dispatch),
771
+ [datafeed, dispatch],
772
+ );
773
+
774
+ return (
775
+ <KLineChart
776
+ dataLoader={dataLoader}
777
+ symbol={state.symbol ?? undefined}
778
+ period={state.period}
779
+ locale={state.locale}
780
+ timezone={state.timezone}
781
+ styles={state.theme}
782
+ onReady={(chart) => dispatch({ type: "SET_CHART", chart })}
783
+ />
784
+ );
785
+ }
786
+ ```
787
+
788
+ ---
789
+
790
+ ## Data & Constants
791
+
792
+ All constants are exported from the root `index.ts`:
793
+
794
+ ### DEFAULT_PERIODS
795
+
796
+ ```typescript
797
+ const DEFAULT_PERIODS: TerminalPeriod[] = [
798
+ { span: 1, type: "minute", label: "1m" },
799
+ { span: 5, type: "minute", label: "5m" },
800
+ { span: 15, type: "minute", label: "15m" },
801
+ { span: 1, type: "hour", label: "1H" },
802
+ { span: 2, type: "hour", label: "2H" },
803
+ { span: 4, type: "hour", label: "4H" },
804
+ { span: 1, type: "day", label: "D" },
805
+ { span: 1, type: "week", label: "W" },
806
+ { span: 1, type: "month", label: "M" },
807
+ { span: 1, type: "year", label: "Y" },
808
+ ];
809
+ ```
810
+
811
+ ### TIMEZONES
812
+
813
+ Array of `TimezoneOption[]` — 18 timezones. Fields: `key` (IANA) and `localeKey` (short name).
814
+
815
+ ### MAIN_INDICATORS / SUB_INDICATORS
816
+
817
+ String arrays of indicator names used as the available-for-selection list.
818
+
819
+ ### INDICATOR_PARAMS
820
+
821
+ ```typescript
822
+ const INDICATOR_PARAMS: Record<string, IndicatorDefinition>;
823
+
824
+ interface IndicatorDefinition {
825
+ name: string;
826
+ localeKey: string;
827
+ params: { label: string; defaultValue: number }[];
828
+ }
829
+ ```
830
+
831
+ Used internally by `getIndicatorParams` inside `useIndicators`.
832
+
833
+ ### DRAWING_CATEGORIES
834
+
835
+ Array of drawing tool categories. Used by `useDrawingTools`.
836
+
837
+ ### CANDLE_TYPES
838
+
839
+ ```typescript
840
+ const CANDLE_TYPES: CandleTypeOption[] = [
841
+ { key: "candle_solid", localeKey: "candle_solid" },
842
+ { key: "candle_stroke", localeKey: "candle_stroke" },
843
+ { key: "candle_up_stroke", localeKey: "candle_up_stroke" },
844
+ { key: "candle_down_stroke", localeKey: "candle_down_stroke" },
845
+ { key: "ohlc", localeKey: "ohlc" },
846
+ { key: "area", localeKey: "area" },
847
+ ];
848
+ ```
849
+
850
+ ### PRICE_AXIS_TYPES
851
+
852
+ ```typescript
853
+ const PRICE_AXIS_TYPES = ["normal", "percentage", "log"] as const;
854
+ type PriceAxisType = "normal" | "percentage" | "log";
855
+ ```
856
+
857
+ ---
858
+
859
+ ## Drawing Overlays
860
+
861
+ `OverlayTemplate` instances for drawing tools. Automatically registered when `registerExtensions: true` (default).
862
+
863
+ **Named exports:**
864
+
865
+ | Export | Overlay name | Description |
866
+ | ----------------------------- | ------------------------------- | ------------------- |
867
+ | `arrow` | `"arrow"` | Arrow |
868
+ | `circle` | `"circle"` | Circle |
869
+ | `rect` | `"rect"` | Rectangle |
870
+ | `triangle` | `"triangle"` | Triangle |
871
+ | `parallelogram` | `"parallelogram"` | Parallelogram |
872
+ | `fibonacciCircle` | `"fibonacciCircle"` | Fibonacci circle |
873
+ | `fibonacciSegment` | `"fibonacciSegment"` | Fibonacci segment |
874
+ | `fibonacciSpiral` | `"fibonacciSpiral"` | Fibonacci spiral |
875
+ | `fibonacciSpeedResistanceFan` | `"fibonacciSpeedResistanceFan"` | Fibonacci fan |
876
+ | `fibonacciExtension` | `"fibonacciExtension"` | Fibonacci extension |
877
+ | `gannBox` | `"gannBox"` | Gann box |
878
+ | `threeWaves` | `"threeWaves"` | 3-wave pattern |
879
+ | `fiveWaves` | `"fiveWaves"` | 5-wave pattern |
880
+ | `eightWaves` | `"eightWaves"` | 8-wave pattern |
881
+ | `anyWaves` | `"anyWaves"` | Custom wave pattern |
882
+ | `abcd` | `"abcd"` | ABCD pattern |
883
+ | `xabcd` | `"xabcd"` | XABCD pattern |
884
+
885
+ ---
886
+
887
+ ## Extensions
888
+
889
+ ### registerExtensions
890
+
891
+ Registers all built-in drawing overlays via `registerOverlay`. Called automatically by the provider when `registerExtensions: true`.
892
+
893
+ ```typescript
894
+ import { registerExtensions } from "react-klinecharts-ui";
895
+ registerExtensions(); // Idempotent — repeated calls are ignored
896
+ ```
897
+
898
+ ### orderLine
899
+
900
+ Overlay template for horizontal price level lines. **Not registered automatically** — must be passed explicitly to the provider.
901
+
902
+ ```typescript
903
+ import { orderLine, KlinechartsUIProvider } from "react-klinecharts-ui";
904
+
905
+ <KlinechartsUIProvider overlays={[orderLine]}>...</KlinechartsUIProvider>;
906
+ ```
907
+
908
+ **Implementation details:**
909
+
910
+ - `needDefaultYAxisFigure: false` — custom rendering of the Y-axis mark
911
+ - `createYAxisFigures` — draws a colored price mark on the right axis
912
+ - `createPointFigures` — draws a horizontal line with an optional text label
913
+ - `performEventPressedMove` — updates `points[0].value` during drag
914
+ - Uses `chart.getSymbol()?.pricePrecision ?? 2` for price formatting
915
+
916
+ #### OrderLineExtendData
917
+
918
+ All fields are optional. Defaults produce an orange dashed line with a white-on-orange price mark.
919
+
920
+ ```typescript
921
+ interface OrderLineExtendData {
922
+ /** Primary color for line, mark bg, and label fallback. Default: "rgba(255, 165, 0, 0.85)" */
923
+ color?: string;
924
+ /** Optional text label displayed above the line. */
925
+ text?: string;
926
+ /** Line style overrides. */
927
+ line?: OrderLineLineStyle;
928
+ /** Y-axis price mark style overrides. */
929
+ mark?: OrderLineMarkStyle;
930
+ /** Text label style overrides. */
931
+ label?: OrderLineLabelStyle;
932
+ }
933
+
934
+ interface OrderLineLineStyle {
935
+ style?: "solid" | "dashed" | "dotted"; // Default: "dashed"
936
+ width?: number; // Default: 1
937
+ dashedValue?: [number, number]; // Default: [4, 2]
938
+ }
939
+
940
+ interface OrderLineMarkStyle {
941
+ color?: string; // Text color. Default: "#ffffff"
942
+ bg?: string; // Background (falls back to top-level color)
943
+ borderRadius?: number; // Default: 2
944
+ font?: OrderLineFontStyle;
945
+ padding?: OrderLinePadding;
946
+ }
947
+
948
+ interface OrderLineLabelStyle {
949
+ color?: string; // Text color (falls back to top-level color)
950
+ bg?: string; // Background. Default: "transparent"
951
+ borderRadius?: number; // Default: 0
952
+ font?: OrderLineFontStyle;
953
+ padding?: OrderLinePadding;
954
+ offset?: OrderLinePadding; // Position offset. Defaults: x=8, y=3
955
+ }
956
+
957
+ interface OrderLineFontStyle {
958
+ size?: number; // Default: 11
959
+ family?: string; // Default: "Helvetica Neue, Arial, sans-serif"
960
+ weight?: string; // Default: "bold" (mark) / "normal" (label)
961
+ }
962
+
963
+ interface OrderLinePadding {
964
+ x?: number;
965
+ y?: number;
966
+ }
967
+ ```
968
+
969
+ All sub-interfaces (`OrderLineLineStyle`, `OrderLineMarkStyle`, `OrderLineLabelStyle`, `OrderLineFontStyle`, `OrderLinePadding`) are exported as named types from both the main and `extensions` entry points.
970
+
971
+ ### overlays (array)
972
+
973
+ Array of all built-in drawing overlays — for direct access to the list:
974
+
975
+ ```typescript
976
+ import { overlays } from "react-klinecharts-ui";
977
+ // overlays === [arrow, circle, rect, triangle, ...] (17 items)
978
+ ```
979
+
980
+ ---
981
+
982
+ ## State Callbacks
983
+
984
+ Callbacks are invoked **synchronously** inside `enhancedDispatch`, before the React re-render — enabling immediate state persistence.
985
+
986
+ ```tsx
987
+ <KlinechartsUIProvider
988
+ datafeed={datafeed}
989
+ onStateChange={(action, nextState, prevState) => {
990
+ // Called for every dispatched action
991
+ console.log(action.type, nextState);
992
+ }}
993
+ onSymbolChange={(symbol) => {
994
+ localStorage.setItem('symbol', JSON.stringify(symbol));
995
+ }}
996
+ onPeriodChange={(period) => {
997
+ localStorage.setItem('period', JSON.stringify(period));
998
+ }}
999
+ onThemeChange={(theme) => {
1000
+ localStorage.setItem('theme', theme);
1001
+ }}
1002
+ onTimezoneChange={(timezone) => {
1003
+ localStorage.setItem('timezone', timezone);
1004
+ }}
1005
+ onMainIndicatorsChange={(indicators) => {
1006
+ localStorage.setItem('mainIndicators', JSON.stringify(indicators));
1007
+ }}
1008
+ onSubIndicatorsChange={(indicators) => {
1009
+ localStorage.setItem('subIndicators', JSON.stringify(indicators));
1010
+ }}
1011
+ onSettingsChange={(settings) => {
1012
+ // Called from useKlinechartsUISettings on every change
1013
+ localStorage.setItem('settings', JSON.stringify(settings));
1014
+ }}
1015
+ >
1016
+ ```
1017
+
1018
+ **Restoring persisted state:**
1019
+
1020
+ ```tsx
1021
+ const savedSymbol = JSON.parse(localStorage.getItem('symbol') ?? 'null');
1022
+ const savedTheme = localStorage.getItem('theme') ?? 'dark';
1023
+
1024
+ <KlinechartsUIProvider
1025
+ defaultSymbol={savedSymbol ?? { ticker: 'BTCUSDT' }}
1026
+ defaultTheme={savedTheme}
1027
+ >
1028
+ ```
1029
+
1030
+ ---
1031
+
1032
+ ## Full Export List
1033
+
1034
+ ```typescript
1035
+ // Provider & context
1036
+ export { KlinechartsUIProvider };
1037
+ export {
1038
+ useKlinechartsUI,
1039
+ KlinechartsUIStateContext,
1040
+ KlinechartsUIDispatchContext,
1041
+ };
1042
+ export type {
1043
+ KlinechartsUIOptions,
1044
+ KlinechartsUIState,
1045
+ KlinechartsUIAction,
1046
+ KlinechartsUIContextValue,
1047
+ KlinechartsUIDispatchValue,
1048
+ Datafeed,
1049
+ PartialSymbolInfo,
1050
+ };
1051
+
1052
+ // Hooks
1053
+ export { useKlinechartsUITheme, type UseKlinechartsUIThemeReturn };
1054
+ export { useKlinechartsUILoading, type UseKlinechartsUILoadingReturn };
1055
+ export { usePeriods, type UsePeriodsReturn };
1056
+ export { useTimezone, type UseTimezoneReturn, type TimezoneItem };
1057
+ export { useSymbolSearch, type UseSymbolSearchReturn };
1058
+ export { useIndicators, type UseIndicatorsReturn, type IndicatorInfo };
1059
+ export {
1060
+ useDrawingTools,
1061
+ type UseDrawingToolsReturn,
1062
+ type DrawingToolItem,
1063
+ type DrawingCategoryItem,
1064
+ };
1065
+ export {
1066
+ useKlinechartsUISettings,
1067
+ type UseKlinechartsUISettingsReturn,
1068
+ type KlinechartsUISettingsState,
1069
+ type CandleTypeItem,
1070
+ };
1071
+ export { useScreenshot, type UseScreenshotReturn };
1072
+ export { useFullscreen, type UseFullscreenReturn };
1073
+ export { useOrderLines, type UseOrderLinesReturn, type OrderLineOptions };
1074
+
1075
+ // Data
1076
+ export { DEFAULT_PERIODS, type TerminalPeriod };
1077
+ export { TIMEZONES, type TimezoneOption };
1078
+ export {
1079
+ MAIN_INDICATORS,
1080
+ SUB_INDICATORS,
1081
+ INDICATOR_PARAMS,
1082
+ type IndicatorParamConfig,
1083
+ type IndicatorDefinition,
1084
+ };
1085
+ export {
1086
+ DRAWING_CATEGORIES,
1087
+ type DrawingTool,
1088
+ type DrawingToolCategory,
1089
+ type MagnetMode,
1090
+ };
1091
+ export {
1092
+ CANDLE_TYPES,
1093
+ PRICE_AXIS_TYPES,
1094
+ type CandleTypeOption,
1095
+ type PriceAxisType,
1096
+ };
1097
+
1098
+ // Utilities
1099
+ export { createDataLoader };
1100
+
1101
+ // Drawing overlays (all 17)
1102
+ export {
1103
+ arrow,
1104
+ circle,
1105
+ rect,
1106
+ triangle,
1107
+ parallelogram,
1108
+ fibonacciCircle,
1109
+ fibonacciSegment,
1110
+ fibonacciSpiral,
1111
+ fibonacciSpeedResistanceFan,
1112
+ fibonacciExtension,
1113
+ gannBox,
1114
+ threeWaves,
1115
+ fiveWaves,
1116
+ eightWaves,
1117
+ anyWaves,
1118
+ abcd,
1119
+ xabcd,
1120
+ };
1121
+
1122
+ // Extensions
1123
+ export { registerExtensions, overlays, orderLine };
1124
+ export type {
1125
+ OrderLineExtendData,
1126
+ OrderLineLineStyle,
1127
+ OrderLineMarkStyle,
1128
+ OrderLineLabelStyle,
1129
+ OrderLineFontStyle,
1130
+ OrderLinePadding,
1131
+ };
1132
+ ```