@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.
- package/dist/index.d.ts +186 -0
- package/dist/index.js +945 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|