@coderyo/ui-shell 1.0.0-rc.2

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,186 @@
1
+ import { Interval, SymbolSearchHit } from '@coderyo/data';
2
+ import { IndicatorConfig } from '@coderyo/indicators';
3
+ import * as _coderyo_drawings from '@coderyo/drawings';
4
+ import { DrawingRecord } from '@coderyo/drawings';
5
+
6
+ declare const GRID_SETTING_KEY = "tradview:settings:showGrid";
7
+ declare const RETURN_CURSOR_KEY = "tradview:settings:returnToCursorAfterDraw";
8
+ declare function loadShowGridPreference(): boolean;
9
+ declare function saveShowGridPreference(show: boolean): void;
10
+ declare function loadReturnToCursorPreference(): boolean;
11
+ declare function saveReturnToCursorPreference(v: boolean): void;
12
+ declare function loadIndicatorConfig(symbol: string, interval: string): IndicatorConfig;
13
+ declare function saveIndicatorConfig(symbol: string, interval: string, config: IndicatorConfig): void;
14
+
15
+ interface SettingsPanelOptions {
16
+ showGrid?: boolean;
17
+ onShowGridChange?: (show: boolean) => void;
18
+ returnToCursorAfterDraw?: boolean;
19
+ onReturnToCursorChange?: (v: boolean) => void;
20
+ indicatorConfig?: IndicatorConfig;
21
+ onIndicatorConfigChange?: (config: IndicatorConfig) => void;
22
+ }
23
+ declare function mountSettingsPanel(parent: HTMLElement, opts?: SettingsPanelOptions): HTMLElement;
24
+
25
+ type SymbolInputMode = 'manual' | 'search' | 'none';
26
+ interface TopBarOptions {
27
+ intervals?: Interval[];
28
+ initialSymbol?: string;
29
+ onSymbolSearch?: (query: string) => Promise<SymbolSearchHit[]>;
30
+ onSymbolSelect?: (symbol: string) => void;
31
+ onIntervalChange?: (interval: Interval) => void;
32
+ onThemeToggle?: () => void;
33
+ onFullscreen?: () => void;
34
+ onScreenshot?: () => void;
35
+ settings?: SettingsPanelOptions;
36
+ symbolInput?: SymbolInputMode;
37
+ showSettings?: boolean;
38
+ }
39
+ declare function mountTopBar(parent: HTMLElement, opts?: TopBarOptions): HTMLElement;
40
+
41
+ interface ContextMenuAction {
42
+ id: string;
43
+ label: string;
44
+ onClick: () => void;
45
+ }
46
+ interface ContextMenuOptions {
47
+ actions?: ContextMenuAction[];
48
+ }
49
+ declare function attachChartContextMenu(chartHost: HTMLElement, opts?: ContextMenuOptions): () => void;
50
+
51
+ interface OhlcvSnapshot {
52
+ o?: number;
53
+ h?: number;
54
+ l?: number;
55
+ c?: number;
56
+ v?: number;
57
+ }
58
+ interface StatusBarOptions {
59
+ connection?: string;
60
+ symbol?: string;
61
+ interval?: string;
62
+ ohlcv?: OhlcvSnapshot | null;
63
+ locale?: string;
64
+ onLocaleChange?: (locale: string) => void;
65
+ }
66
+ declare function mountStatusBar(parent: HTMLElement, opts?: StatusBarOptions): {
67
+ el: HTMLElement;
68
+ update: (patch: StatusBarOptions) => void;
69
+ };
70
+
71
+ interface CrosshairLegendOptions {
72
+ symbol?: string;
73
+ interval?: string;
74
+ }
75
+ declare function mountCrosshairLegend(chartHost: HTMLElement, opts?: CrosshairLegendOptions): {
76
+ el: HTMLElement;
77
+ update: (payload: {
78
+ time?: number;
79
+ ohlcv?: OhlcvSnapshot | null;
80
+ }) => void;
81
+ setMeta: (meta: CrosshairLegendOptions) => void;
82
+ hide: () => void;
83
+ };
84
+
85
+ interface DrawingPropertiesPanelOptions {
86
+ onStyleChange?: (patch: {
87
+ color?: string;
88
+ lineWidth?: number;
89
+ text?: string;
90
+ }) => void;
91
+ }
92
+ declare function mountDrawingPropertiesPanel(parent: HTMLElement, opts?: DrawingPropertiesPanelOptions): {
93
+ el: HTMLElement;
94
+ bind: (drawing: DrawingRecord | null) => void;
95
+ };
96
+
97
+ interface LayoutFeatures {
98
+ showTopBar?: boolean;
99
+ showLeftToolbar?: boolean;
100
+ showBottomToolbar?: boolean;
101
+ showCrosshairLegend?: boolean;
102
+ showStatusBar?: boolean;
103
+ showPropertiesPanel?: boolean;
104
+ showContextMenu?: boolean;
105
+ showSettings?: boolean;
106
+ showShortcuts?: boolean;
107
+ /** When TopBar is on and no search API: manual symbol input (default). */
108
+ symbolInput?: 'manual' | 'search' | 'none';
109
+ }
110
+ interface ResolvedLayoutFeatures {
111
+ showTopBar: boolean;
112
+ showLeftToolbar: boolean;
113
+ showBottomToolbar: boolean;
114
+ showCrosshairLegend: boolean;
115
+ showStatusBar: boolean;
116
+ showPropertiesPanel: boolean;
117
+ showContextMenu: boolean;
118
+ showSettings: boolean;
119
+ showShortcuts: boolean;
120
+ symbolInput: 'manual' | 'search' | 'none';
121
+ }
122
+ declare const DEFAULT_LAYOUT_FEATURES: ResolvedLayoutFeatures;
123
+ declare function resolveLayoutFeatures(opts?: ChartLayoutOptions): ResolvedLayoutFeatures;
124
+ declare function mergeLayoutFeatures(current: ResolvedLayoutFeatures, patch: LayoutFeatures): ResolvedLayoutFeatures;
125
+ /** Playground: enable full TV shell. */
126
+ declare function createDemoLayoutOptions(partial?: ChartLayoutOptions): ChartLayoutOptions;
127
+
128
+ type DrawingToolId = 'cursor' | 'trendline' | 'hline' | 'vline' | 'rectangle' | 'fibonacci' | 'text';
129
+ interface ChartLayoutOptions extends TopBarOptions {
130
+ showTopBar?: boolean;
131
+ showLeftToolbar?: boolean;
132
+ showBottomToolbar?: boolean;
133
+ activeDrawingTool?: DrawingToolId;
134
+ onDrawingToolSelect?: (tool: DrawingToolId) => void;
135
+ statusBar?: StatusBarOptions;
136
+ contextMenuActions?: ContextMenuAction[];
137
+ settings?: SettingsPanelOptions;
138
+ showStatusBar?: boolean;
139
+ showCrosshairLegend?: boolean;
140
+ showPropertiesPanel?: boolean;
141
+ showContextMenu?: boolean;
142
+ showSettings?: boolean;
143
+ showShortcuts?: boolean;
144
+ symbolInput?: 'manual' | 'search' | 'none';
145
+ onDrawingStyleChange?: (patch: {
146
+ color?: string;
147
+ lineWidth?: number;
148
+ text?: string;
149
+ }) => void;
150
+ onDrawingSelectionBind?: (bind: (drawing: _coderyo_drawings.DrawingRecord | null) => void) => void;
151
+ }
152
+ declare function mountChartLayout(root: HTMLElement, opts?: ChartLayoutOptions): {
153
+ chartHost: HTMLElement;
154
+ indicatorHost: HTMLElement;
155
+ topBar: HTMLElement;
156
+ statusBar: ReturnType<typeof mountStatusBar>;
157
+ crosshairLegend: ReturnType<typeof mountCrosshairLegend>;
158
+ detachContextMenu: () => void;
159
+ setActiveDrawingTool: (tool: DrawingToolId) => void;
160
+ propertiesPanel: ReturnType<typeof mountDrawingPropertiesPanel>;
161
+ setLayoutFeatures: (patch: LayoutFeatures) => void;
162
+ getLayoutFeatures: () => ResolvedLayoutFeatures;
163
+ };
164
+
165
+ interface SymbolSearchOptions {
166
+ onSearch: (query: string) => Promise<SymbolSearchHit[]>;
167
+ onSelect: (symbol: string) => void;
168
+ initialSymbol?: string;
169
+ }
170
+ declare function mountSymbolSearch(parent: HTMLElement, opts: SymbolSearchOptions): HTMLElement;
171
+
172
+ interface DrawingContextMenuHandlers {
173
+ onDelete?: () => void;
174
+ onCopy?: () => void;
175
+ onToggleLock?: () => void;
176
+ onDeselect?: () => void;
177
+ onEditText?: () => void;
178
+ }
179
+ declare function openDrawingContextMenu(clientX: number, clientY: number, drawing: DrawingRecord | null, handlers: DrawingContextMenuHandlers): () => void;
180
+
181
+ declare function bindShortcutsModal(): () => void;
182
+ declare function openShortcutsModal(): void;
183
+
184
+ declare function mountCodeSnippetPanel(parent: HTMLElement, getCode: () => string): HTMLElement;
185
+
186
+ export { type ChartLayoutOptions, type ContextMenuAction, type ContextMenuOptions, type CrosshairLegendOptions, DEFAULT_LAYOUT_FEATURES, type DrawingContextMenuHandlers, type DrawingPropertiesPanelOptions, type DrawingToolId, GRID_SETTING_KEY, type LayoutFeatures, type OhlcvSnapshot, RETURN_CURSOR_KEY, type ResolvedLayoutFeatures, type SettingsPanelOptions as SettingsMenuOptions, type SettingsPanelOptions, type StatusBarOptions, type SymbolInputMode, type SymbolSearchOptions, type TopBarOptions, attachChartContextMenu, bindShortcutsModal, createDemoLayoutOptions, loadIndicatorConfig, loadReturnToCursorPreference, loadShowGridPreference, mergeLayoutFeatures, mountChartLayout, mountCodeSnippetPanel, mountCrosshairLegend, mountDrawingPropertiesPanel, mountSettingsPanel as mountSettingsMenu, mountSettingsPanel, mountStatusBar, mountSymbolSearch, mountTopBar, openDrawingContextMenu, openShortcutsModal, resolveLayoutFeatures, saveIndicatorConfig, saveReturnToCursorPreference, saveShowGridPreference };
package/dist/index.js ADDED
@@ -0,0 +1,945 @@
1
+ // src/top-bar.ts
2
+ import { DEFAULT_INTERVALS } from "@coderyo/data";
3
+ import { t as t3 } from "@coderyo/i18n";
4
+
5
+ // src/user-preferences.ts
6
+ import { DEFAULT_INDICATOR_CONFIG, indicatorConfigStorageKey } from "@coderyo/indicators";
7
+ var GRID_SETTING_KEY = "tradview:settings:showGrid";
8
+ var RETURN_CURSOR_KEY = "tradview:settings:returnToCursorAfterDraw";
9
+ function loadShowGridPreference() {
10
+ try {
11
+ return localStorage.getItem(GRID_SETTING_KEY) === "1";
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+ function saveShowGridPreference(show) {
17
+ try {
18
+ localStorage.setItem(GRID_SETTING_KEY, show ? "1" : "0");
19
+ } catch {
20
+ }
21
+ }
22
+ function loadReturnToCursorPreference() {
23
+ try {
24
+ return localStorage.getItem(RETURN_CURSOR_KEY) === "1";
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ function saveReturnToCursorPreference(v) {
30
+ try {
31
+ localStorage.setItem(RETURN_CURSOR_KEY, v ? "1" : "0");
32
+ } catch {
33
+ }
34
+ }
35
+ function loadIndicatorConfig(symbol, interval) {
36
+ try {
37
+ const raw = localStorage.getItem(indicatorConfigStorageKey(symbol, interval));
38
+ if (!raw) return { ...DEFAULT_INDICATOR_CONFIG };
39
+ return { ...DEFAULT_INDICATOR_CONFIG, ...JSON.parse(raw) };
40
+ } catch {
41
+ return { ...DEFAULT_INDICATOR_CONFIG };
42
+ }
43
+ }
44
+ function saveIndicatorConfig(symbol, interval, config) {
45
+ try {
46
+ localStorage.setItem(indicatorConfigStorageKey(symbol, interval), JSON.stringify(config));
47
+ } catch {
48
+ }
49
+ }
50
+
51
+ // src/settings-panel.ts
52
+ import { DEFAULT_INDICATOR_CONFIG as DEFAULT_INDICATOR_CONFIG2 } from "@coderyo/indicators";
53
+ import { t } from "@coderyo/i18n";
54
+ function mountSettingsPanel(parent, opts = {}) {
55
+ let open = false;
56
+ let tab = "chart";
57
+ let showGrid = opts.showGrid ?? loadShowGridPreference();
58
+ let returnToCursor = opts.returnToCursorAfterDraw ?? loadReturnToCursorPreference();
59
+ let indicatorConfig = { ...opts.indicatorConfig ?? DEFAULT_INDICATOR_CONFIG2 };
60
+ const wrap = document.createElement("div");
61
+ wrap.style.cssText = "position:relative;";
62
+ const btn = document.createElement("button");
63
+ btn.type = "button";
64
+ btn.title = t("settings.title", "\u8A2D\u5B9A");
65
+ btn.textContent = "\u2699";
66
+ btn.style.cssText = "background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;padding:4px 10px;cursor:pointer;font-size:14px;";
67
+ const panel = document.createElement("div");
68
+ panel.style.cssText = "display:none;position:absolute;right:0;top:100%;margin-top:4px;width:280px;max-height:70vh;overflow:auto;padding:0;background:#161b22;border:1px solid #30363d;border-radius:6px;box-shadow:0 8px 24px #01040988;z-index:30;";
69
+ const tabs = document.createElement("div");
70
+ tabs.style.cssText = "display:flex;border-bottom:1px solid #30363d;";
71
+ const content = document.createElement("div");
72
+ content.style.cssText = "padding:10px 12px;font-size:12px;";
73
+ const tabIds = [
74
+ ["chart", t("settings.tab.chart", "\u5716\u8868")],
75
+ ["drawing", t("settings.tab.drawing", "\u7E6A\u5716")],
76
+ ["indicator", t("settings.tab.indicator", "\u6307\u6A19")]
77
+ ];
78
+ const renderTabs = () => {
79
+ tabs.replaceChildren();
80
+ for (const [id, label] of tabIds) {
81
+ const b = document.createElement("button");
82
+ b.type = "button";
83
+ b.textContent = label;
84
+ b.style.cssText = `flex:1;padding:8px;border:none;cursor:pointer;font-size:12px;${tab === id ? "background:#21262d;color:#e6edf3;" : "background:transparent;color:#8b949e;"}`;
85
+ b.onclick = (e) => {
86
+ e.stopPropagation();
87
+ tab = id;
88
+ renderTabs();
89
+ renderContent();
90
+ };
91
+ tabs.appendChild(b);
92
+ }
93
+ };
94
+ const checkbox = (label, checked, onChange) => {
95
+ const row = document.createElement("label");
96
+ row.style.cssText = "display:flex;align-items:center;gap:8px;margin-bottom:8px;cursor:pointer;color:#e6edf3;";
97
+ const input = document.createElement("input");
98
+ input.type = "checkbox";
99
+ input.checked = checked;
100
+ input.onchange = () => onChange(input.checked);
101
+ row.append(input, document.createTextNode(label));
102
+ return row;
103
+ };
104
+ const numberField = (label, value, onChange) => {
105
+ const row = document.createElement("label");
106
+ row.style.cssText = "display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;";
107
+ row.innerHTML = `<span style="color:#8b949e">${label}</span>`;
108
+ const input = document.createElement("input");
109
+ input.type = "number";
110
+ input.value = String(value);
111
+ input.style.cssText = "width:72px;padding:2px 6px;border-radius:4px;border:1px solid #30363d;background:#0d1117;color:#e6edf3;";
112
+ input.onchange = () => onChange(Number(input.value) || value);
113
+ row.appendChild(input);
114
+ return row;
115
+ };
116
+ const renderContent = () => {
117
+ content.replaceChildren();
118
+ if (tab === "chart") {
119
+ content.appendChild(
120
+ checkbox(t("settings.showGrid", "\u986F\u793A\u7DB2\u683C"), showGrid, (v) => {
121
+ showGrid = v;
122
+ saveShowGridPreference(v);
123
+ opts.onShowGridChange?.(v);
124
+ })
125
+ );
126
+ } else if (tab === "drawing") {
127
+ content.appendChild(
128
+ checkbox(t("settings.returnCursor", "\u756B\u5B8C\u5207\u56DE\u6E38\u6A19"), returnToCursor, (v) => {
129
+ returnToCursor = v;
130
+ saveReturnToCursorPreference(v);
131
+ opts.onReturnToCursorChange?.(v);
132
+ })
133
+ );
134
+ } else {
135
+ content.appendChild(
136
+ checkbox(t("settings.ind.macd", "MACD \u7A97\u683C"), indicatorConfig.showMacd, (v) => {
137
+ indicatorConfig = { ...indicatorConfig, showMacd: v };
138
+ opts.onIndicatorConfigChange?.(indicatorConfig);
139
+ })
140
+ );
141
+ content.appendChild(
142
+ checkbox(t("settings.ind.rsi", "RSI \u7A97\u683C"), indicatorConfig.showRsi, (v) => {
143
+ indicatorConfig = { ...indicatorConfig, showRsi: v };
144
+ opts.onIndicatorConfigChange?.(indicatorConfig);
145
+ })
146
+ );
147
+ content.appendChild(
148
+ checkbox(t("settings.ind.kdj", "KDJ \u7A97\u683C"), indicatorConfig.showKdj, (v) => {
149
+ indicatorConfig = { ...indicatorConfig, showKdj: v };
150
+ opts.onIndicatorConfigChange?.(indicatorConfig);
151
+ })
152
+ );
153
+ const src = document.createElement("label");
154
+ src.style.cssText = "display:flex;justify-content:space-between;margin:8px 0 6px;";
155
+ src.innerHTML = `<span style="color:#8b949e">${t("settings.ind.source", "\u6E90")}</span>`;
156
+ const sel = document.createElement("select");
157
+ sel.style.cssText = "background:#0d1117;color:#e6edf3;border:1px solid #30363d;border-radius:4px;padding:2px 6px;";
158
+ for (const v of ["close", "hlc3"]) {
159
+ const o = document.createElement("option");
160
+ o.value = v;
161
+ o.textContent = v;
162
+ sel.appendChild(o);
163
+ }
164
+ sel.value = indicatorConfig.source;
165
+ sel.onchange = () => {
166
+ indicatorConfig = { ...indicatorConfig, source: sel.value };
167
+ opts.onIndicatorConfigChange?.(indicatorConfig);
168
+ };
169
+ src.appendChild(sel);
170
+ content.appendChild(src);
171
+ content.appendChild(
172
+ numberField("MA", indicatorConfig.maPeriod, (n) => {
173
+ indicatorConfig = { ...indicatorConfig, maPeriod: n };
174
+ opts.onIndicatorConfigChange?.(indicatorConfig);
175
+ })
176
+ );
177
+ content.appendChild(
178
+ numberField("MACD fast", indicatorConfig.macdFast, (n) => {
179
+ indicatorConfig = { ...indicatorConfig, macdFast: n };
180
+ opts.onIndicatorConfigChange?.(indicatorConfig);
181
+ })
182
+ );
183
+ content.appendChild(
184
+ numberField("MACD slow", indicatorConfig.macdSlow, (n) => {
185
+ indicatorConfig = { ...indicatorConfig, macdSlow: n };
186
+ opts.onIndicatorConfigChange?.(indicatorConfig);
187
+ })
188
+ );
189
+ content.appendChild(
190
+ numberField("RSI", indicatorConfig.rsiPeriod, (n) => {
191
+ indicatorConfig = { ...indicatorConfig, rsiPeriod: n };
192
+ opts.onIndicatorConfigChange?.(indicatorConfig);
193
+ })
194
+ );
195
+ content.appendChild(
196
+ numberField("KDJ", indicatorConfig.kdjPeriod, (n) => {
197
+ indicatorConfig = { ...indicatorConfig, kdjPeriod: n };
198
+ opts.onIndicatorConfigChange?.(indicatorConfig);
199
+ })
200
+ );
201
+ }
202
+ };
203
+ renderTabs();
204
+ renderContent();
205
+ panel.append(tabs, content);
206
+ btn.onclick = (e) => {
207
+ e.stopPropagation();
208
+ open = !open;
209
+ panel.style.display = open ? "block" : "none";
210
+ };
211
+ const close = () => {
212
+ open = false;
213
+ panel.style.display = "none";
214
+ };
215
+ document.addEventListener("click", close);
216
+ panel.onclick = (e) => e.stopPropagation();
217
+ wrap.append(btn, panel);
218
+ parent.appendChild(wrap);
219
+ return wrap;
220
+ }
221
+
222
+ // src/symbol-search.ts
223
+ import { t as t2 } from "@coderyo/i18n";
224
+ function mountSymbolSearch(parent, opts) {
225
+ const wrap = document.createElement("div");
226
+ wrap.style.cssText = "display:flex;align-items:center;gap:6px;margin-right:8px;";
227
+ const input = document.createElement("input");
228
+ input.type = "search";
229
+ input.placeholder = t2("symbol.search", "\u641C\u5C0B\u5546\u54C1");
230
+ input.value = opts.initialSymbol ?? "";
231
+ input.style.cssText = "width:140px;padding:4px 8px;border-radius:4px;border:1px solid #30363d;background:#0d1117;color:#e6edf3;font-size:12px;";
232
+ const list = document.createElement("div");
233
+ list.style.cssText = "display:none;position:absolute;top:100%;left:0;z-index:20;min-width:200px;max-height:200px;overflow:auto;background:#161b22;border:1px solid #30363d;border-radius:4px;";
234
+ const box = document.createElement("div");
235
+ box.style.position = "relative";
236
+ box.append(input, list);
237
+ wrap.appendChild(box);
238
+ let timer;
239
+ const renderHits = (hits) => {
240
+ list.replaceChildren();
241
+ if (hits.length === 0) {
242
+ list.style.display = "none";
243
+ return;
244
+ }
245
+ for (const hit of hits) {
246
+ const row = document.createElement("button");
247
+ row.type = "button";
248
+ row.textContent = hit.exchange ? `${hit.symbol} \xB7 ${hit.exchange}` : hit.symbol;
249
+ row.style.cssText = "display:block;width:100%;text-align:left;padding:6px 10px;border:none;background:transparent;color:#e6edf3;cursor:pointer;font-size:12px;";
250
+ row.onmouseenter = () => {
251
+ row.style.background = "#21262d";
252
+ };
253
+ row.onmouseleave = () => {
254
+ row.style.background = "transparent";
255
+ };
256
+ row.onclick = () => {
257
+ input.value = hit.symbol;
258
+ list.style.display = "none";
259
+ opts.onSelect(hit.symbol);
260
+ };
261
+ list.appendChild(row);
262
+ }
263
+ list.style.display = "block";
264
+ };
265
+ input.addEventListener("input", () => {
266
+ clearTimeout(timer);
267
+ const q = input.value.trim();
268
+ if (q.length < 1) {
269
+ list.style.display = "none";
270
+ return;
271
+ }
272
+ timer = setTimeout(() => {
273
+ void opts.onSearch(q).then(renderHits).catch(() => {
274
+ list.style.display = "none";
275
+ });
276
+ }, 200);
277
+ });
278
+ document.addEventListener("click", (e) => {
279
+ if (!box.contains(e.target)) list.style.display = "none";
280
+ });
281
+ parent.appendChild(wrap);
282
+ return wrap;
283
+ }
284
+
285
+ // src/top-bar.ts
286
+ function mountManualSymbolInput(parent, opts) {
287
+ const wrap = document.createElement("div");
288
+ wrap.style.cssText = "display:flex;align-items:center;gap:4px;margin-right:8px;";
289
+ const input = document.createElement("input");
290
+ input.type = "text";
291
+ input.placeholder = t3("symbol.search", "Symbol");
292
+ input.value = opts.initialSymbol ?? "";
293
+ input.style.cssText = "width:140px;background:#0d1117;color:#e6edf3;border:1px solid #30363d;border-radius:4px;padding:4px 8px;font-size:12px;";
294
+ const apply = () => {
295
+ const v = input.value.trim();
296
+ if (v) opts.onSymbolSelect?.(v);
297
+ };
298
+ input.addEventListener("keydown", (e) => {
299
+ if (e.key === "Enter") apply();
300
+ });
301
+ const btn = document.createElement("button");
302
+ btn.type = "button";
303
+ btn.textContent = "\u21B5";
304
+ btn.title = "Apply symbol";
305
+ btn.style.cssText = "background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;padding:4px 8px;cursor:pointer;font-size:12px;";
306
+ btn.onclick = apply;
307
+ wrap.append(input, btn);
308
+ parent.appendChild(wrap);
309
+ }
310
+ function mountTopBar(parent, opts = {}) {
311
+ const bar = document.createElement("div");
312
+ bar.className = "tv-topbar";
313
+ bar.style.cssText = "display:flex;gap:8px;padding:8px 12px;align-items:center;border-bottom:1px solid #30363d;background:#161b22;";
314
+ const symbolMode = opts.symbolInput ?? "manual";
315
+ if (symbolMode === "search" && opts.onSymbolSearch && opts.onSymbolSelect) {
316
+ mountSymbolSearch(bar, {
317
+ initialSymbol: opts.initialSymbol,
318
+ onSearch: opts.onSymbolSearch,
319
+ onSelect: opts.onSymbolSelect
320
+ });
321
+ } else if (symbolMode === "manual" && opts.onSymbolSelect) {
322
+ mountManualSymbolInput(bar, {
323
+ initialSymbol: opts.initialSymbol,
324
+ onSymbolSelect: opts.onSymbolSelect
325
+ });
326
+ }
327
+ const intervals = opts.intervals ?? DEFAULT_INTERVALS;
328
+ for (const iv of intervals) {
329
+ const btn = document.createElement("button");
330
+ btn.type = "button";
331
+ btn.textContent = t3(`interval.${iv}`, iv);
332
+ btn.style.cssText = "background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;padding:4px 10px;cursor:pointer;font-size:12px;";
333
+ btn.onclick = () => opts.onIntervalChange?.(iv);
334
+ bar.appendChild(btn);
335
+ }
336
+ const spacer = document.createElement("div");
337
+ spacer.style.flex = "1";
338
+ bar.appendChild(spacer);
339
+ const mkBtn = (label, fn) => {
340
+ const b = document.createElement("button");
341
+ b.type = "button";
342
+ b.textContent = label;
343
+ b.style.cssText = "background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;padding:4px 10px;cursor:pointer;font-size:12px;";
344
+ b.onclick = () => fn?.();
345
+ return b;
346
+ };
347
+ bar.appendChild(mkBtn(t3("theme.dark", "\u4E3B\u984C"), opts.onThemeToggle));
348
+ if (opts.showSettings && opts.settings) mountSettingsPanel(bar, opts.settings);
349
+ bar.appendChild(mkBtn("\u26F6", opts.onFullscreen));
350
+ bar.appendChild(mkBtn("\u{1F4F7}", opts.onScreenshot));
351
+ parent.prepend(bar);
352
+ return bar;
353
+ }
354
+
355
+ // src/context-menu.ts
356
+ import { t as t4 } from "@coderyo/i18n";
357
+ function attachChartContextMenu(chartHost, opts = {}) {
358
+ let menu = null;
359
+ const close = () => {
360
+ menu?.remove();
361
+ menu = null;
362
+ };
363
+ const open = (x, y) => {
364
+ close();
365
+ menu = document.createElement("div");
366
+ menu.style.cssText = "position:fixed;z-index:50;min-width:160px;padding:4px 0;background:#161b22;border:1px solid #30363d;border-radius:6px;box-shadow:0 8px 24px #01040988;";
367
+ const actions = opts.actions ?? [
368
+ {
369
+ id: "fit",
370
+ label: t4("context.fitContent", "\u9069\u914D\u756B\u9762"),
371
+ onClick: () => {
372
+ }
373
+ }
374
+ ];
375
+ for (const action of actions) {
376
+ const row = document.createElement("button");
377
+ row.type = "button";
378
+ row.textContent = action.label;
379
+ row.style.cssText = "display:block;width:100%;text-align:left;padding:8px 12px;border:none;background:transparent;color:#e6edf3;font-size:12px;cursor:pointer;";
380
+ row.onmouseenter = () => {
381
+ row.style.background = "#21262d";
382
+ };
383
+ row.onmouseleave = () => {
384
+ row.style.background = "transparent";
385
+ };
386
+ row.onclick = () => {
387
+ action.onClick();
388
+ close();
389
+ };
390
+ menu.appendChild(row);
391
+ }
392
+ menu.style.left = `${x}px`;
393
+ menu.style.top = `${y}px`;
394
+ document.body.appendChild(menu);
395
+ };
396
+ const onContext = (e) => {
397
+ e.preventDefault();
398
+ open(e.clientX, e.clientY);
399
+ };
400
+ chartHost.addEventListener("contextmenu", onContext);
401
+ document.addEventListener("click", close);
402
+ document.addEventListener("scroll", close, true);
403
+ return () => {
404
+ chartHost.removeEventListener("contextmenu", onContext);
405
+ document.removeEventListener("click", close);
406
+ document.removeEventListener("scroll", close, true);
407
+ close();
408
+ };
409
+ }
410
+
411
+ // src/crosshair-legend.ts
412
+ function mountCrosshairLegend(chartHost, opts = {}) {
413
+ const box = document.createElement("div");
414
+ box.className = "tv-crosshair-legend";
415
+ box.style.cssText = "display:none;position:absolute;top:8px;left:8px;z-index:10;padding:6px 10px;border-radius:6px;background:#161b22e6;border:1px solid #30363d;font-size:11px;color:#e6edf3;pointer-events:none;line-height:1.5;";
416
+ const title = document.createElement("div");
417
+ title.style.cssText = "color:#8b949e;margin-bottom:2px;";
418
+ const body = document.createElement("div");
419
+ box.append(title, body);
420
+ chartHost.appendChild(box);
421
+ const fmt2 = (n) => n == null ? "\u2014" : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
422
+ const render = (payload) => {
423
+ const parts = [opts.symbol, opts.interval].filter(Boolean);
424
+ title.textContent = parts.length ? parts.join(" \xB7 ") : "";
425
+ const o = payload.ohlcv;
426
+ if (!o?.c && o?.o == null) {
427
+ box.style.display = "none";
428
+ return;
429
+ }
430
+ const timeStr = payload.time != null ? new Date(payload.time).toLocaleString() : "";
431
+ body.textContent = `${timeStr}
432
+ O ${fmt2(o?.o)} H ${fmt2(o?.h)} L ${fmt2(o?.l)} C ${fmt2(o?.c)}`;
433
+ box.style.display = "block";
434
+ };
435
+ return {
436
+ el: box,
437
+ update: render,
438
+ setMeta: (meta) => {
439
+ Object.assign(opts, meta);
440
+ title.textContent = [opts.symbol, opts.interval].filter(Boolean).join(" \xB7 ");
441
+ },
442
+ hide: () => {
443
+ box.style.display = "none";
444
+ }
445
+ };
446
+ }
447
+
448
+ // src/drawing-properties-panel.ts
449
+ import { t as t5 } from "@coderyo/i18n";
450
+ function mountDrawingPropertiesPanel(parent, opts = {}) {
451
+ const panel = document.createElement("aside");
452
+ panel.className = "tv-drawing-props";
453
+ panel.style.cssText = "display:none;width:220px;flex-shrink:0;border-left:1px solid #30363d;background:#161b22;padding:10px 12px;font-size:12px;color:#e6edf3;overflow:auto;";
454
+ const title = document.createElement("div");
455
+ title.textContent = t5("drawing.props.title", "\u7E6A\u5716\u5C6C\u6027");
456
+ title.style.cssText = "font-weight:600;margin-bottom:10px;";
457
+ const typeEl = document.createElement("div");
458
+ typeEl.style.color = "#8b949e";
459
+ typeEl.style.marginBottom = "10px";
460
+ const mkRow = (label) => {
461
+ const row = document.createElement("label");
462
+ row.style.cssText = "display:flex;flex-direction:column;gap:4px;margin-bottom:8px;";
463
+ const span = document.createElement("span");
464
+ span.textContent = label;
465
+ span.style.color = "#8b949e";
466
+ row.appendChild(span);
467
+ return row;
468
+ };
469
+ const colorRow = mkRow(t5("drawing.props.color", "\u984F\u8272"));
470
+ const colorInput = document.createElement("input");
471
+ colorInput.type = "color";
472
+ colorInput.style.cssText = "width:100%;height:28px;border:none;background:transparent;cursor:pointer;";
473
+ colorRow.appendChild(colorInput);
474
+ const widthRow = mkRow(t5("drawing.props.lineWidth", "\u7DDA\u5BEC"));
475
+ const widthInput = document.createElement("input");
476
+ widthInput.type = "range";
477
+ widthInput.min = "1";
478
+ widthInput.max = "6";
479
+ widthInput.style.width = "100%";
480
+ widthRow.appendChild(widthInput);
481
+ const textRow = mkRow(t5("drawing.props.text", "\u6587\u5B57"));
482
+ const textInput = document.createElement("input");
483
+ textInput.type = "text";
484
+ textInput.style.cssText = "padding:4px 8px;border-radius:4px;border:1px solid #30363d;background:#0d1117;color:#e6edf3;";
485
+ textRow.appendChild(textInput);
486
+ const lockedHint = document.createElement("div");
487
+ lockedHint.style.cssText = "color:#f78166;font-size:11px;margin-top:6px;display:none;";
488
+ lockedHint.textContent = t5("drawing.props.locked", "\u5DF2\u9396\u5B9A \u2014 \u89E3\u9396\u5F8C\u53EF\u7DE8\u8F2F");
489
+ panel.append(title, typeEl, colorRow, widthRow, textRow, lockedHint);
490
+ parent.appendChild(panel);
491
+ const emit = () => {
492
+ opts.onStyleChange?.({
493
+ color: colorInput.value,
494
+ lineWidth: Number(widthInput.value),
495
+ text: textInput.value
496
+ });
497
+ };
498
+ colorInput.oninput = emit;
499
+ widthInput.oninput = emit;
500
+ textInput.oninput = emit;
501
+ const bind = (drawing) => {
502
+ if (!drawing) {
503
+ panel.style.display = "none";
504
+ return;
505
+ }
506
+ panel.style.display = "block";
507
+ typeEl.textContent = `${t5("drawing.props.type", "\u985E\u578B")}: ${drawing.type}`;
508
+ const meta = drawing.meta ?? {};
509
+ colorInput.value = String(meta.color ?? "#58a6ff");
510
+ widthInput.value = String(meta.lineWidth ?? 2);
511
+ textInput.value = String(meta.text ?? "Note");
512
+ textRow.style.display = drawing.type === "text" ? "flex" : "none";
513
+ const locked = Boolean(meta.locked);
514
+ lockedHint.style.display = locked ? "block" : "none";
515
+ colorInput.disabled = locked;
516
+ widthInput.disabled = locked;
517
+ textInput.disabled = locked;
518
+ };
519
+ return { el: panel, bind };
520
+ }
521
+
522
+ // src/indicator-pane-host.ts
523
+ function mountIndicatorPaneHost(parent) {
524
+ const host = document.createElement("div");
525
+ host.dataset.tradviewIndicatorHost = "1";
526
+ host.style.cssText = "display:flex;flex-direction:column;flex:2;min-height:120px;border-top:1px solid #30363d;background:#0d1117;overflow:hidden;";
527
+ parent.appendChild(host);
528
+ return host;
529
+ }
530
+
531
+ // src/layout-features.ts
532
+ var DEFAULT_LAYOUT_FEATURES = {
533
+ showTopBar: false,
534
+ showLeftToolbar: false,
535
+ showBottomToolbar: false,
536
+ showCrosshairLegend: false,
537
+ showStatusBar: false,
538
+ showPropertiesPanel: false,
539
+ showContextMenu: false,
540
+ showSettings: false,
541
+ showShortcuts: false,
542
+ symbolInput: "manual"
543
+ };
544
+ function resolveLayoutFeatures(opts = {}) {
545
+ const d = DEFAULT_LAYOUT_FEATURES;
546
+ const top = opts.showTopBar ?? d.showTopBar;
547
+ return {
548
+ showTopBar: top,
549
+ showLeftToolbar: opts.showLeftToolbar ?? d.showLeftToolbar,
550
+ showBottomToolbar: opts.showBottomToolbar ?? d.showBottomToolbar,
551
+ showCrosshairLegend: opts.showCrosshairLegend ?? d.showCrosshairLegend,
552
+ showStatusBar: opts.showStatusBar ?? d.showStatusBar,
553
+ showPropertiesPanel: opts.showPropertiesPanel ?? d.showPropertiesPanel,
554
+ showContextMenu: opts.showContextMenu ?? d.showContextMenu,
555
+ showSettings: opts.settings !== void 0 ? opts.showSettings ?? true : opts.showSettings ?? d.showSettings,
556
+ showShortcuts: opts.showShortcuts ?? d.showShortcuts,
557
+ symbolInput: opts.symbolInput ?? (opts.onSymbolSearch ? "search" : d.symbolInput)
558
+ };
559
+ }
560
+ function mergeLayoutFeatures(current, patch) {
561
+ return resolveLayoutFeatures({
562
+ showTopBar: patch.showTopBar ?? current.showTopBar,
563
+ showLeftToolbar: patch.showLeftToolbar ?? current.showLeftToolbar,
564
+ showBottomToolbar: patch.showBottomToolbar ?? current.showBottomToolbar,
565
+ showCrosshairLegend: patch.showCrosshairLegend ?? current.showCrosshairLegend,
566
+ showStatusBar: patch.showStatusBar ?? current.showStatusBar,
567
+ showPropertiesPanel: patch.showPropertiesPanel ?? current.showPropertiesPanel,
568
+ showContextMenu: patch.showContextMenu ?? current.showContextMenu,
569
+ showSettings: patch.showSettings ?? current.showSettings,
570
+ showShortcuts: patch.showShortcuts ?? current.showShortcuts,
571
+ symbolInput: patch.symbolInput ?? current.symbolInput
572
+ });
573
+ }
574
+ function createDemoLayoutOptions(partial = {}) {
575
+ return {
576
+ ...partial,
577
+ showTopBar: partial.showTopBar ?? true,
578
+ showLeftToolbar: partial.showLeftToolbar ?? true,
579
+ showCrosshairLegend: partial.showCrosshairLegend ?? true,
580
+ showStatusBar: partial.showStatusBar ?? true,
581
+ showPropertiesPanel: partial.showPropertiesPanel ?? true,
582
+ showContextMenu: partial.showContextMenu ?? true,
583
+ showSettings: partial.showSettings ?? true,
584
+ showShortcuts: partial.showShortcuts ?? true,
585
+ showBottomToolbar: partial.showBottomToolbar ?? true,
586
+ symbolInput: partial.symbolInput ?? (partial.onSymbolSearch ? "search" : "manual")
587
+ };
588
+ }
589
+
590
+ // src/status-bar.ts
591
+ import { getLocale, setLocale, t as t6 } from "@coderyo/i18n";
592
+ function fmt(n, digits = 2) {
593
+ if (n == null || Number.isNaN(n)) return "\u2014";
594
+ return n.toLocaleString(void 0, { maximumFractionDigits: digits });
595
+ }
596
+ function mountStatusBar(parent, opts = {}) {
597
+ const bar = document.createElement("div");
598
+ bar.className = "tv-statusbar";
599
+ bar.style.cssText = "display:flex;align-items:center;gap:14px;padding:6px 12px;font-size:11px;color:#8b949e;border-top:1px solid #30363d;background:#161b22;flex-shrink:0;";
600
+ const conn = document.createElement("span");
601
+ const sym = document.createElement("span");
602
+ const ohlcv = document.createElement("span");
603
+ ohlcv.style.flex = "1";
604
+ ohlcv.style.color = "#e6edf3";
605
+ const localeWrap = document.createElement("label");
606
+ localeWrap.style.cssText = "display:flex;align-items:center;gap:4px;margin-left:auto;";
607
+ const localeLabel = document.createElement("span");
608
+ localeLabel.textContent = t6("status.locale", "\u8A9E\u7CFB");
609
+ const localeSelect = document.createElement("select");
610
+ localeSelect.style.cssText = "background:#0d1117;color:#e6edf3;border:1px solid #30363d;border-radius:4px;font-size:11px;padding:2px 4px;";
611
+ for (const loc of ["zh-TW", "en"]) {
612
+ const opt = document.createElement("option");
613
+ opt.value = loc;
614
+ opt.textContent = loc;
615
+ localeSelect.appendChild(opt);
616
+ }
617
+ localeSelect.value = opts.locale ?? getLocale();
618
+ localeSelect.onchange = () => {
619
+ setLocale(localeSelect.value);
620
+ opts.onLocaleChange?.(localeSelect.value);
621
+ localeLabel.textContent = t6("status.locale", "\u8A9E\u7CFB");
622
+ render(opts);
623
+ };
624
+ localeWrap.append(localeLabel, localeSelect);
625
+ bar.append(conn, sym, ohlcv, localeWrap);
626
+ parent.appendChild(bar);
627
+ const render = (state) => {
628
+ const merged = { ...opts, ...state };
629
+ conn.textContent = `${t6("status.connection", "\u9023\u7DDA")}\uFF1A${merged.connection ?? "\u2014"}`;
630
+ const parts = [merged.symbol, merged.interval].filter(Boolean);
631
+ sym.textContent = parts.length ? parts.join(" \xB7 ") : "";
632
+ const o = merged.ohlcv;
633
+ if (o && (o.o != null || o.c != null)) {
634
+ ohlcv.textContent = `O ${fmt(o.o)} H ${fmt(o.h)} L ${fmt(o.l)} C ${fmt(o.c)} V ${fmt(o.v, 0)}`;
635
+ } else {
636
+ ohlcv.textContent = t6("status.ohlcvHint", "\u79FB\u52D5\u5341\u5B57\u7DDA\u67E5\u770B OHLCV");
637
+ }
638
+ };
639
+ render(opts);
640
+ return {
641
+ el: bar,
642
+ update: (patch) => {
643
+ Object.assign(opts, patch);
644
+ render(opts);
645
+ }
646
+ };
647
+ }
648
+
649
+ // src/shortcuts-modal.ts
650
+ import { t as t7 } from "@coderyo/i18n";
651
+ var SHORTCUTS = [
652
+ { key: "\u2196 / Esc", desc: "\u6E38\u6A19\uFF08\u9078\u53D6/\u7DE8\u8F2F\u7E6A\u5716\uFF09" },
653
+ { key: "Delete", desc: "\u522A\u9664\u9078\u4E2D\u7E6A\u5716" },
654
+ { key: "R", desc: "\u9069\u914D\u756B\u9762" },
655
+ { key: "End", desc: "\u8DF3\u5230\u6700\u65B0 K \u7DDA" },
656
+ { key: "F", desc: "\u5168\u87A2\u5E55" },
657
+ { key: "S", desc: "\u622A\u5716 PNG" },
658
+ { key: "L", desc: "\u5C0D\u6578\u50F9\u683C\u8EF8" },
659
+ { key: "T", desc: "\u5207\u63DB\u4E3B\u984C" },
660
+ { key: "?", desc: "\u672C\u8AAA\u660E" }
661
+ ];
662
+ function bindShortcutsModal() {
663
+ const handler = (e) => {
664
+ if (e.key !== "?" || e.ctrlKey || e.metaKey) return;
665
+ const tag = e.target?.tagName;
666
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return;
667
+ e.preventDefault();
668
+ openShortcutsModal();
669
+ };
670
+ window.addEventListener("keydown", handler);
671
+ return () => window.removeEventListener("keydown", handler);
672
+ }
673
+ function openShortcutsModal() {
674
+ const backdrop = document.createElement("div");
675
+ backdrop.style.cssText = "position:fixed;inset:0;z-index:100;background:#01040999;display:flex;align-items:center;justify-content:center;";
676
+ const box = document.createElement("div");
677
+ box.style.cssText = "min-width:320px;max-width:90vw;padding:16px 20px;background:#161b22;border:1px solid #30363d;border-radius:8px;color:#e6edf3;";
678
+ const h = document.createElement("h2");
679
+ h.textContent = t7("shortcuts.title", "\u5FEB\u6377\u9375");
680
+ h.style.cssText = "font-size:16px;margin:0 0 12px;";
681
+ const list = document.createElement("div");
682
+ list.style.cssText = "font-size:13px;line-height:1.8;";
683
+ for (const s of SHORTCUTS) {
684
+ const row = document.createElement("div");
685
+ row.innerHTML = `<kbd style="background:#21262d;padding:2px 6px;border-radius:4px;margin-right:8px;">${s.key}</kbd>${s.desc}`;
686
+ list.appendChild(row);
687
+ }
688
+ const closeBtn = document.createElement("button");
689
+ closeBtn.type = "button";
690
+ closeBtn.textContent = t7("shortcuts.close", "\u95DC\u9589");
691
+ closeBtn.style.cssText = "margin-top:14px;padding:6px 14px;background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;cursor:pointer;";
692
+ const close = () => backdrop.remove();
693
+ closeBtn.onclick = close;
694
+ backdrop.onclick = (e) => {
695
+ if (e.target === backdrop) close();
696
+ };
697
+ box.append(h, list, closeBtn);
698
+ backdrop.appendChild(box);
699
+ document.body.appendChild(backdrop);
700
+ }
701
+
702
+ // src/chart-layout.ts
703
+ var DRAWING_TOOLS = [
704
+ { id: "cursor", label: "\u2196" },
705
+ { id: "trendline", label: "\u2571" },
706
+ { id: "hline", label: "\u2500" },
707
+ { id: "vline", label: "\u2502" },
708
+ { id: "rectangle", label: "\u25AD" },
709
+ { id: "fibonacci", label: "\u03C6" },
710
+ { id: "text", label: "T" }
711
+ ];
712
+ var MOBILE_MQ = "(max-width: 768px)";
713
+ function mountToolButtons(parent, activeTool, onSelect, layout) {
714
+ const btnStyle = layout === "column" ? "width:36px;height:32px;background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;cursor:pointer;font-size:14px;" : "min-width:40px;height:36px;background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;cursor:pointer;font-size:14px;padding:0 8px;";
715
+ const activeStyle = btnStyle.replace("#21262d", "#388bfd").replace("#e6edf3", "#fff");
716
+ const buttons = /* @__PURE__ */ new Map();
717
+ for (const tool of DRAWING_TOOLS) {
718
+ const btn = document.createElement("button");
719
+ btn.type = "button";
720
+ btn.title = tool.id;
721
+ btn.textContent = tool.label;
722
+ btn.style.cssText = activeTool === tool.id ? activeStyle : btnStyle;
723
+ btn.onclick = () => onSelect(tool.id);
724
+ buttons.set(tool.id, btn);
725
+ parent.appendChild(btn);
726
+ }
727
+ return {
728
+ setActive: (tool) => {
729
+ for (const [id, btn] of buttons) {
730
+ btn.style.cssText = id === tool ? activeStyle : btnStyle;
731
+ }
732
+ }
733
+ };
734
+ }
735
+ function mountChartLayout(root, opts = {}) {
736
+ let layoutFeatures = resolveLayoutFeatures(opts);
737
+ root.style.display = "flex";
738
+ root.style.flexDirection = "column";
739
+ root.style.height = "100%";
740
+ root.style.background = "#0d1117";
741
+ let activeTool = opts.activeDrawingTool ?? "cursor";
742
+ let setActiveDesktop = null;
743
+ let setActiveMobile = null;
744
+ const setActiveDrawingTool = (tool) => {
745
+ activeTool = tool;
746
+ setActiveDesktop?.(tool);
747
+ setActiveMobile?.(tool);
748
+ };
749
+ const onToolSelect = (tool) => {
750
+ setActiveDrawingTool(tool);
751
+ opts.onDrawingToolSelect?.(tool);
752
+ };
753
+ const body = document.createElement("div");
754
+ body.style.cssText = "display:flex;flex:1;min-height:0;";
755
+ const leftAside = document.createElement("aside");
756
+ leftAside.style.cssText = "width:48px;border-right:1px solid #30363d;background:#161b22;display:flex;flex-direction:column;align-items:center;padding:8px 4px;gap:8px;flex-shrink:0;z-index:20;";
757
+ const bottomBar = document.createElement("div");
758
+ bottomBar.style.cssText = "display:none;flex-shrink:0;gap:6px;padding:6px 8px;border-top:1px solid #30363d;background:#161b22;overflow-x:auto;";
759
+ const chartColumn = document.createElement("div");
760
+ chartColumn.style.cssText = "display:flex;flex-direction:column;flex:1;min-height:0;";
761
+ const chartHost = document.createElement("div");
762
+ chartHost.style.cssText = "flex:1;min-height:0;width:100%;height:100%;position:relative;overflow:hidden;";
763
+ chartColumn.appendChild(chartHost);
764
+ const indicatorHost = mountIndicatorPaneHost(chartColumn);
765
+ const statusBar = mountStatusBar(chartColumn, opts.statusBar ?? {});
766
+ body.appendChild(chartColumn);
767
+ const propertiesPanel = mountDrawingPropertiesPanel(body, {
768
+ onStyleChange: opts.onDrawingStyleChange
769
+ });
770
+ opts.onDrawingSelectionBind?.(propertiesPanel.bind);
771
+ root.appendChild(body);
772
+ root.appendChild(bottomBar);
773
+ let topBar = document.createElement("div");
774
+ topBar.style.display = "none";
775
+ root.insertBefore(topBar, body);
776
+ const crosshairLegend = mountCrosshairLegend(chartHost, { symbol: opts.initialSymbol });
777
+ let detachContextMenu = () => {
778
+ };
779
+ let shortcutsBound = false;
780
+ const mountLeftToolbar = () => {
781
+ if (leftAside.parentElement) return;
782
+ const desktopTools = mountToolButtons(leftAside, activeTool, onToolSelect, "column");
783
+ setActiveDesktop = desktopTools.setActive;
784
+ body.insertBefore(leftAside, chartColumn);
785
+ bottomBar.style.flexDirection = "row";
786
+ const mobileTools = mountToolButtons(bottomBar, activeTool, onToolSelect, "row");
787
+ setActiveMobile = mobileTools.setActive;
788
+ const mq = window.matchMedia(MOBILE_MQ);
789
+ const applyLayout = () => {
790
+ const mobile = mq.matches;
791
+ leftAside.style.display = mobile ? "none" : "flex";
792
+ const showBottom = layoutFeatures.showBottomToolbar !== false;
793
+ bottomBar.style.display = mobile && showBottom ? "flex" : "none";
794
+ };
795
+ mq.addEventListener("change", applyLayout);
796
+ applyLayout();
797
+ };
798
+ const unmountLeftToolbar = () => {
799
+ leftAside.remove();
800
+ bottomBar.innerHTML = "";
801
+ setActiveDesktop = null;
802
+ setActiveMobile = null;
803
+ };
804
+ const applyLayoutFeatures = () => {
805
+ const f = layoutFeatures;
806
+ if (f.showTopBar) {
807
+ topBar.remove();
808
+ topBar = mountTopBar(root, {
809
+ ...opts,
810
+ symbolInput: f.symbolInput,
811
+ showSettings: f.showSettings
812
+ });
813
+ } else {
814
+ topBar.style.display = "none";
815
+ }
816
+ if (f.showLeftToolbar) mountLeftToolbar();
817
+ else unmountLeftToolbar();
818
+ crosshairLegend.el.style.display = f.showCrosshairLegend ? "" : "none";
819
+ statusBar.el.style.display = f.showStatusBar ? "" : "none";
820
+ propertiesPanel.el.style.display = f.showPropertiesPanel ? "" : "none";
821
+ detachContextMenu();
822
+ if (f.showContextMenu) {
823
+ detachContextMenu = attachChartContextMenu(chartHost, {
824
+ actions: opts.contextMenuActions
825
+ });
826
+ }
827
+ if (f.showShortcuts && !shortcutsBound) {
828
+ bindShortcutsModal();
829
+ shortcutsBound = true;
830
+ }
831
+ };
832
+ applyLayoutFeatures();
833
+ return {
834
+ chartHost,
835
+ indicatorHost,
836
+ topBar,
837
+ statusBar,
838
+ crosshairLegend,
839
+ detachContextMenu,
840
+ setActiveDrawingTool,
841
+ propertiesPanel,
842
+ setLayoutFeatures: (patch) => {
843
+ layoutFeatures = mergeLayoutFeatures(layoutFeatures, patch);
844
+ applyLayoutFeatures();
845
+ },
846
+ getLayoutFeatures: () => ({ ...layoutFeatures })
847
+ };
848
+ }
849
+
850
+ // src/drawing-context-menu.ts
851
+ import { t as t8 } from "@coderyo/i18n";
852
+ function openDrawingContextMenu(clientX, clientY, drawing, handlers) {
853
+ const menu = document.createElement("div");
854
+ menu.style.cssText = "position:fixed;z-index:60;min-width:168px;padding:4px 0;background:#161b22;border:1px solid #30363d;border-radius:6px;box-shadow:0 8px 24px #01040988;";
855
+ menu.style.left = `${clientX}px`;
856
+ menu.style.top = `${clientY}px`;
857
+ const add = (label, fn, disabled = false) => {
858
+ if (!fn) return;
859
+ const btn = document.createElement("button");
860
+ btn.type = "button";
861
+ btn.textContent = label;
862
+ btn.disabled = disabled;
863
+ btn.style.cssText = "display:block;width:100%;text-align:left;padding:8px 12px;border:none;background:transparent;color:#e6edf3;font-size:12px;cursor:pointer;";
864
+ if (disabled) btn.style.opacity = "0.45";
865
+ btn.onclick = () => {
866
+ fn();
867
+ close();
868
+ };
869
+ menu.appendChild(btn);
870
+ };
871
+ if (drawing) {
872
+ const locked = Boolean(drawing.meta?.locked);
873
+ add(t8("drawing.ctx.delete", "\u522A\u9664"), handlers.onDelete);
874
+ add(t8("drawing.ctx.copy", "\u8907\u88FD"), handlers.onCopy);
875
+ add(
876
+ locked ? t8("drawing.ctx.unlock", "\u89E3\u9396") : t8("drawing.ctx.lock", "\u9396\u5B9A"),
877
+ handlers.onToggleLock
878
+ );
879
+ if (drawing.type === "text") {
880
+ add(t8("drawing.ctx.editText", "\u7DE8\u8F2F\u6587\u5B57"), handlers.onEditText);
881
+ }
882
+ add(t8("drawing.ctx.deselect", "\u53D6\u6D88\u9078\u53D6"), handlers.onDeselect);
883
+ }
884
+ document.body.appendChild(menu);
885
+ const close = () => {
886
+ menu.remove();
887
+ document.removeEventListener("click", close);
888
+ };
889
+ setTimeout(() => document.addEventListener("click", close), 0);
890
+ return close;
891
+ }
892
+
893
+ // src/code-snippet-panel.ts
894
+ function mountCodeSnippetPanel(parent, getCode) {
895
+ const wrap = document.createElement("details");
896
+ wrap.style.cssText = "flex-shrink:0;border-top:1px solid #30363d;background:#161b22;padding:6px 12px;font-size:11px;color:#8b949e;";
897
+ const summary = document.createElement("summary");
898
+ summary.textContent = "\u5D4C\u5165\u7A0B\u5F0F\u78BC\uFF08\u6574\u5408\u65B9\uFF09";
899
+ summary.style.cssText = "cursor:pointer;color:#58a6ff;user-select:none;";
900
+ const pre = document.createElement("pre");
901
+ pre.style.cssText = "margin:8px 0 0;padding:10px;background:#0d1117;border:1px solid #30363d;border-radius:6px;color:#e6edf3;font-size:11px;overflow:auto;max-height:160px;white-space:pre-wrap;";
902
+ const copyBtn = document.createElement("button");
903
+ copyBtn.type = "button";
904
+ copyBtn.textContent = "\u8907\u88FD";
905
+ copyBtn.style.cssText = "margin-top:6px;padding:4px 10px;background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;cursor:pointer;font-size:11px;";
906
+ copyBtn.onclick = () => {
907
+ const code = getCode();
908
+ pre.textContent = code;
909
+ void navigator.clipboard.writeText(code);
910
+ };
911
+ wrap.ontoggle = () => {
912
+ if (wrap.open) pre.textContent = getCode();
913
+ };
914
+ wrap.append(summary, pre, copyBtn);
915
+ parent.appendChild(wrap);
916
+ return wrap;
917
+ }
918
+ export {
919
+ DEFAULT_LAYOUT_FEATURES,
920
+ GRID_SETTING_KEY,
921
+ RETURN_CURSOR_KEY,
922
+ attachChartContextMenu,
923
+ bindShortcutsModal,
924
+ createDemoLayoutOptions,
925
+ loadIndicatorConfig,
926
+ loadReturnToCursorPreference,
927
+ loadShowGridPreference,
928
+ mergeLayoutFeatures,
929
+ mountChartLayout,
930
+ mountCodeSnippetPanel,
931
+ mountCrosshairLegend,
932
+ mountDrawingPropertiesPanel,
933
+ mountSettingsPanel as mountSettingsMenu,
934
+ mountSettingsPanel,
935
+ mountStatusBar,
936
+ mountSymbolSearch,
937
+ mountTopBar,
938
+ openDrawingContextMenu,
939
+ openShortcutsModal,
940
+ resolveLayoutFeatures,
941
+ saveIndicatorConfig,
942
+ saveReturnToCursorPreference,
943
+ saveShowGridPreference
944
+ };
945
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/top-bar.ts","../src/user-preferences.ts","../src/settings-panel.ts","../src/symbol-search.ts","../src/context-menu.ts","../src/crosshair-legend.ts","../src/drawing-properties-panel.ts","../src/indicator-pane-host.ts","../src/layout-features.ts","../src/status-bar.ts","../src/shortcuts-modal.ts","../src/chart-layout.ts","../src/drawing-context-menu.ts","../src/code-snippet-panel.ts"],"sourcesContent":["import { DEFAULT_INTERVALS, type Interval, type SymbolSearchHit } from '@coderyo/data';\nimport { t } from '@coderyo/i18n';\nimport { mountSettingsMenu, type SettingsMenuOptions } from './settings-menu.js';\nimport { mountSymbolSearch } from './symbol-search.js';\n\nexport type SymbolInputMode = 'manual' | 'search' | 'none';\n\nexport interface TopBarOptions {\n intervals?: Interval[];\n initialSymbol?: string;\n onSymbolSearch?: (query: string) => Promise<SymbolSearchHit[]>;\n onSymbolSelect?: (symbol: string) => void;\n onIntervalChange?: (interval: Interval) => void;\n onThemeToggle?: () => void;\n onFullscreen?: () => void;\n onScreenshot?: () => void;\n settings?: SettingsMenuOptions;\n symbolInput?: SymbolInputMode;\n showSettings?: boolean;\n}\n\nfunction mountManualSymbolInput(\n parent: HTMLElement,\n opts: { initialSymbol?: string; onSymbolSelect?: (symbol: string) => void },\n): void {\n const wrap = document.createElement('div');\n wrap.style.cssText = 'display:flex;align-items:center;gap:4px;margin-right:8px;';\n const input = document.createElement('input');\n input.type = 'text';\n input.placeholder = t('symbol.search', 'Symbol');\n input.value = opts.initialSymbol ?? '';\n input.style.cssText =\n 'width:140px;background:#0d1117;color:#e6edf3;border:1px solid #30363d;border-radius:4px;padding:4px 8px;font-size:12px;';\n const apply = () => {\n const v = input.value.trim();\n if (v) opts.onSymbolSelect?.(v);\n };\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') apply();\n });\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.textContent = '↵';\n btn.title = 'Apply symbol';\n btn.style.cssText =\n 'background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;padding:4px 8px;cursor:pointer;font-size:12px;';\n btn.onclick = apply;\n wrap.append(input, btn);\n parent.appendChild(wrap);\n}\n\nexport function mountTopBar(parent: HTMLElement, opts: TopBarOptions = {}): HTMLElement {\n const bar = document.createElement('div');\n bar.className = 'tv-topbar';\n bar.style.cssText =\n 'display:flex;gap:8px;padding:8px 12px;align-items:center;border-bottom:1px solid #30363d;background:#161b22;';\n\n const symbolMode = opts.symbolInput ?? 'manual';\n if (symbolMode === 'search' && opts.onSymbolSearch && opts.onSymbolSelect) {\n mountSymbolSearch(bar, {\n initialSymbol: opts.initialSymbol,\n onSearch: opts.onSymbolSearch,\n onSelect: opts.onSymbolSelect,\n });\n } else if (symbolMode === 'manual' && opts.onSymbolSelect) {\n mountManualSymbolInput(bar, {\n initialSymbol: opts.initialSymbol,\n onSymbolSelect: opts.onSymbolSelect,\n });\n }\n\n const intervals = opts.intervals ?? DEFAULT_INTERVALS;\n for (const iv of intervals) {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.textContent = t(`interval.${iv}`, iv);\n btn.style.cssText =\n 'background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;padding:4px 10px;cursor:pointer;font-size:12px;';\n btn.onclick = () => opts.onIntervalChange?.(iv);\n bar.appendChild(btn);\n }\n\n const spacer = document.createElement('div');\n spacer.style.flex = '1';\n bar.appendChild(spacer);\n\n const mkBtn = (label: string, fn?: () => void) => {\n const b = document.createElement('button');\n b.type = 'button';\n b.textContent = label;\n b.style.cssText =\n 'background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;padding:4px 10px;cursor:pointer;font-size:12px;';\n b.onclick = () => fn?.();\n return b;\n };\n\n bar.appendChild(mkBtn(t('theme.dark', '主題'), opts.onThemeToggle));\n if (opts.showSettings && opts.settings) mountSettingsMenu(bar, opts.settings);\n bar.appendChild(mkBtn('⛶', opts.onFullscreen));\n bar.appendChild(mkBtn('📷', opts.onScreenshot));\n\n parent.prepend(bar);\n return bar;\n}","import type { IndicatorConfig } from '@coderyo/indicators';\nimport { DEFAULT_INDICATOR_CONFIG, indicatorConfigStorageKey } from '@coderyo/indicators';\n\nexport const GRID_SETTING_KEY = 'tradview:settings:showGrid';\nexport const RETURN_CURSOR_KEY = 'tradview:settings:returnToCursorAfterDraw';\n\nexport function loadShowGridPreference(): boolean {\n try {\n return localStorage.getItem(GRID_SETTING_KEY) === '1';\n } catch {\n return false;\n }\n}\n\nexport function saveShowGridPreference(show: boolean): void {\n try {\n localStorage.setItem(GRID_SETTING_KEY, show ? '1' : '0');\n } catch {\n /* ignore */\n }\n}\n\nexport function loadReturnToCursorPreference(): boolean {\n try {\n return localStorage.getItem(RETURN_CURSOR_KEY) === '1';\n } catch {\n return false;\n }\n}\n\nexport function saveReturnToCursorPreference(v: boolean): void {\n try {\n localStorage.setItem(RETURN_CURSOR_KEY, v ? '1' : '0');\n } catch {\n /* ignore */\n }\n}\n\nexport function loadIndicatorConfig(symbol: string, interval: string): IndicatorConfig {\n try {\n const raw = localStorage.getItem(indicatorConfigStorageKey(symbol, interval));\n if (!raw) return { ...DEFAULT_INDICATOR_CONFIG };\n return { ...DEFAULT_INDICATOR_CONFIG, ...JSON.parse(raw) };\n } catch {\n return { ...DEFAULT_INDICATOR_CONFIG };\n }\n}\n\nexport function saveIndicatorConfig(\n symbol: string,\n interval: string,\n config: IndicatorConfig,\n): void {\n try {\n localStorage.setItem(indicatorConfigStorageKey(symbol, interval), JSON.stringify(config));\n } catch {\n /* ignore */\n }\n}","import { DEFAULT_INDICATOR_CONFIG, type IndicatorConfig } from '@coderyo/indicators';\nimport { t } from '@coderyo/i18n';\nimport {\n loadReturnToCursorPreference,\n loadShowGridPreference,\n saveReturnToCursorPreference,\n saveShowGridPreference,\n} from './user-preferences.js';\n\nexport interface SettingsPanelOptions {\n showGrid?: boolean;\n onShowGridChange?: (show: boolean) => void;\n returnToCursorAfterDraw?: boolean;\n onReturnToCursorChange?: (v: boolean) => void;\n indicatorConfig?: IndicatorConfig;\n onIndicatorConfigChange?: (config: IndicatorConfig) => void;\n}\n\nexport function mountSettingsPanel(parent: HTMLElement, opts: SettingsPanelOptions = {}): HTMLElement {\n let open = false;\n let tab: 'chart' | 'drawing' | 'indicator' = 'chart';\n let showGrid = opts.showGrid ?? loadShowGridPreference();\n let returnToCursor = opts.returnToCursorAfterDraw ?? loadReturnToCursorPreference();\n let indicatorConfig = { ...(opts.indicatorConfig ?? DEFAULT_INDICATOR_CONFIG) };\n\n const wrap = document.createElement('div');\n wrap.style.cssText = 'position:relative;';\n\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.title = t('settings.title', '設定');\n btn.textContent = '⚙';\n btn.style.cssText =\n 'background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;padding:4px 10px;cursor:pointer;font-size:14px;';\n\n const panel = document.createElement('div');\n panel.style.cssText =\n 'display:none;position:absolute;right:0;top:100%;margin-top:4px;width:280px;max-height:70vh;overflow:auto;padding:0;background:#161b22;border:1px solid #30363d;border-radius:6px;box-shadow:0 8px 24px #01040988;z-index:30;';\n\n const tabs = document.createElement('div');\n tabs.style.cssText = 'display:flex;border-bottom:1px solid #30363d;';\n const content = document.createElement('div');\n content.style.cssText = 'padding:10px 12px;font-size:12px;';\n\n const tabIds = [\n ['chart', t('settings.tab.chart', '圖表')],\n ['drawing', t('settings.tab.drawing', '繪圖')],\n ['indicator', t('settings.tab.indicator', '指標')],\n ] as const;\n\n const renderTabs = () => {\n tabs.replaceChildren();\n for (const [id, label] of tabIds) {\n const b = document.createElement('button');\n b.type = 'button';\n b.textContent = label;\n b.style.cssText = `flex:1;padding:8px;border:none;cursor:pointer;font-size:12px;${\n tab === id ? 'background:#21262d;color:#e6edf3;' : 'background:transparent;color:#8b949e;'\n }`;\n b.onclick = (e) => {\n e.stopPropagation();\n tab = id;\n renderTabs();\n renderContent();\n };\n tabs.appendChild(b);\n }\n };\n\n const checkbox = (label: string, checked: boolean, onChange: (v: boolean) => void) => {\n const row = document.createElement('label');\n row.style.cssText =\n 'display:flex;align-items:center;gap:8px;margin-bottom:8px;cursor:pointer;color:#e6edf3;';\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = checked;\n input.onchange = () => onChange(input.checked);\n row.append(input, document.createTextNode(label));\n return row;\n };\n\n const numberField = (label: string, value: number, onChange: (n: number) => void) => {\n const row = document.createElement('label');\n row.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;';\n row.innerHTML = `<span style=\"color:#8b949e\">${label}</span>`;\n const input = document.createElement('input');\n input.type = 'number';\n input.value = String(value);\n input.style.cssText =\n 'width:72px;padding:2px 6px;border-radius:4px;border:1px solid #30363d;background:#0d1117;color:#e6edf3;';\n input.onchange = () => onChange(Number(input.value) || value);\n row.appendChild(input);\n return row;\n };\n\n const renderContent = () => {\n content.replaceChildren();\n if (tab === 'chart') {\n content.appendChild(\n checkbox(t('settings.showGrid', '顯示網格'), showGrid, (v) => {\n showGrid = v;\n saveShowGridPreference(v);\n opts.onShowGridChange?.(v);\n }),\n );\n } else if (tab === 'drawing') {\n content.appendChild(\n checkbox(t('settings.returnCursor', '畫完切回游標'), returnToCursor, (v) => {\n returnToCursor = v;\n saveReturnToCursorPreference(v);\n opts.onReturnToCursorChange?.(v);\n }),\n );\n } else {\n content.appendChild(\n checkbox(t('settings.ind.macd', 'MACD 窗格'), indicatorConfig.showMacd, (v) => {\n indicatorConfig = { ...indicatorConfig, showMacd: v };\n opts.onIndicatorConfigChange?.(indicatorConfig);\n }),\n );\n content.appendChild(\n checkbox(t('settings.ind.rsi', 'RSI 窗格'), indicatorConfig.showRsi, (v) => {\n indicatorConfig = { ...indicatorConfig, showRsi: v };\n opts.onIndicatorConfigChange?.(indicatorConfig);\n }),\n );\n content.appendChild(\n checkbox(t('settings.ind.kdj', 'KDJ 窗格'), indicatorConfig.showKdj, (v) => {\n indicatorConfig = { ...indicatorConfig, showKdj: v };\n opts.onIndicatorConfigChange?.(indicatorConfig);\n }),\n );\n const src = document.createElement('label');\n src.style.cssText = 'display:flex;justify-content:space-between;margin:8px 0 6px;';\n src.innerHTML = `<span style=\"color:#8b949e\">${t('settings.ind.source', '源')}</span>`;\n const sel = document.createElement('select');\n sel.style.cssText =\n 'background:#0d1117;color:#e6edf3;border:1px solid #30363d;border-radius:4px;padding:2px 6px;';\n for (const v of ['close', 'hlc3'] as const) {\n const o = document.createElement('option');\n o.value = v;\n o.textContent = v;\n sel.appendChild(o);\n }\n sel.value = indicatorConfig.source;\n sel.onchange = () => {\n indicatorConfig = { ...indicatorConfig, source: sel.value as IndicatorConfig['source'] };\n opts.onIndicatorConfigChange?.(indicatorConfig);\n };\n src.appendChild(sel);\n content.appendChild(src);\n content.appendChild(\n numberField('MA', indicatorConfig.maPeriod, (n) => {\n indicatorConfig = { ...indicatorConfig, maPeriod: n };\n opts.onIndicatorConfigChange?.(indicatorConfig);\n }),\n );\n content.appendChild(\n numberField('MACD fast', indicatorConfig.macdFast, (n) => {\n indicatorConfig = { ...indicatorConfig, macdFast: n };\n opts.onIndicatorConfigChange?.(indicatorConfig);\n }),\n );\n content.appendChild(\n numberField('MACD slow', indicatorConfig.macdSlow, (n) => {\n indicatorConfig = { ...indicatorConfig, macdSlow: n };\n opts.onIndicatorConfigChange?.(indicatorConfig);\n }),\n );\n content.appendChild(\n numberField('RSI', indicatorConfig.rsiPeriod, (n) => {\n indicatorConfig = { ...indicatorConfig, rsiPeriod: n };\n opts.onIndicatorConfigChange?.(indicatorConfig);\n }),\n );\n content.appendChild(\n numberField('KDJ', indicatorConfig.kdjPeriod, (n) => {\n indicatorConfig = { ...indicatorConfig, kdjPeriod: n };\n opts.onIndicatorConfigChange?.(indicatorConfig);\n }),\n );\n }\n };\n\n renderTabs();\n renderContent();\n panel.append(tabs, content);\n\n btn.onclick = (e) => {\n e.stopPropagation();\n open = !open;\n panel.style.display = open ? 'block' : 'none';\n };\n\n const close = () => {\n open = false;\n panel.style.display = 'none';\n };\n document.addEventListener('click', close);\n panel.onclick = (e) => e.stopPropagation();\n\n wrap.append(btn, panel);\n parent.appendChild(wrap);\n return wrap;\n}","import type { SymbolSearchHit } from '@coderyo/data';\nimport { t } from '@coderyo/i18n';\n\nexport interface SymbolSearchOptions {\n onSearch: (query: string) => Promise<SymbolSearchHit[]>;\n onSelect: (symbol: string) => void;\n initialSymbol?: string;\n}\n\nexport function mountSymbolSearch(parent: HTMLElement, opts: SymbolSearchOptions): HTMLElement {\n const wrap = document.createElement('div');\n wrap.style.cssText = 'display:flex;align-items:center;gap:6px;margin-right:8px;';\n\n const input = document.createElement('input');\n input.type = 'search';\n input.placeholder = t('symbol.search', '搜尋商品');\n input.value = opts.initialSymbol ?? '';\n input.style.cssText =\n 'width:140px;padding:4px 8px;border-radius:4px;border:1px solid #30363d;background:#0d1117;color:#e6edf3;font-size:12px;';\n\n const list = document.createElement('div');\n list.style.cssText =\n 'display:none;position:absolute;top:100%;left:0;z-index:20;min-width:200px;max-height:200px;overflow:auto;background:#161b22;border:1px solid #30363d;border-radius:4px;';\n\n const box = document.createElement('div');\n box.style.position = 'relative';\n box.append(input, list);\n wrap.appendChild(box);\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const renderHits = (hits: SymbolSearchHit[]) => {\n list.replaceChildren();\n if (hits.length === 0) {\n list.style.display = 'none';\n return;\n }\n for (const hit of hits) {\n const row = document.createElement('button');\n row.type = 'button';\n row.textContent = hit.exchange ? `${hit.symbol} · ${hit.exchange}` : hit.symbol;\n row.style.cssText =\n 'display:block;width:100%;text-align:left;padding:6px 10px;border:none;background:transparent;color:#e6edf3;cursor:pointer;font-size:12px;';\n row.onmouseenter = () => {\n row.style.background = '#21262d';\n };\n row.onmouseleave = () => {\n row.style.background = 'transparent';\n };\n row.onclick = () => {\n input.value = hit.symbol;\n list.style.display = 'none';\n opts.onSelect(hit.symbol);\n };\n list.appendChild(row);\n }\n list.style.display = 'block';\n };\n\n input.addEventListener('input', () => {\n clearTimeout(timer);\n const q = input.value.trim();\n if (q.length < 1) {\n list.style.display = 'none';\n return;\n }\n timer = setTimeout(() => {\n void opts.onSearch(q).then(renderHits).catch(() => {\n list.style.display = 'none';\n });\n }, 200);\n });\n\n document.addEventListener('click', (e) => {\n if (!box.contains(e.target as Node)) list.style.display = 'none';\n });\n\n parent.appendChild(wrap);\n return wrap;\n}","import { t } from '@coderyo/i18n';\n\nexport interface ContextMenuAction {\n id: string;\n label: string;\n onClick: () => void;\n}\n\nexport interface ContextMenuOptions {\n actions?: ContextMenuAction[];\n}\n\nexport function attachChartContextMenu(\n chartHost: HTMLElement,\n opts: ContextMenuOptions = {},\n): () => void {\n let menu: HTMLDivElement | null = null;\n\n const close = () => {\n menu?.remove();\n menu = null;\n };\n\n const open = (x: number, y: number) => {\n close();\n menu = document.createElement('div');\n menu.style.cssText =\n 'position:fixed;z-index:50;min-width:160px;padding:4px 0;background:#161b22;border:1px solid #30363d;border-radius:6px;box-shadow:0 8px 24px #01040988;';\n\n const actions: ContextMenuAction[] = opts.actions ?? [\n {\n id: 'fit',\n label: t('context.fitContent', '適配畫面'),\n onClick: () => {},\n },\n ];\n\n for (const action of actions) {\n const row = document.createElement('button');\n row.type = 'button';\n row.textContent = action.label;\n row.style.cssText =\n 'display:block;width:100%;text-align:left;padding:8px 12px;border:none;background:transparent;color:#e6edf3;font-size:12px;cursor:pointer;';\n row.onmouseenter = () => {\n row.style.background = '#21262d';\n };\n row.onmouseleave = () => {\n row.style.background = 'transparent';\n };\n row.onclick = () => {\n action.onClick();\n close();\n };\n menu.appendChild(row);\n }\n\n menu.style.left = `${x}px`;\n menu.style.top = `${y}px`;\n document.body.appendChild(menu);\n };\n\n const onContext = (e: MouseEvent) => {\n e.preventDefault();\n open(e.clientX, e.clientY);\n };\n\n chartHost.addEventListener('contextmenu', onContext);\n document.addEventListener('click', close);\n document.addEventListener('scroll', close, true);\n\n return () => {\n chartHost.removeEventListener('contextmenu', onContext);\n document.removeEventListener('click', close);\n document.removeEventListener('scroll', close, true);\n close();\n };\n}","import type { OhlcvSnapshot } from './status-bar.js';\n\nexport interface CrosshairLegendOptions {\n symbol?: string;\n interval?: string;\n}\n\nexport function mountCrosshairLegend(\n chartHost: HTMLElement,\n opts: CrosshairLegendOptions = {},\n): {\n el: HTMLElement;\n update: (payload: { time?: number; ohlcv?: OhlcvSnapshot | null }) => void;\n setMeta: (meta: CrosshairLegendOptions) => void;\n hide: () => void;\n} {\n const box = document.createElement('div');\n box.className = 'tv-crosshair-legend';\n box.style.cssText =\n 'display:none;position:absolute;top:8px;left:8px;z-index:10;padding:6px 10px;border-radius:6px;background:#161b22e6;border:1px solid #30363d;font-size:11px;color:#e6edf3;pointer-events:none;line-height:1.5;';\n\n const title = document.createElement('div');\n title.style.cssText = 'color:#8b949e;margin-bottom:2px;';\n const body = document.createElement('div');\n box.append(title, body);\n chartHost.appendChild(box);\n\n const fmt = (n: number | undefined) =>\n n == null ? '—' : n.toLocaleString(undefined, { maximumFractionDigits: 2 });\n\n const render = (payload: { time?: number; ohlcv?: OhlcvSnapshot | null }) => {\n const parts = [opts.symbol, opts.interval].filter(Boolean);\n title.textContent = parts.length ? parts.join(' · ') : '';\n const o = payload.ohlcv;\n if (!o?.c && o?.o == null) {\n box.style.display = 'none';\n return;\n }\n const timeStr = payload.time != null ? new Date(payload.time).toLocaleString() : '';\n body.textContent = `${timeStr}\\nO ${fmt(o?.o)} H ${fmt(o?.h)} L ${fmt(o?.l)} C ${fmt(o?.c)}`;\n box.style.display = 'block';\n };\n\n return {\n el: box,\n update: render,\n setMeta: (meta) => {\n Object.assign(opts, meta);\n title.textContent = [opts.symbol, opts.interval].filter(Boolean).join(' · ');\n },\n hide: () => {\n box.style.display = 'none';\n },\n };\n}","import type { DrawingRecord } from '@coderyo/drawings';\nimport { t } from '@coderyo/i18n';\n\nexport interface DrawingPropertiesPanelOptions {\n onStyleChange?: (patch: { color?: string; lineWidth?: number; text?: string }) => void;\n}\n\nexport function mountDrawingPropertiesPanel(\n parent: HTMLElement,\n opts: DrawingPropertiesPanelOptions = {},\n): { el: HTMLElement; bind: (drawing: DrawingRecord | null) => void } {\n const panel = document.createElement('aside');\n panel.className = 'tv-drawing-props';\n panel.style.cssText =\n 'display:none;width:220px;flex-shrink:0;border-left:1px solid #30363d;background:#161b22;padding:10px 12px;font-size:12px;color:#e6edf3;overflow:auto;';\n\n const title = document.createElement('div');\n title.textContent = t('drawing.props.title', '繪圖屬性');\n title.style.cssText = 'font-weight:600;margin-bottom:10px;';\n\n const typeEl = document.createElement('div');\n typeEl.style.color = '#8b949e';\n typeEl.style.marginBottom = '10px';\n\n const mkRow = (label: string) => {\n const row = document.createElement('label');\n row.style.cssText = 'display:flex;flex-direction:column;gap:4px;margin-bottom:8px;';\n const span = document.createElement('span');\n span.textContent = label;\n span.style.color = '#8b949e';\n row.appendChild(span);\n return row;\n };\n\n const colorRow = mkRow(t('drawing.props.color', '顏色'));\n const colorInput = document.createElement('input');\n colorInput.type = 'color';\n colorInput.style.cssText = 'width:100%;height:28px;border:none;background:transparent;cursor:pointer;';\n colorRow.appendChild(colorInput);\n\n const widthRow = mkRow(t('drawing.props.lineWidth', '線寬'));\n const widthInput = document.createElement('input');\n widthInput.type = 'range';\n widthInput.min = '1';\n widthInput.max = '6';\n widthInput.style.width = '100%';\n widthRow.appendChild(widthInput);\n\n const textRow = mkRow(t('drawing.props.text', '文字'));\n const textInput = document.createElement('input');\n textInput.type = 'text';\n textInput.style.cssText =\n 'padding:4px 8px;border-radius:4px;border:1px solid #30363d;background:#0d1117;color:#e6edf3;';\n textRow.appendChild(textInput);\n\n const lockedHint = document.createElement('div');\n lockedHint.style.cssText = 'color:#f78166;font-size:11px;margin-top:6px;display:none;';\n lockedHint.textContent = t('drawing.props.locked', '已鎖定 — 解鎖後可編輯');\n\n panel.append(title, typeEl, colorRow, widthRow, textRow, lockedHint);\n parent.appendChild(panel);\n\n const emit = () => {\n opts.onStyleChange?.({\n color: colorInput.value,\n lineWidth: Number(widthInput.value),\n text: textInput.value,\n });\n };\n colorInput.oninput = emit;\n widthInput.oninput = emit;\n textInput.oninput = emit;\n\n const bind = (drawing: DrawingRecord | null) => {\n if (!drawing) {\n panel.style.display = 'none';\n return;\n }\n panel.style.display = 'block';\n typeEl.textContent = `${t('drawing.props.type', '類型')}: ${drawing.type}`;\n const meta = drawing.meta ?? {};\n colorInput.value = String(meta.color ?? '#58a6ff');\n widthInput.value = String(meta.lineWidth ?? 2);\n textInput.value = String(meta.text ?? 'Note');\n textRow.style.display = drawing.type === 'text' ? 'flex' : 'none';\n const locked = Boolean(meta.locked);\n lockedHint.style.display = locked ? 'block' : 'none';\n colorInput.disabled = locked;\n widthInput.disabled = locked;\n textInput.disabled = locked;\n };\n\n return { el: panel, bind };\n}","/** Host for MACD / RSI / KDJ indicator panes (PR-11). */\nexport function mountIndicatorPaneHost(parent: HTMLElement): HTMLElement {\n const host = document.createElement('div');\n host.dataset.tradviewIndicatorHost = '1';\n host.style.cssText =\n 'display:flex;flex-direction:column;flex:2;min-height:120px;border-top:1px solid #30363d;background:#0d1117;overflow:hidden;';\n parent.appendChild(host);\n return host;\n}","import type { ChartLayoutOptions } from './chart-layout.js';\n\nexport interface LayoutFeatures {\n showTopBar?: boolean;\n showLeftToolbar?: boolean;\n showBottomToolbar?: boolean;\n showCrosshairLegend?: boolean;\n showStatusBar?: boolean;\n showPropertiesPanel?: boolean;\n showContextMenu?: boolean;\n showSettings?: boolean;\n showShortcuts?: boolean;\n /** When TopBar is on and no search API: manual symbol input (default). */\n symbolInput?: 'manual' | 'search' | 'none';\n}\n\nexport interface ResolvedLayoutFeatures {\n showTopBar: boolean;\n showLeftToolbar: boolean;\n showBottomToolbar: boolean;\n showCrosshairLegend: boolean;\n showStatusBar: boolean;\n showPropertiesPanel: boolean;\n showContextMenu: boolean;\n showSettings: boolean;\n showShortcuts: boolean;\n symbolInput: 'manual' | 'search' | 'none';\n}\n\nexport const DEFAULT_LAYOUT_FEATURES: ResolvedLayoutFeatures = {\n showTopBar: false,\n showLeftToolbar: false,\n showBottomToolbar: false,\n showCrosshairLegend: false,\n showStatusBar: false,\n showPropertiesPanel: false,\n showContextMenu: false,\n showSettings: false,\n showShortcuts: false,\n symbolInput: 'manual',\n};\n\nexport function resolveLayoutFeatures(\n opts: ChartLayoutOptions = {},\n): ResolvedLayoutFeatures {\n const d = DEFAULT_LAYOUT_FEATURES;\n const top = opts.showTopBar ?? d.showTopBar;\n return {\n showTopBar: top,\n showLeftToolbar: opts.showLeftToolbar ?? d.showLeftToolbar,\n showBottomToolbar: opts.showBottomToolbar ?? d.showBottomToolbar,\n showCrosshairLegend: opts.showCrosshairLegend ?? d.showCrosshairLegend,\n showStatusBar: opts.showStatusBar ?? d.showStatusBar,\n showPropertiesPanel: opts.showPropertiesPanel ?? d.showPropertiesPanel,\n showContextMenu: opts.showContextMenu ?? d.showContextMenu,\n showSettings: opts.settings !== undefined ? (opts.showSettings ?? true) : (opts.showSettings ?? d.showSettings),\n showShortcuts: opts.showShortcuts ?? d.showShortcuts,\n symbolInput: opts.symbolInput ?? (opts.onSymbolSearch ? 'search' : d.symbolInput),\n };\n}\n\nexport function mergeLayoutFeatures(\n current: ResolvedLayoutFeatures,\n patch: LayoutFeatures,\n): ResolvedLayoutFeatures {\n return resolveLayoutFeatures({\n showTopBar: patch.showTopBar ?? current.showTopBar,\n showLeftToolbar: patch.showLeftToolbar ?? current.showLeftToolbar,\n showBottomToolbar: patch.showBottomToolbar ?? current.showBottomToolbar,\n showCrosshairLegend: patch.showCrosshairLegend ?? current.showCrosshairLegend,\n showStatusBar: patch.showStatusBar ?? current.showStatusBar,\n showPropertiesPanel: patch.showPropertiesPanel ?? current.showPropertiesPanel,\n showContextMenu: patch.showContextMenu ?? current.showContextMenu,\n showSettings: patch.showSettings ?? current.showSettings,\n showShortcuts: patch.showShortcuts ?? current.showShortcuts,\n symbolInput: patch.symbolInput ?? current.symbolInput,\n });\n}\n\n/** Playground: enable full TV shell. */\nexport function createDemoLayoutOptions(\n partial: ChartLayoutOptions = {},\n): ChartLayoutOptions {\n return {\n ...partial,\n showTopBar: partial.showTopBar ?? true,\n showLeftToolbar: partial.showLeftToolbar ?? true,\n showCrosshairLegend: partial.showCrosshairLegend ?? true,\n showStatusBar: partial.showStatusBar ?? true,\n showPropertiesPanel: partial.showPropertiesPanel ?? true,\n showContextMenu: partial.showContextMenu ?? true,\n showSettings: partial.showSettings ?? true,\n showShortcuts: partial.showShortcuts ?? true,\n showBottomToolbar: partial.showBottomToolbar ?? true,\n symbolInput: partial.symbolInput ?? (partial.onSymbolSearch ? 'search' : 'manual'),\n };\n}","import { getLocale, setLocale, t } from '@coderyo/i18n';\n\nexport interface OhlcvSnapshot {\n o?: number;\n h?: number;\n l?: number;\n c?: number;\n v?: number;\n}\n\nexport interface StatusBarOptions {\n connection?: string;\n symbol?: string;\n interval?: string;\n ohlcv?: OhlcvSnapshot | null;\n locale?: string;\n onLocaleChange?: (locale: string) => void;\n}\n\nfunction fmt(n: number | undefined, digits = 2): string {\n if (n == null || Number.isNaN(n)) return '—';\n return n.toLocaleString(undefined, { maximumFractionDigits: digits });\n}\n\nexport function mountStatusBar(parent: HTMLElement, opts: StatusBarOptions = {}): {\n el: HTMLElement;\n update: (patch: StatusBarOptions) => void;\n} {\n const bar = document.createElement('div');\n bar.className = 'tv-statusbar';\n bar.style.cssText =\n 'display:flex;align-items:center;gap:14px;padding:6px 12px;font-size:11px;color:#8b949e;border-top:1px solid #30363d;background:#161b22;flex-shrink:0;';\n\n const conn = document.createElement('span');\n const sym = document.createElement('span');\n const ohlcv = document.createElement('span');\n ohlcv.style.flex = '1';\n ohlcv.style.color = '#e6edf3';\n\n const localeWrap = document.createElement('label');\n localeWrap.style.cssText = 'display:flex;align-items:center;gap:4px;margin-left:auto;';\n const localeLabel = document.createElement('span');\n localeLabel.textContent = t('status.locale', '語系');\n const localeSelect = document.createElement('select');\n localeSelect.style.cssText =\n 'background:#0d1117;color:#e6edf3;border:1px solid #30363d;border-radius:4px;font-size:11px;padding:2px 4px;';\n for (const loc of ['zh-TW', 'en']) {\n const opt = document.createElement('option');\n opt.value = loc;\n opt.textContent = loc;\n localeSelect.appendChild(opt);\n }\n localeSelect.value = opts.locale ?? getLocale();\n localeSelect.onchange = () => {\n setLocale(localeSelect.value);\n opts.onLocaleChange?.(localeSelect.value);\n localeLabel.textContent = t('status.locale', '語系');\n render(opts);\n };\n localeWrap.append(localeLabel, localeSelect);\n\n bar.append(conn, sym, ohlcv, localeWrap);\n parent.appendChild(bar);\n\n const render = (state: StatusBarOptions) => {\n const merged = { ...opts, ...state };\n conn.textContent = `${t('status.connection', '連線')}:${merged.connection ?? '—'}`;\n const parts = [merged.symbol, merged.interval].filter(Boolean);\n sym.textContent = parts.length ? parts.join(' · ') : '';\n const o = merged.ohlcv;\n if (o && (o.o != null || o.c != null)) {\n ohlcv.textContent = `O ${fmt(o.o)} H ${fmt(o.h)} L ${fmt(o.l)} C ${fmt(o.c)} V ${fmt(o.v, 0)}`;\n } else {\n ohlcv.textContent = t('status.ohlcvHint', '移動十字線查看 OHLCV');\n }\n };\n\n render(opts);\n return {\n el: bar,\n update: (patch) => {\n Object.assign(opts, patch);\n render(opts);\n },\n };\n}","import { t } from '@coderyo/i18n';\n\nconst SHORTCUTS: Array<{ key: string; desc: string }> = [\n { key: '↖ / Esc', desc: '游標(選取/編輯繪圖)' },\n { key: 'Delete', desc: '刪除選中繪圖' },\n { key: 'R', desc: '適配畫面' },\n { key: 'End', desc: '跳到最新 K 線' },\n { key: 'F', desc: '全螢幕' },\n { key: 'S', desc: '截圖 PNG' },\n { key: 'L', desc: '對數價格軸' },\n { key: 'T', desc: '切換主題' },\n { key: '?', desc: '本說明' },\n];\n\nexport function bindShortcutsModal(): () => void {\n const handler = (e: KeyboardEvent) => {\n if (e.key !== '?' || e.ctrlKey || e.metaKey) return;\n const tag = (e.target as HTMLElement)?.tagName;\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;\n e.preventDefault();\n openShortcutsModal();\n };\n window.addEventListener('keydown', handler);\n return () => window.removeEventListener('keydown', handler);\n}\n\nexport function openShortcutsModal(): void {\n const backdrop = document.createElement('div');\n backdrop.style.cssText =\n 'position:fixed;inset:0;z-index:100;background:#01040999;display:flex;align-items:center;justify-content:center;';\n\n const box = document.createElement('div');\n box.style.cssText =\n 'min-width:320px;max-width:90vw;padding:16px 20px;background:#161b22;border:1px solid #30363d;border-radius:8px;color:#e6edf3;';\n\n const h = document.createElement('h2');\n h.textContent = t('shortcuts.title', '快捷鍵');\n h.style.cssText = 'font-size:16px;margin:0 0 12px;';\n\n const list = document.createElement('div');\n list.style.cssText = 'font-size:13px;line-height:1.8;';\n for (const s of SHORTCUTS) {\n const row = document.createElement('div');\n row.innerHTML = `<kbd style=\"background:#21262d;padding:2px 6px;border-radius:4px;margin-right:8px;\">${s.key}</kbd>${s.desc}`;\n list.appendChild(row);\n }\n\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.textContent = t('shortcuts.close', '關閉');\n closeBtn.style.cssText =\n 'margin-top:14px;padding:6px 14px;background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;cursor:pointer;';\n const close = () => backdrop.remove();\n closeBtn.onclick = close;\n backdrop.onclick = (e) => {\n if (e.target === backdrop) close();\n };\n\n box.append(h, list, closeBtn);\n backdrop.appendChild(box);\n document.body.appendChild(backdrop);\n}","import { attachChartContextMenu, type ContextMenuAction } from './context-menu.js';\nimport { mountCrosshairLegend } from './crosshair-legend.js';\nimport { mountDrawingPropertiesPanel } from './drawing-properties-panel.js';\nimport { mountIndicatorPaneHost } from './indicator-pane-host.js';\nimport {\n mergeLayoutFeatures,\n resolveLayoutFeatures,\n type LayoutFeatures,\n type ResolvedLayoutFeatures,\n} from './layout-features.js';\nimport { mountStatusBar, type StatusBarOptions } from './status-bar.js';\nimport { mountTopBar, type TopBarOptions } from './top-bar.js';\nimport type { SettingsPanelOptions } from './settings-panel.js';\nimport { bindShortcutsModal } from './shortcuts-modal.js';\n\nexport type { LayoutFeatures, ResolvedLayoutFeatures } from './layout-features.js';\nexport { resolveLayoutFeatures, DEFAULT_LAYOUT_FEATURES, createDemoLayoutOptions } from './layout-features.js';\n\nexport type DrawingToolId =\n | 'cursor'\n | 'trendline'\n | 'hline'\n | 'vline'\n | 'rectangle'\n | 'fibonacci'\n | 'text';\n\nconst DRAWING_TOOLS: Array<{ id: DrawingToolId; label: string }> = [\n { id: 'cursor', label: '↖' },\n { id: 'trendline', label: '╱' },\n { id: 'hline', label: '─' },\n { id: 'vline', label: '│' },\n { id: 'rectangle', label: '▭' },\n { id: 'fibonacci', label: 'φ' },\n { id: 'text', label: 'T' },\n];\n\nconst MOBILE_MQ = '(max-width: 768px)';\n\nexport interface ChartLayoutOptions extends TopBarOptions {\n showTopBar?: boolean;\n showLeftToolbar?: boolean;\n showBottomToolbar?: boolean;\n activeDrawingTool?: DrawingToolId;\n onDrawingToolSelect?: (tool: DrawingToolId) => void;\n statusBar?: StatusBarOptions;\n contextMenuActions?: ContextMenuAction[];\n settings?: SettingsPanelOptions;\n showStatusBar?: boolean;\n showCrosshairLegend?: boolean;\n showPropertiesPanel?: boolean;\n showContextMenu?: boolean;\n showSettings?: boolean;\n showShortcuts?: boolean;\n symbolInput?: 'manual' | 'search' | 'none';\n onDrawingStyleChange?: (patch: { color?: string; lineWidth?: number; text?: string }) => void;\n onDrawingSelectionBind?: (bind: (drawing: import('@coderyo/drawings').DrawingRecord | null) => void) => void;\n}\n\nfunction mountToolButtons(\n parent: HTMLElement,\n activeTool: DrawingToolId,\n onSelect: (tool: DrawingToolId) => void,\n layout: 'column' | 'row',\n): { setActive: (tool: DrawingToolId) => void } {\n const btnStyle =\n layout === 'column'\n ? 'width:36px;height:32px;background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;cursor:pointer;font-size:14px;'\n : 'min-width:40px;height:36px;background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;cursor:pointer;font-size:14px;padding:0 8px;';\n const activeStyle = btnStyle.replace('#21262d', '#388bfd').replace('#e6edf3', '#fff');\n\n const buttons = new Map<DrawingToolId, HTMLButtonElement>();\n for (const tool of DRAWING_TOOLS) {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.title = tool.id;\n btn.textContent = tool.label;\n btn.style.cssText = activeTool === tool.id ? activeStyle : btnStyle;\n btn.onclick = () => onSelect(tool.id);\n buttons.set(tool.id, btn);\n parent.appendChild(btn);\n }\n\n return {\n setActive: (tool) => {\n for (const [id, btn] of buttons) {\n btn.style.cssText = id === tool ? activeStyle : btnStyle;\n }\n },\n };\n}\n\nexport function mountChartLayout(root: HTMLElement, opts: ChartLayoutOptions = {}): {\n chartHost: HTMLElement;\n indicatorHost: HTMLElement;\n topBar: HTMLElement;\n statusBar: ReturnType<typeof mountStatusBar>;\n crosshairLegend: ReturnType<typeof mountCrosshairLegend>;\n detachContextMenu: () => void;\n setActiveDrawingTool: (tool: DrawingToolId) => void;\n propertiesPanel: ReturnType<typeof mountDrawingPropertiesPanel>;\n setLayoutFeatures: (patch: LayoutFeatures) => void;\n getLayoutFeatures: () => ResolvedLayoutFeatures;\n} {\n let layoutFeatures = resolveLayoutFeatures(opts);\n\n root.style.display = 'flex';\n root.style.flexDirection = 'column';\n root.style.height = '100%';\n root.style.background = '#0d1117';\n\n let activeTool: DrawingToolId = opts.activeDrawingTool ?? 'cursor';\n let setActiveDesktop: ((t: DrawingToolId) => void) | null = null;\n let setActiveMobile: ((t: DrawingToolId) => void) | null = null;\n\n const setActiveDrawingTool = (tool: DrawingToolId) => {\n activeTool = tool;\n setActiveDesktop?.(tool);\n setActiveMobile?.(tool);\n };\n\n const onToolSelect = (tool: DrawingToolId) => {\n setActiveDrawingTool(tool);\n opts.onDrawingToolSelect?.(tool);\n };\n\n const body = document.createElement('div');\n body.style.cssText = 'display:flex;flex:1;min-height:0;';\n\n const leftAside = document.createElement('aside');\n leftAside.style.cssText =\n 'width:48px;border-right:1px solid #30363d;background:#161b22;display:flex;flex-direction:column;align-items:center;padding:8px 4px;gap:8px;flex-shrink:0;z-index:20;';\n\n const bottomBar = document.createElement('div');\n bottomBar.style.cssText =\n 'display:none;flex-shrink:0;gap:6px;padding:6px 8px;border-top:1px solid #30363d;background:#161b22;overflow-x:auto;';\n\n const chartColumn = document.createElement('div');\n chartColumn.style.cssText = 'display:flex;flex-direction:column;flex:1;min-height:0;';\n\n const chartHost = document.createElement('div');\n chartHost.style.cssText = 'flex:1;min-height:0;width:100%;height:100%;position:relative;overflow:hidden;';\n chartColumn.appendChild(chartHost);\n\n const indicatorHost = mountIndicatorPaneHost(chartColumn);\n\n const statusBar = mountStatusBar(chartColumn, opts.statusBar ?? {});\n\n body.appendChild(chartColumn);\n\n const propertiesPanel = mountDrawingPropertiesPanel(body, {\n onStyleChange: opts.onDrawingStyleChange,\n });\n\n opts.onDrawingSelectionBind?.(propertiesPanel.bind);\n\n root.appendChild(body);\n root.appendChild(bottomBar);\n\n let topBar: HTMLElement = document.createElement('div');\n topBar.style.display = 'none';\n root.insertBefore(topBar, body);\n\n const crosshairLegend = mountCrosshairLegend(chartHost, { symbol: opts.initialSymbol });\n\n let detachContextMenu = () => {};\n let shortcutsBound = false;\n\n const mountLeftToolbar = () => {\n if (leftAside.parentElement) return;\n const desktopTools = mountToolButtons(leftAside, activeTool, onToolSelect, 'column');\n setActiveDesktop = desktopTools.setActive;\n body.insertBefore(leftAside, chartColumn);\n\n bottomBar.style.flexDirection = 'row';\n const mobileTools = mountToolButtons(bottomBar, activeTool, onToolSelect, 'row');\n setActiveMobile = mobileTools.setActive;\n\n const mq = window.matchMedia(MOBILE_MQ);\n const applyLayout = () => {\n const mobile = mq.matches;\n leftAside.style.display = mobile ? 'none' : 'flex';\n const showBottom = layoutFeatures.showBottomToolbar !== false;\n bottomBar.style.display = mobile && showBottom ? 'flex' : 'none';\n };\n mq.addEventListener('change', applyLayout);\n applyLayout();\n };\n\n const unmountLeftToolbar = () => {\n leftAside.remove();\n bottomBar.innerHTML = '';\n setActiveDesktop = null;\n setActiveMobile = null;\n };\n\n const applyLayoutFeatures = () => {\n const f = layoutFeatures;\n\n if (f.showTopBar) {\n topBar.remove();\n topBar = mountTopBar(root, {\n ...opts,\n symbolInput: f.symbolInput,\n showSettings: f.showSettings,\n });\n } else {\n topBar.style.display = 'none';\n }\n\n if (f.showLeftToolbar) mountLeftToolbar();\n else unmountLeftToolbar();\n\n crosshairLegend.el.style.display = f.showCrosshairLegend ? '' : 'none';\n statusBar.el.style.display = f.showStatusBar ? '' : 'none';\n propertiesPanel.el.style.display = f.showPropertiesPanel ? '' : 'none';\n\n detachContextMenu();\n if (f.showContextMenu) {\n detachContextMenu = attachChartContextMenu(chartHost, {\n actions: opts.contextMenuActions,\n });\n }\n\n if (f.showShortcuts && !shortcutsBound) {\n bindShortcutsModal();\n shortcutsBound = true;\n }\n };\n\n applyLayoutFeatures();\n\n return {\n chartHost,\n indicatorHost,\n topBar,\n statusBar,\n crosshairLegend,\n detachContextMenu,\n setActiveDrawingTool,\n propertiesPanel,\n setLayoutFeatures: (patch) => {\n layoutFeatures = mergeLayoutFeatures(layoutFeatures, patch);\n applyLayoutFeatures();\n },\n getLayoutFeatures: () => ({ ...layoutFeatures }),\n };\n}","import type { DrawingRecord } from '@coderyo/drawings';\nimport { t } from '@coderyo/i18n';\n\nexport interface DrawingContextMenuHandlers {\n onDelete?: () => void;\n onCopy?: () => void;\n onToggleLock?: () => void;\n onDeselect?: () => void;\n onEditText?: () => void;\n}\n\nexport function openDrawingContextMenu(\n clientX: number,\n clientY: number,\n drawing: DrawingRecord | null,\n handlers: DrawingContextMenuHandlers,\n): () => void {\n const menu = document.createElement('div');\n menu.style.cssText =\n 'position:fixed;z-index:60;min-width:168px;padding:4px 0;background:#161b22;border:1px solid #30363d;border-radius:6px;box-shadow:0 8px 24px #01040988;';\n menu.style.left = `${clientX}px`;\n menu.style.top = `${clientY}px`;\n\n const add = (label: string, fn?: () => void, disabled = false) => {\n if (!fn) return;\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.textContent = label;\n btn.disabled = disabled;\n btn.style.cssText =\n 'display:block;width:100%;text-align:left;padding:8px 12px;border:none;background:transparent;color:#e6edf3;font-size:12px;cursor:pointer;';\n if (disabled) btn.style.opacity = '0.45';\n btn.onclick = () => {\n fn();\n close();\n };\n menu.appendChild(btn);\n };\n\n if (drawing) {\n const locked = Boolean(drawing.meta?.locked);\n add(t('drawing.ctx.delete', '刪除'), handlers.onDelete);\n add(t('drawing.ctx.copy', '複製'), handlers.onCopy);\n add(\n locked ? t('drawing.ctx.unlock', '解鎖') : t('drawing.ctx.lock', '鎖定'),\n handlers.onToggleLock,\n );\n if (drawing.type === 'text') {\n add(t('drawing.ctx.editText', '編輯文字'), handlers.onEditText);\n }\n add(t('drawing.ctx.deselect', '取消選取'), handlers.onDeselect);\n }\n\n document.body.appendChild(menu);\n\n const close = () => {\n menu.remove();\n document.removeEventListener('click', close);\n };\n setTimeout(() => document.addEventListener('click', close), 0);\n return close;\n}","export function mountCodeSnippetPanel(parent: HTMLElement, getCode: () => string): HTMLElement {\n const wrap = document.createElement('details');\n wrap.style.cssText =\n 'flex-shrink:0;border-top:1px solid #30363d;background:#161b22;padding:6px 12px;font-size:11px;color:#8b949e;';\n\n const summary = document.createElement('summary');\n summary.textContent = '嵌入程式碼(整合方)';\n summary.style.cssText = 'cursor:pointer;color:#58a6ff;user-select:none;';\n\n const pre = document.createElement('pre');\n pre.style.cssText =\n 'margin:8px 0 0;padding:10px;background:#0d1117;border:1px solid #30363d;border-radius:6px;color:#e6edf3;font-size:11px;overflow:auto;max-height:160px;white-space:pre-wrap;';\n\n const copyBtn = document.createElement('button');\n copyBtn.type = 'button';\n copyBtn.textContent = '複製';\n copyBtn.style.cssText =\n 'margin-top:6px;padding:4px 10px;background:#21262d;color:#e6edf3;border:1px solid #30363d;border-radius:4px;cursor:pointer;font-size:11px;';\n copyBtn.onclick = () => {\n const code = getCode();\n pre.textContent = code;\n void navigator.clipboard.writeText(code);\n };\n\n wrap.ontoggle = () => {\n if (wrap.open) pre.textContent = getCode();\n };\n\n wrap.append(summary, pre, copyBtn);\n parent.appendChild(wrap);\n return wrap;\n}"],"mappings":"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","names":["t","DEFAULT_INDICATOR_CONFIG","DEFAULT_INDICATOR_CONFIG","t","t","t","fmt","t","t","t","t"]}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@coderyo/ui-shell",
3
+ "version": "1.0.0-rc.2",
4
+ "license": "UNLICENSED",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ }
11
+ },
12
+ "dependencies": {
13
+ "@coderyo/data": "1.0.0-rc.2",
14
+ "@coderyo/drawings": "1.0.0-rc.2",
15
+ "@coderyo/i18n": "1.0.0-rc.2",
16
+ "@coderyo/indicators": "1.0.0-rc.2"
17
+ },
18
+ "devDependencies": {
19
+ "tsup": "^8.5.0",
20
+ "typescript": "^5.8.3",
21
+ "vitest": "^3.2.4",
22
+ "@coderyo/eslint-config": "0.0.0",
23
+ "@coderyo/tsconfig": "0.0.0"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/CodeRyoStudio/tradview.git",
31
+ "directory": "packages/ui-shell"
32
+ },
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "test": "vitest run --passWithNoTests",
36
+ "lint": "eslint src",
37
+ "typecheck": "tsc --noEmit"
38
+ }
39
+ }