@kodiak-finance/orderly-layout-core 2.9.1
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.mts +244 -0
- package/dist/index.d.ts +244 -0
- package/dist/index.js +331 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +323 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +37 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { ReactNode, ComponentType } from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base layout model type - each strategy defines its own concrete type
|
|
6
|
+
* This is a marker type to ensure type safety in serialization
|
|
7
|
+
*/
|
|
8
|
+
type LayoutModel = Record<string, unknown>;
|
|
9
|
+
/**
|
|
10
|
+
* Panel registry entry: maps panel IDs to React component with optional props
|
|
11
|
+
*/
|
|
12
|
+
type PanelRegistryEntry = {
|
|
13
|
+
node: ReactNode;
|
|
14
|
+
props?: Record<string, unknown>;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Panel registry: maps panel IDs to React components/nodes
|
|
18
|
+
*/
|
|
19
|
+
type PanelRegistry = Map<string, PanelRegistryEntry>;
|
|
20
|
+
/**
|
|
21
|
+
* Layout change callback (generic version)
|
|
22
|
+
* @param layout - The new layout model (strategy-specific)
|
|
23
|
+
*/
|
|
24
|
+
type OnLayoutChange<TLayout extends LayoutModel = LayoutModel> = (layout: TLayout) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Layout persist callback - called when layout should be persisted (e.g., on drag stop)
|
|
27
|
+
* @param layout - The layout model to persist
|
|
28
|
+
*/
|
|
29
|
+
type OnLayoutPersist<TLayout extends LayoutModel = LayoutModel> = (layout: TLayout) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Layout strategy interface
|
|
32
|
+
* Each layout strategy (split, grid, etc.) must implement this interface
|
|
33
|
+
*/
|
|
34
|
+
interface LayoutStrategy<TLayout extends LayoutModel = LayoutModel> {
|
|
35
|
+
/** Unique identifier for this strategy */
|
|
36
|
+
id: string;
|
|
37
|
+
/** Human-readable display name */
|
|
38
|
+
displayName: string;
|
|
39
|
+
/** Create default layout model for given panel IDs */
|
|
40
|
+
defaultLayout: (panelIds: string[]) => TLayout;
|
|
41
|
+
/** Serialize layout model to JSON for persistence */
|
|
42
|
+
serialize: (layout: TLayout) => string;
|
|
43
|
+
/** Deserialize JSON string back to layout model */
|
|
44
|
+
deserialize: (json: string) => TLayout;
|
|
45
|
+
/** Renderer component that takes layout model and panels and renders them */
|
|
46
|
+
Renderer: ComponentType<LayoutRendererProps<TLayout>>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Props for the strategy renderer component
|
|
50
|
+
*/
|
|
51
|
+
interface LayoutRendererProps<TLayout extends LayoutModel = LayoutModel> {
|
|
52
|
+
/** Current layout model */
|
|
53
|
+
layout: TLayout;
|
|
54
|
+
/** Panel registry containing all available panels */
|
|
55
|
+
panels: PanelRegistry;
|
|
56
|
+
/** Callback when layout changes (typed to the specific layout model) */
|
|
57
|
+
onLayoutChange: OnLayoutChange<TLayout>;
|
|
58
|
+
/** Callback when layout should be persisted (e.g., on drag stop or resize stop) */
|
|
59
|
+
onLayoutPersist?: OnLayoutPersist<TLayout>;
|
|
60
|
+
/** Optional className for the root container */
|
|
61
|
+
className?: string;
|
|
62
|
+
/** Optional style for the root container */
|
|
63
|
+
style?: React.CSSProperties;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Props for LayoutHost component
|
|
67
|
+
*/
|
|
68
|
+
interface LayoutHostProps<TLayout extends LayoutModel = LayoutModel> {
|
|
69
|
+
/** The layout strategy to use */
|
|
70
|
+
strategy: LayoutStrategy<TLayout>;
|
|
71
|
+
/** Panel registry: map of panel ID to ReactNode or PanelRegistryEntry */
|
|
72
|
+
panels: PanelRegistry | Record<string, PanelRegistryEntry | ReactNode>;
|
|
73
|
+
/** Initial layout model (optional, will use defaultLayout if not provided) */
|
|
74
|
+
initialLayout?: TLayout;
|
|
75
|
+
/** Callback when layout changes (typed to the specific layout model) */
|
|
76
|
+
onLayoutChange?: OnLayoutChange<TLayout>;
|
|
77
|
+
/** Storage key for persistence (optional, if not provided layout won't be persisted) */
|
|
78
|
+
storageKey?: string;
|
|
79
|
+
/** Optional className for the root container */
|
|
80
|
+
className?: string;
|
|
81
|
+
/** Optional style for the root container */
|
|
82
|
+
style?: React.CSSProperties;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Strategy resolver options
|
|
86
|
+
*/
|
|
87
|
+
interface StrategyResolverOptions {
|
|
88
|
+
/** Preferred strategy ID (may not be available) */
|
|
89
|
+
preferredId?: string;
|
|
90
|
+
/** Available strategies to choose from */
|
|
91
|
+
availableStrategies: LayoutStrategy[];
|
|
92
|
+
/** Default strategy to use if preferred is not available */
|
|
93
|
+
defaultStrategy?: LayoutStrategy;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Trading page panel ID constants.
|
|
98
|
+
* Used across layout strategies (split, grid) to identify trading panels.
|
|
99
|
+
* Component mapping happens in the trading package.
|
|
100
|
+
*/
|
|
101
|
+
declare const TRADING_PANEL_IDS: {
|
|
102
|
+
readonly SYMBOL_INFO_BAR: "symbolInfoBar";
|
|
103
|
+
readonly TRADING_VIEW: "tradingView";
|
|
104
|
+
readonly ORDERBOOK: "orderbook";
|
|
105
|
+
readonly DATA_LIST: "dataList";
|
|
106
|
+
readonly ORDER_ENTRY: "orderEntry";
|
|
107
|
+
readonly MARGIN: "orderEntryMargin";
|
|
108
|
+
readonly ASSETS: "orderEntryAssets";
|
|
109
|
+
readonly MARKETS: "markets";
|
|
110
|
+
readonly HORIZONTAL_MARKETS: "horizontalMarkets";
|
|
111
|
+
};
|
|
112
|
+
type TradingPanelId = (typeof TRADING_PANEL_IDS)[keyof typeof TRADING_PANEL_IDS];
|
|
113
|
+
/**
|
|
114
|
+
* Returns all trading panel IDs from layout-core.
|
|
115
|
+
* Used by layout strategies (split, grid) to derive panel set; component mapping is in trading package.
|
|
116
|
+
*/
|
|
117
|
+
declare function getTradingPanelIds(): readonly string[];
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* LayoutHost component
|
|
121
|
+
* Provides a unified interface for rendering layouts using any strategy
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```tsx
|
|
125
|
+
* <LayoutHost
|
|
126
|
+
* strategy={splitStrategy}
|
|
127
|
+
* panels={panelMap}
|
|
128
|
+
* storageKey="my_layout_state"
|
|
129
|
+
* onLayoutChange={(layout) => console.log('Layout changed', layout)}
|
|
130
|
+
* />
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
declare function LayoutHost<TLayout extends LayoutModel = LayoutModel>(props: LayoutHostProps<TLayout>): react_jsx_runtime.JSX.Element;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Hook for persisting layout state to localStorage
|
|
137
|
+
* @param strategy - The layout strategy being used
|
|
138
|
+
* @param storageKey - localStorage key (optional, if not provided returns undefined)
|
|
139
|
+
* @param dep - Dependency to trigger re-read from storage when it changes
|
|
140
|
+
* @returns Tuple of [currentLayout, setLayout]
|
|
141
|
+
*/
|
|
142
|
+
declare function useLayoutPersistence<TLayout extends LayoutModel>(strategy: LayoutStrategy<TLayout>, storageKey?: string, dep?: unknown): [TLayout | undefined, (layout: TLayout) => void];
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Generic preset shape: id, name, and strategy-specific rule.
|
|
146
|
+
*/
|
|
147
|
+
interface LayoutPreset<TRule = unknown> {
|
|
148
|
+
id: string;
|
|
149
|
+
name: string;
|
|
150
|
+
rule: TRule;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Storage key configuration per strategy (split vs grid use different keys).
|
|
154
|
+
*/
|
|
155
|
+
interface LayoutRuleManagerOptions {
|
|
156
|
+
/** localStorage key for persisting the selected preset id. */
|
|
157
|
+
presetIdStorageKey: string;
|
|
158
|
+
/** Base key for layout persistence; actual key is `${layoutStorageKeyPrefix}_${presetId}`. */
|
|
159
|
+
layoutStorageKeyPrefix: string;
|
|
160
|
+
/**
|
|
161
|
+
* When false, no localStorage read/write; preset selection kept in memory only.
|
|
162
|
+
* Default true for backward compatibility.
|
|
163
|
+
*/
|
|
164
|
+
persist?: boolean;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Manages layout presets, selected preset persistence, and reset.
|
|
168
|
+
* Persistence of the user-selected rule (preset id) is done via presetIdStorageKey.
|
|
169
|
+
* When persist is false, selection is kept in memory only (no localStorage).
|
|
170
|
+
*/
|
|
171
|
+
declare class LayoutRuleManager<TRule = unknown> {
|
|
172
|
+
private readonly _presets;
|
|
173
|
+
private readonly options;
|
|
174
|
+
/** In-memory selected preset id when persist is false. */
|
|
175
|
+
private _selectedId?;
|
|
176
|
+
constructor(presets: LayoutPreset<TRule>[], options: LayoutRuleManagerOptions);
|
|
177
|
+
/** Whether persistence to localStorage is enabled. */
|
|
178
|
+
private get persist();
|
|
179
|
+
/** Read-only preset list. */
|
|
180
|
+
get presets(): readonly LayoutPreset<TRule>[];
|
|
181
|
+
/**
|
|
182
|
+
* Reads selected preset id from localStorage (presetIdStorageKey).
|
|
183
|
+
* When persist is false, returns in-memory selection or first preset.
|
|
184
|
+
* Validates against presets; falls back to first preset id when missing or invalid.
|
|
185
|
+
*/
|
|
186
|
+
getSelectedPresetId(): string;
|
|
187
|
+
/**
|
|
188
|
+
* Writes selected preset id to localStorage (presetIdStorageKey).
|
|
189
|
+
* When persist is false, only updates in-memory selection.
|
|
190
|
+
* Validates id exists in presets before writing.
|
|
191
|
+
*/
|
|
192
|
+
setSelectedPresetId(id: string): void;
|
|
193
|
+
/** Returns the preset for the currently selected id. */
|
|
194
|
+
getSelectedPreset(): LayoutPreset<TRule> | undefined;
|
|
195
|
+
/**
|
|
196
|
+
* Storage key for layout persistence for the current preset.
|
|
197
|
+
* Used by LayoutHost: `${layoutStorageKeyPrefix}_${selectedPresetId}`.
|
|
198
|
+
*/
|
|
199
|
+
getLayoutStorageKey(): string;
|
|
200
|
+
/**
|
|
201
|
+
* Clears persisted layout data for the current preset (reset to preset rule).
|
|
202
|
+
* After this, next load will use getInitialLayout() from preset rule.
|
|
203
|
+
* No-op when persist is false (nothing to clear).
|
|
204
|
+
*/
|
|
205
|
+
reset(): void;
|
|
206
|
+
/**
|
|
207
|
+
* Returns whether the current preset has any persisted layout data.
|
|
208
|
+
* Useful for UI "custom" badge or "Reset to default" visibility.
|
|
209
|
+
* Returns false when persist is false.
|
|
210
|
+
*/
|
|
211
|
+
hasPersistedLayout(): boolean;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
interface UseLayoutRuleManagerResult<TRule> {
|
|
215
|
+
presets: readonly LayoutPreset<TRule>[];
|
|
216
|
+
selectedPresetId: string;
|
|
217
|
+
setSelectedPresetId: (id: string) => void;
|
|
218
|
+
layoutStorageKey: string;
|
|
219
|
+
reset: () => void;
|
|
220
|
+
getSelectedPreset: () => LayoutPreset<TRule> | undefined;
|
|
221
|
+
hasPersistedLayout: () => boolean;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Binds a LayoutRuleManager to React state so selection and reset trigger re-renders.
|
|
225
|
+
* setSelectedPresetId updates both the manager (localStorage) and local state.
|
|
226
|
+
*
|
|
227
|
+
* @param manager - LayoutRuleManager instance (e.g. from LayoutRuleManager<SplitLayoutRule>)
|
|
228
|
+
* @returns Presets, selectedPresetId, setSelectedPresetId, layoutStorageKey, reset, getSelectedPreset, hasPersistedLayout
|
|
229
|
+
*/
|
|
230
|
+
declare function useLayoutRuleManager<TRule>(manager: LayoutRuleManager<TRule>): UseLayoutRuleManagerResult<TRule>;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Resolve which strategy to use based on preferred ID and available strategies
|
|
234
|
+
* Ensures we always return a valid strategy, falling back to default if preferred is not available
|
|
235
|
+
*
|
|
236
|
+
* @param options - Resolver options
|
|
237
|
+
* @returns Resolved strategy and its ID
|
|
238
|
+
*/
|
|
239
|
+
declare function resolveStrategy(options: StrategyResolverOptions): {
|
|
240
|
+
strategy: LayoutStrategy;
|
|
241
|
+
id: string;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export { LayoutHost, type LayoutHostProps, type LayoutModel, type LayoutPreset, type LayoutRendererProps, LayoutRuleManager, type LayoutRuleManagerOptions, type LayoutStrategy, type OnLayoutChange, type OnLayoutPersist, type PanelRegistry, type StrategyResolverOptions, TRADING_PANEL_IDS, type TradingPanelId, type UseLayoutRuleManagerResult, getTradingPanelIds, resolveStrategy, useLayoutPersistence, useLayoutRuleManager };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { ReactNode, ComponentType } from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base layout model type - each strategy defines its own concrete type
|
|
6
|
+
* This is a marker type to ensure type safety in serialization
|
|
7
|
+
*/
|
|
8
|
+
type LayoutModel = Record<string, unknown>;
|
|
9
|
+
/**
|
|
10
|
+
* Panel registry entry: maps panel IDs to React component with optional props
|
|
11
|
+
*/
|
|
12
|
+
type PanelRegistryEntry = {
|
|
13
|
+
node: ReactNode;
|
|
14
|
+
props?: Record<string, unknown>;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Panel registry: maps panel IDs to React components/nodes
|
|
18
|
+
*/
|
|
19
|
+
type PanelRegistry = Map<string, PanelRegistryEntry>;
|
|
20
|
+
/**
|
|
21
|
+
* Layout change callback (generic version)
|
|
22
|
+
* @param layout - The new layout model (strategy-specific)
|
|
23
|
+
*/
|
|
24
|
+
type OnLayoutChange<TLayout extends LayoutModel = LayoutModel> = (layout: TLayout) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Layout persist callback - called when layout should be persisted (e.g., on drag stop)
|
|
27
|
+
* @param layout - The layout model to persist
|
|
28
|
+
*/
|
|
29
|
+
type OnLayoutPersist<TLayout extends LayoutModel = LayoutModel> = (layout: TLayout) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Layout strategy interface
|
|
32
|
+
* Each layout strategy (split, grid, etc.) must implement this interface
|
|
33
|
+
*/
|
|
34
|
+
interface LayoutStrategy<TLayout extends LayoutModel = LayoutModel> {
|
|
35
|
+
/** Unique identifier for this strategy */
|
|
36
|
+
id: string;
|
|
37
|
+
/** Human-readable display name */
|
|
38
|
+
displayName: string;
|
|
39
|
+
/** Create default layout model for given panel IDs */
|
|
40
|
+
defaultLayout: (panelIds: string[]) => TLayout;
|
|
41
|
+
/** Serialize layout model to JSON for persistence */
|
|
42
|
+
serialize: (layout: TLayout) => string;
|
|
43
|
+
/** Deserialize JSON string back to layout model */
|
|
44
|
+
deserialize: (json: string) => TLayout;
|
|
45
|
+
/** Renderer component that takes layout model and panels and renders them */
|
|
46
|
+
Renderer: ComponentType<LayoutRendererProps<TLayout>>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Props for the strategy renderer component
|
|
50
|
+
*/
|
|
51
|
+
interface LayoutRendererProps<TLayout extends LayoutModel = LayoutModel> {
|
|
52
|
+
/** Current layout model */
|
|
53
|
+
layout: TLayout;
|
|
54
|
+
/** Panel registry containing all available panels */
|
|
55
|
+
panels: PanelRegistry;
|
|
56
|
+
/** Callback when layout changes (typed to the specific layout model) */
|
|
57
|
+
onLayoutChange: OnLayoutChange<TLayout>;
|
|
58
|
+
/** Callback when layout should be persisted (e.g., on drag stop or resize stop) */
|
|
59
|
+
onLayoutPersist?: OnLayoutPersist<TLayout>;
|
|
60
|
+
/** Optional className for the root container */
|
|
61
|
+
className?: string;
|
|
62
|
+
/** Optional style for the root container */
|
|
63
|
+
style?: React.CSSProperties;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Props for LayoutHost component
|
|
67
|
+
*/
|
|
68
|
+
interface LayoutHostProps<TLayout extends LayoutModel = LayoutModel> {
|
|
69
|
+
/** The layout strategy to use */
|
|
70
|
+
strategy: LayoutStrategy<TLayout>;
|
|
71
|
+
/** Panel registry: map of panel ID to ReactNode or PanelRegistryEntry */
|
|
72
|
+
panels: PanelRegistry | Record<string, PanelRegistryEntry | ReactNode>;
|
|
73
|
+
/** Initial layout model (optional, will use defaultLayout if not provided) */
|
|
74
|
+
initialLayout?: TLayout;
|
|
75
|
+
/** Callback when layout changes (typed to the specific layout model) */
|
|
76
|
+
onLayoutChange?: OnLayoutChange<TLayout>;
|
|
77
|
+
/** Storage key for persistence (optional, if not provided layout won't be persisted) */
|
|
78
|
+
storageKey?: string;
|
|
79
|
+
/** Optional className for the root container */
|
|
80
|
+
className?: string;
|
|
81
|
+
/** Optional style for the root container */
|
|
82
|
+
style?: React.CSSProperties;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Strategy resolver options
|
|
86
|
+
*/
|
|
87
|
+
interface StrategyResolverOptions {
|
|
88
|
+
/** Preferred strategy ID (may not be available) */
|
|
89
|
+
preferredId?: string;
|
|
90
|
+
/** Available strategies to choose from */
|
|
91
|
+
availableStrategies: LayoutStrategy[];
|
|
92
|
+
/** Default strategy to use if preferred is not available */
|
|
93
|
+
defaultStrategy?: LayoutStrategy;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Trading page panel ID constants.
|
|
98
|
+
* Used across layout strategies (split, grid) to identify trading panels.
|
|
99
|
+
* Component mapping happens in the trading package.
|
|
100
|
+
*/
|
|
101
|
+
declare const TRADING_PANEL_IDS: {
|
|
102
|
+
readonly SYMBOL_INFO_BAR: "symbolInfoBar";
|
|
103
|
+
readonly TRADING_VIEW: "tradingView";
|
|
104
|
+
readonly ORDERBOOK: "orderbook";
|
|
105
|
+
readonly DATA_LIST: "dataList";
|
|
106
|
+
readonly ORDER_ENTRY: "orderEntry";
|
|
107
|
+
readonly MARGIN: "orderEntryMargin";
|
|
108
|
+
readonly ASSETS: "orderEntryAssets";
|
|
109
|
+
readonly MARKETS: "markets";
|
|
110
|
+
readonly HORIZONTAL_MARKETS: "horizontalMarkets";
|
|
111
|
+
};
|
|
112
|
+
type TradingPanelId = (typeof TRADING_PANEL_IDS)[keyof typeof TRADING_PANEL_IDS];
|
|
113
|
+
/**
|
|
114
|
+
* Returns all trading panel IDs from layout-core.
|
|
115
|
+
* Used by layout strategies (split, grid) to derive panel set; component mapping is in trading package.
|
|
116
|
+
*/
|
|
117
|
+
declare function getTradingPanelIds(): readonly string[];
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* LayoutHost component
|
|
121
|
+
* Provides a unified interface for rendering layouts using any strategy
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```tsx
|
|
125
|
+
* <LayoutHost
|
|
126
|
+
* strategy={splitStrategy}
|
|
127
|
+
* panels={panelMap}
|
|
128
|
+
* storageKey="my_layout_state"
|
|
129
|
+
* onLayoutChange={(layout) => console.log('Layout changed', layout)}
|
|
130
|
+
* />
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
declare function LayoutHost<TLayout extends LayoutModel = LayoutModel>(props: LayoutHostProps<TLayout>): react_jsx_runtime.JSX.Element;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Hook for persisting layout state to localStorage
|
|
137
|
+
* @param strategy - The layout strategy being used
|
|
138
|
+
* @param storageKey - localStorage key (optional, if not provided returns undefined)
|
|
139
|
+
* @param dep - Dependency to trigger re-read from storage when it changes
|
|
140
|
+
* @returns Tuple of [currentLayout, setLayout]
|
|
141
|
+
*/
|
|
142
|
+
declare function useLayoutPersistence<TLayout extends LayoutModel>(strategy: LayoutStrategy<TLayout>, storageKey?: string, dep?: unknown): [TLayout | undefined, (layout: TLayout) => void];
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Generic preset shape: id, name, and strategy-specific rule.
|
|
146
|
+
*/
|
|
147
|
+
interface LayoutPreset<TRule = unknown> {
|
|
148
|
+
id: string;
|
|
149
|
+
name: string;
|
|
150
|
+
rule: TRule;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Storage key configuration per strategy (split vs grid use different keys).
|
|
154
|
+
*/
|
|
155
|
+
interface LayoutRuleManagerOptions {
|
|
156
|
+
/** localStorage key for persisting the selected preset id. */
|
|
157
|
+
presetIdStorageKey: string;
|
|
158
|
+
/** Base key for layout persistence; actual key is `${layoutStorageKeyPrefix}_${presetId}`. */
|
|
159
|
+
layoutStorageKeyPrefix: string;
|
|
160
|
+
/**
|
|
161
|
+
* When false, no localStorage read/write; preset selection kept in memory only.
|
|
162
|
+
* Default true for backward compatibility.
|
|
163
|
+
*/
|
|
164
|
+
persist?: boolean;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Manages layout presets, selected preset persistence, and reset.
|
|
168
|
+
* Persistence of the user-selected rule (preset id) is done via presetIdStorageKey.
|
|
169
|
+
* When persist is false, selection is kept in memory only (no localStorage).
|
|
170
|
+
*/
|
|
171
|
+
declare class LayoutRuleManager<TRule = unknown> {
|
|
172
|
+
private readonly _presets;
|
|
173
|
+
private readonly options;
|
|
174
|
+
/** In-memory selected preset id when persist is false. */
|
|
175
|
+
private _selectedId?;
|
|
176
|
+
constructor(presets: LayoutPreset<TRule>[], options: LayoutRuleManagerOptions);
|
|
177
|
+
/** Whether persistence to localStorage is enabled. */
|
|
178
|
+
private get persist();
|
|
179
|
+
/** Read-only preset list. */
|
|
180
|
+
get presets(): readonly LayoutPreset<TRule>[];
|
|
181
|
+
/**
|
|
182
|
+
* Reads selected preset id from localStorage (presetIdStorageKey).
|
|
183
|
+
* When persist is false, returns in-memory selection or first preset.
|
|
184
|
+
* Validates against presets; falls back to first preset id when missing or invalid.
|
|
185
|
+
*/
|
|
186
|
+
getSelectedPresetId(): string;
|
|
187
|
+
/**
|
|
188
|
+
* Writes selected preset id to localStorage (presetIdStorageKey).
|
|
189
|
+
* When persist is false, only updates in-memory selection.
|
|
190
|
+
* Validates id exists in presets before writing.
|
|
191
|
+
*/
|
|
192
|
+
setSelectedPresetId(id: string): void;
|
|
193
|
+
/** Returns the preset for the currently selected id. */
|
|
194
|
+
getSelectedPreset(): LayoutPreset<TRule> | undefined;
|
|
195
|
+
/**
|
|
196
|
+
* Storage key for layout persistence for the current preset.
|
|
197
|
+
* Used by LayoutHost: `${layoutStorageKeyPrefix}_${selectedPresetId}`.
|
|
198
|
+
*/
|
|
199
|
+
getLayoutStorageKey(): string;
|
|
200
|
+
/**
|
|
201
|
+
* Clears persisted layout data for the current preset (reset to preset rule).
|
|
202
|
+
* After this, next load will use getInitialLayout() from preset rule.
|
|
203
|
+
* No-op when persist is false (nothing to clear).
|
|
204
|
+
*/
|
|
205
|
+
reset(): void;
|
|
206
|
+
/**
|
|
207
|
+
* Returns whether the current preset has any persisted layout data.
|
|
208
|
+
* Useful for UI "custom" badge or "Reset to default" visibility.
|
|
209
|
+
* Returns false when persist is false.
|
|
210
|
+
*/
|
|
211
|
+
hasPersistedLayout(): boolean;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
interface UseLayoutRuleManagerResult<TRule> {
|
|
215
|
+
presets: readonly LayoutPreset<TRule>[];
|
|
216
|
+
selectedPresetId: string;
|
|
217
|
+
setSelectedPresetId: (id: string) => void;
|
|
218
|
+
layoutStorageKey: string;
|
|
219
|
+
reset: () => void;
|
|
220
|
+
getSelectedPreset: () => LayoutPreset<TRule> | undefined;
|
|
221
|
+
hasPersistedLayout: () => boolean;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Binds a LayoutRuleManager to React state so selection and reset trigger re-renders.
|
|
225
|
+
* setSelectedPresetId updates both the manager (localStorage) and local state.
|
|
226
|
+
*
|
|
227
|
+
* @param manager - LayoutRuleManager instance (e.g. from LayoutRuleManager<SplitLayoutRule>)
|
|
228
|
+
* @returns Presets, selectedPresetId, setSelectedPresetId, layoutStorageKey, reset, getSelectedPreset, hasPersistedLayout
|
|
229
|
+
*/
|
|
230
|
+
declare function useLayoutRuleManager<TRule>(manager: LayoutRuleManager<TRule>): UseLayoutRuleManagerResult<TRule>;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Resolve which strategy to use based on preferred ID and available strategies
|
|
234
|
+
* Ensures we always return a valid strategy, falling back to default if preferred is not available
|
|
235
|
+
*
|
|
236
|
+
* @param options - Resolver options
|
|
237
|
+
* @returns Resolved strategy and its ID
|
|
238
|
+
*/
|
|
239
|
+
declare function resolveStrategy(options: StrategyResolverOptions): {
|
|
240
|
+
strategy: LayoutStrategy;
|
|
241
|
+
id: string;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export { LayoutHost, type LayoutHostProps, type LayoutModel, type LayoutPreset, type LayoutRendererProps, LayoutRuleManager, type LayoutRuleManagerOptions, type LayoutStrategy, type OnLayoutChange, type OnLayoutPersist, type PanelRegistry, type StrategyResolverOptions, TRADING_PANEL_IDS, type TradingPanelId, type UseLayoutRuleManagerResult, getTradingPanelIds, resolveStrategy, useLayoutPersistence, useLayoutRuleManager };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/tradingPanelIds.ts
|
|
7
|
+
var TRADING_PANEL_IDS = {
|
|
8
|
+
SYMBOL_INFO_BAR: "symbolInfoBar",
|
|
9
|
+
TRADING_VIEW: "tradingView",
|
|
10
|
+
ORDERBOOK: "orderbook",
|
|
11
|
+
DATA_LIST: "dataList",
|
|
12
|
+
ORDER_ENTRY: "orderEntry",
|
|
13
|
+
MARGIN: "orderEntryMargin",
|
|
14
|
+
ASSETS: "orderEntryAssets",
|
|
15
|
+
// MAIN: "orderEntryMain",
|
|
16
|
+
MARKETS: "markets",
|
|
17
|
+
HORIZONTAL_MARKETS: "horizontalMarkets"
|
|
18
|
+
};
|
|
19
|
+
function getTradingPanelIds() {
|
|
20
|
+
return Object.values(TRADING_PANEL_IDS);
|
|
21
|
+
}
|
|
22
|
+
function canUseLocalStorage() {
|
|
23
|
+
try {
|
|
24
|
+
return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function useLayoutPersistence(strategy, storageKey, dep) {
|
|
30
|
+
const debounceTimerRef = react.useRef(null);
|
|
31
|
+
const layout = react.useMemo(() => {
|
|
32
|
+
if (!storageKey || !canUseLocalStorage()) {
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const stored = localStorage.getItem(storageKey);
|
|
37
|
+
if (stored) {
|
|
38
|
+
return strategy.deserialize(stored);
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
}
|
|
42
|
+
return void 0;
|
|
43
|
+
}, [strategy, storageKey, dep]);
|
|
44
|
+
react.useEffect(() => {
|
|
45
|
+
return () => {
|
|
46
|
+
if (debounceTimerRef.current) {
|
|
47
|
+
clearTimeout(debounceTimerRef.current);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}, []);
|
|
51
|
+
const setLayout = react.useCallback(
|
|
52
|
+
(newLayout) => {
|
|
53
|
+
if (!storageKey || !canUseLocalStorage()) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (debounceTimerRef.current) {
|
|
57
|
+
clearTimeout(debounceTimerRef.current);
|
|
58
|
+
}
|
|
59
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
60
|
+
try {
|
|
61
|
+
const serialized = strategy.serialize(newLayout);
|
|
62
|
+
localStorage.setItem(storageKey, serialized);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
}
|
|
65
|
+
}, 100);
|
|
66
|
+
},
|
|
67
|
+
[strategy, storageKey]
|
|
68
|
+
);
|
|
69
|
+
return [layout, setLayout];
|
|
70
|
+
}
|
|
71
|
+
function isMap(value) {
|
|
72
|
+
return value !== null && typeof value === "object" && typeof value.get === "function" && typeof value.keys === "function";
|
|
73
|
+
}
|
|
74
|
+
function LayoutHost(props) {
|
|
75
|
+
const {
|
|
76
|
+
strategy,
|
|
77
|
+
panels: panelsInput,
|
|
78
|
+
initialLayout,
|
|
79
|
+
onLayoutChange,
|
|
80
|
+
storageKey,
|
|
81
|
+
className,
|
|
82
|
+
style
|
|
83
|
+
} = props;
|
|
84
|
+
const panels = react.useMemo(() => {
|
|
85
|
+
if (isMap(panelsInput)) {
|
|
86
|
+
return panelsInput;
|
|
87
|
+
}
|
|
88
|
+
const map = /* @__PURE__ */ new Map();
|
|
89
|
+
Object.entries(panelsInput).forEach(([id, entry]) => {
|
|
90
|
+
if (entry && typeof entry === "object" && "node" in entry) {
|
|
91
|
+
map.set(id, entry);
|
|
92
|
+
} else {
|
|
93
|
+
map.set(id, { node: entry, props: {} });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return map;
|
|
97
|
+
}, [panelsInput]);
|
|
98
|
+
const panelIds = react.useMemo(() => Array.from(panels.keys()), [panels]);
|
|
99
|
+
const [persistedLayout, setPersistedLayout] = useLayoutPersistence(
|
|
100
|
+
strategy,
|
|
101
|
+
storageKey,
|
|
102
|
+
strategy.id
|
|
103
|
+
// Re-read when strategy changes
|
|
104
|
+
);
|
|
105
|
+
const resolvedLayout = react.useMemo(() => {
|
|
106
|
+
if (persistedLayout) {
|
|
107
|
+
return persistedLayout;
|
|
108
|
+
}
|
|
109
|
+
if (initialLayout) {
|
|
110
|
+
return initialLayout;
|
|
111
|
+
}
|
|
112
|
+
return strategy.defaultLayout(panelIds);
|
|
113
|
+
}, [persistedLayout, initialLayout, strategy, panelIds]);
|
|
114
|
+
const [currentLayout, setCurrentLayout] = react.useState(resolvedLayout);
|
|
115
|
+
react.useEffect(() => {
|
|
116
|
+
setCurrentLayout(resolvedLayout);
|
|
117
|
+
}, [resolvedLayout]);
|
|
118
|
+
const handleLayoutChange = react.useCallback(
|
|
119
|
+
(newLayout) => {
|
|
120
|
+
setCurrentLayout(newLayout);
|
|
121
|
+
onLayoutChange?.(newLayout);
|
|
122
|
+
},
|
|
123
|
+
[onLayoutChange]
|
|
124
|
+
);
|
|
125
|
+
const handleLayoutPersist = react.useCallback(
|
|
126
|
+
(newLayout) => {
|
|
127
|
+
if (storageKey) {
|
|
128
|
+
setPersistedLayout(newLayout);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
[storageKey, setPersistedLayout]
|
|
132
|
+
);
|
|
133
|
+
const Renderer = strategy.Renderer;
|
|
134
|
+
if (panels.size === 0) {
|
|
135
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style });
|
|
136
|
+
}
|
|
137
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
138
|
+
Renderer,
|
|
139
|
+
{
|
|
140
|
+
layout: currentLayout,
|
|
141
|
+
panels,
|
|
142
|
+
onLayoutChange: handleLayoutChange,
|
|
143
|
+
onLayoutPersist: handleLayoutPersist,
|
|
144
|
+
className,
|
|
145
|
+
style
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
function useLayoutRuleManager(manager) {
|
|
150
|
+
const [selectedPresetId, setSelectedPresetIdState] = react.useState(
|
|
151
|
+
() => manager.getSelectedPresetId()
|
|
152
|
+
);
|
|
153
|
+
react.useEffect(() => {
|
|
154
|
+
setSelectedPresetIdState(manager.getSelectedPresetId());
|
|
155
|
+
}, [manager]);
|
|
156
|
+
const setSelectedPresetId = react.useCallback(
|
|
157
|
+
(id) => {
|
|
158
|
+
manager.setSelectedPresetId(id);
|
|
159
|
+
setSelectedPresetIdState(id);
|
|
160
|
+
},
|
|
161
|
+
[manager]
|
|
162
|
+
);
|
|
163
|
+
const reset = react.useCallback(() => {
|
|
164
|
+
manager.reset();
|
|
165
|
+
setSelectedPresetIdState((prev) => prev);
|
|
166
|
+
}, [manager]);
|
|
167
|
+
const layoutStorageKey = react.useMemo(
|
|
168
|
+
() => manager.getLayoutStorageKey(),
|
|
169
|
+
[manager, selectedPresetId]
|
|
170
|
+
);
|
|
171
|
+
const getSelectedPreset = react.useCallback(
|
|
172
|
+
() => manager.getSelectedPreset(),
|
|
173
|
+
[manager]
|
|
174
|
+
);
|
|
175
|
+
const hasPersistedLayout = react.useCallback(
|
|
176
|
+
() => manager.hasPersistedLayout(),
|
|
177
|
+
[manager]
|
|
178
|
+
);
|
|
179
|
+
return react.useMemo(
|
|
180
|
+
() => ({
|
|
181
|
+
presets: manager.presets,
|
|
182
|
+
selectedPresetId,
|
|
183
|
+
setSelectedPresetId,
|
|
184
|
+
layoutStorageKey,
|
|
185
|
+
reset,
|
|
186
|
+
getSelectedPreset,
|
|
187
|
+
hasPersistedLayout
|
|
188
|
+
}),
|
|
189
|
+
[
|
|
190
|
+
manager,
|
|
191
|
+
selectedPresetId,
|
|
192
|
+
setSelectedPresetId,
|
|
193
|
+
layoutStorageKey,
|
|
194
|
+
reset,
|
|
195
|
+
getSelectedPreset,
|
|
196
|
+
hasPersistedLayout
|
|
197
|
+
]
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/utils/strategyResolver.ts
|
|
202
|
+
function resolveStrategy(options) {
|
|
203
|
+
const { preferredId, availableStrategies, defaultStrategy } = options;
|
|
204
|
+
if (preferredId) {
|
|
205
|
+
const preferred = availableStrategies.find((s) => s.id === preferredId);
|
|
206
|
+
if (preferred) {
|
|
207
|
+
return { strategy: preferred, id: preferredId };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (defaultStrategy) {
|
|
211
|
+
return { strategy: defaultStrategy, id: defaultStrategy.id };
|
|
212
|
+
}
|
|
213
|
+
if (availableStrategies.length > 0) {
|
|
214
|
+
const first = availableStrategies[0];
|
|
215
|
+
return { strategy: first, id: first.id };
|
|
216
|
+
}
|
|
217
|
+
throw new Error(
|
|
218
|
+
"No layout strategy available. Please ensure at least one strategy is provided."
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/utils/LayoutRuleManager.ts
|
|
223
|
+
function hasStorage() {
|
|
224
|
+
return typeof window !== "undefined" && !!window.localStorage;
|
|
225
|
+
}
|
|
226
|
+
var LayoutRuleManager = class {
|
|
227
|
+
constructor(presets, options) {
|
|
228
|
+
this._presets = presets.length > 0 ? [...presets] : [];
|
|
229
|
+
this.options = { ...options };
|
|
230
|
+
}
|
|
231
|
+
/** Whether persistence to localStorage is enabled. */
|
|
232
|
+
get persist() {
|
|
233
|
+
return this.options.persist !== false;
|
|
234
|
+
}
|
|
235
|
+
/** Read-only preset list. */
|
|
236
|
+
get presets() {
|
|
237
|
+
return this._presets;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Reads selected preset id from localStorage (presetIdStorageKey).
|
|
241
|
+
* When persist is false, returns in-memory selection or first preset.
|
|
242
|
+
* Validates against presets; falls back to first preset id when missing or invalid.
|
|
243
|
+
*/
|
|
244
|
+
getSelectedPresetId() {
|
|
245
|
+
if (this._presets.length === 0) {
|
|
246
|
+
return "";
|
|
247
|
+
}
|
|
248
|
+
if (!this.persist) {
|
|
249
|
+
return this._selectedId ?? this._presets[0]?.id ?? "";
|
|
250
|
+
}
|
|
251
|
+
if (!hasStorage()) {
|
|
252
|
+
return this._presets[0]?.id ?? "";
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const stored = window.localStorage.getItem(
|
|
256
|
+
this.options.presetIdStorageKey
|
|
257
|
+
);
|
|
258
|
+
const valid = stored && this._presets.some((p) => p.id === stored) ? stored : this._presets[0]?.id ?? "";
|
|
259
|
+
return valid;
|
|
260
|
+
} catch {
|
|
261
|
+
return this._presets[0]?.id ?? "";
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Writes selected preset id to localStorage (presetIdStorageKey).
|
|
266
|
+
* When persist is false, only updates in-memory selection.
|
|
267
|
+
* Validates id exists in presets before writing.
|
|
268
|
+
*/
|
|
269
|
+
setSelectedPresetId(id) {
|
|
270
|
+
if (!this._presets.some((p) => p.id === id)) return;
|
|
271
|
+
if (!this.persist) {
|
|
272
|
+
this._selectedId = id;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (!hasStorage()) return;
|
|
276
|
+
try {
|
|
277
|
+
window.localStorage.setItem(this.options.presetIdStorageKey, id);
|
|
278
|
+
} catch {
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/** Returns the preset for the currently selected id. */
|
|
282
|
+
getSelectedPreset() {
|
|
283
|
+
const id = this.getSelectedPresetId();
|
|
284
|
+
return this._presets.find((p) => p.id === id);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Storage key for layout persistence for the current preset.
|
|
288
|
+
* Used by LayoutHost: `${layoutStorageKeyPrefix}_${selectedPresetId}`.
|
|
289
|
+
*/
|
|
290
|
+
getLayoutStorageKey() {
|
|
291
|
+
const id = this.getSelectedPresetId();
|
|
292
|
+
return `${this.options.layoutStorageKeyPrefix}_${id}`;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Clears persisted layout data for the current preset (reset to preset rule).
|
|
296
|
+
* After this, next load will use getInitialLayout() from preset rule.
|
|
297
|
+
* No-op when persist is false (nothing to clear).
|
|
298
|
+
*/
|
|
299
|
+
reset() {
|
|
300
|
+
if (!this.persist || !hasStorage()) return;
|
|
301
|
+
try {
|
|
302
|
+
window.localStorage.removeItem(this.getLayoutStorageKey());
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Returns whether the current preset has any persisted layout data.
|
|
308
|
+
* Useful for UI "custom" badge or "Reset to default" visibility.
|
|
309
|
+
* Returns false when persist is false.
|
|
310
|
+
*/
|
|
311
|
+
hasPersistedLayout() {
|
|
312
|
+
if (!this.persist || !hasStorage()) return false;
|
|
313
|
+
try {
|
|
314
|
+
const key = this.getLayoutStorageKey();
|
|
315
|
+
const value = window.localStorage.getItem(key);
|
|
316
|
+
return value != null && value.length > 0;
|
|
317
|
+
} catch {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
exports.LayoutHost = LayoutHost;
|
|
324
|
+
exports.LayoutRuleManager = LayoutRuleManager;
|
|
325
|
+
exports.TRADING_PANEL_IDS = TRADING_PANEL_IDS;
|
|
326
|
+
exports.getTradingPanelIds = getTradingPanelIds;
|
|
327
|
+
exports.resolveStrategy = resolveStrategy;
|
|
328
|
+
exports.useLayoutPersistence = useLayoutPersistence;
|
|
329
|
+
exports.useLayoutRuleManager = useLayoutRuleManager;
|
|
330
|
+
//# sourceMappingURL=index.js.map
|
|
331
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tradingPanelIds.ts","../src/hooks/useLayoutPersistence.ts","../src/LayoutHost.tsx","../src/hooks/useLayoutRuleManager.ts","../src/utils/strategyResolver.ts","../src/utils/LayoutRuleManager.ts"],"names":["useRef","useMemo","useEffect","useCallback","useState","jsx"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Trading page panel ID constants.\n * Used across layout strategies (split, grid) to identify trading panels.\n * Component mapping happens in the trading package.\n */\nexport const TRADING_PANEL_IDS = {\n SYMBOL_INFO_BAR: \"symbolInfoBar\",\n TRADING_VIEW: \"tradingView\",\n ORDERBOOK: \"orderbook\",\n DATA_LIST: \"dataList\",\n ORDER_ENTRY: \"orderEntry\",\n MARGIN: \"orderEntryMargin\",\n ASSETS: \"orderEntryAssets\",\n // MAIN: \"orderEntryMain\",\n MARKETS: \"markets\",\n HORIZONTAL_MARKETS: \"horizontalMarkets\",\n} as const;\n\nexport type TradingPanelId =\n (typeof TRADING_PANEL_IDS)[keyof typeof TRADING_PANEL_IDS];\n\n/**\n * Returns all trading panel IDs from layout-core.\n * Used by layout strategies (split, grid) to derive panel set; component mapping is in trading package.\n */\nexport function getTradingPanelIds(): readonly string[] {\n return Object.values(TRADING_PANEL_IDS);\n}\n","import { useMemo, useCallback, useRef, useEffect } from \"react\";\nimport type { LayoutModel, LayoutStrategy } from \"../types\";\n\n/**\n * Check if we're in a browser environment with localStorage available\n */\nfunction canUseLocalStorage(): boolean {\n try {\n return (\n typeof window !== \"undefined\" &&\n typeof window.localStorage !== \"undefined\"\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Hook for persisting layout state to localStorage\n * @param strategy - The layout strategy being used\n * @param storageKey - localStorage key (optional, if not provided returns undefined)\n * @param dep - Dependency to trigger re-read from storage when it changes\n * @returns Tuple of [currentLayout, setLayout]\n */\nexport function useLayoutPersistence<TLayout extends LayoutModel>(\n strategy: LayoutStrategy<TLayout>,\n storageKey?: string,\n dep?: unknown,\n): [TLayout | undefined, (layout: TLayout) => void] {\n // Debounce timer ref\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const layout = useMemo(() => {\n if (!storageKey || !canUseLocalStorage()) {\n return undefined;\n }\n\n try {\n const stored = localStorage.getItem(storageKey);\n if (stored) {\n return strategy.deserialize(stored);\n }\n } catch (error) {\n console.warn(`Failed to deserialize layout from ${storageKey}:`, error);\n }\n\n return undefined;\n }, [strategy, storageKey, dep]);\n\n // Cleanup debounce timer on unmount\n useEffect(() => {\n return () => {\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n };\n }, []);\n\n // Memoized setLayout with debouncing to avoid excessive writes\n const setLayout = useCallback(\n (newLayout: TLayout) => {\n if (!storageKey || !canUseLocalStorage()) {\n return;\n }\n\n // Clear existing debounce timer\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n\n // Debounce the write operation (100ms)\n debounceTimerRef.current = setTimeout(() => {\n try {\n const serialized = strategy.serialize(newLayout);\n localStorage.setItem(storageKey, serialized);\n } catch (error) {\n console.warn(`Failed to serialize layout to ${storageKey}:`, error);\n }\n }, 100);\n },\n [strategy, storageKey],\n );\n\n return [layout, setLayout];\n}\n","import React, { useMemo, useState, useEffect, useCallback } from \"react\";\nimport { useLayoutPersistence } from \"./hooks/useLayoutPersistence\";\nimport type {\n LayoutHostProps,\n LayoutModel,\n PanelRegistry,\n PanelRegistryEntry,\n} from \"./types\";\n\n/**\n * Check if a value is a Map instance (handles cross-realm scenarios)\n */\nfunction isMap(value: unknown): value is Map<string, PanelRegistryEntry> {\n return (\n value !== null &&\n typeof value === \"object\" &&\n typeof (value as Map<string, PanelRegistryEntry>).get === \"function\" &&\n typeof (value as Map<string, PanelRegistryEntry>).keys === \"function\"\n );\n}\n\n/**\n * LayoutHost component\n * Provides a unified interface for rendering layouts using any strategy\n *\n * @example\n * ```tsx\n * <LayoutHost\n * strategy={splitStrategy}\n * panels={panelMap}\n * storageKey=\"my_layout_state\"\n * onLayoutChange={(layout) => console.log('Layout changed', layout)}\n * />\n * ```\n */\nexport function LayoutHost<TLayout extends LayoutModel = LayoutModel>(\n props: LayoutHostProps<TLayout>,\n) {\n const {\n strategy,\n panels: panelsInput,\n initialLayout,\n onLayoutChange,\n storageKey,\n className,\n style,\n } = props;\n\n // Convert panels input to PanelRegistry (Map)\n // Uses duck-typing check for cross-realm compatibility\n const panels: PanelRegistry = useMemo(() => {\n if (isMap(panelsInput)) {\n return panelsInput;\n }\n // Convert Record to Map\n const map = new Map<string, PanelRegistryEntry>();\n Object.entries(panelsInput).forEach(([id, entry]) => {\n // Handle both old format (ReactNode) and new format (PanelRegistryEntry)\n if (entry && typeof entry === \"object\" && \"node\" in entry) {\n map.set(id, entry as PanelRegistryEntry);\n } else {\n map.set(id, { node: entry as React.ReactNode, props: {} });\n }\n });\n return map;\n }, [panelsInput]);\n\n // Get panel IDs from registry\n const panelIds = useMemo(() => Array.from(panels.keys()), [panels]);\n\n // Initialize layout state from persistence\n const [persistedLayout, setPersistedLayout] = useLayoutPersistence(\n strategy,\n storageKey,\n strategy.id, // Re-read when strategy changes\n );\n\n // Compute the resolved layout: persisted > initial > default\n const resolvedLayout = useMemo<TLayout>(() => {\n if (persistedLayout) {\n return persistedLayout;\n }\n if (initialLayout) {\n return initialLayout;\n }\n return strategy.defaultLayout(panelIds) as TLayout;\n }, [persistedLayout, initialLayout, strategy, panelIds]);\n\n // Internal layout state - initialized with resolved layout\n const [currentLayout, setCurrentLayout] = useState<TLayout>(resolvedLayout);\n\n // Sync when resolved layout changes (strategy switch, initial load, etc.)\n useEffect(() => {\n setCurrentLayout(resolvedLayout);\n }, [resolvedLayout]);\n\n // Handle layout changes - only updates internal state, no persistence\n const handleLayoutChange = useCallback(\n (newLayout: TLayout) => {\n setCurrentLayout(newLayout);\n // Notify parent\n onLayoutChange?.(newLayout);\n },\n [onLayoutChange],\n );\n\n // Handle layout persist - persists layout to storage\n const handleLayoutPersist = useCallback(\n (newLayout: TLayout) => {\n // Persist if storage key is provided\n if (storageKey) {\n setPersistedLayout(newLayout);\n }\n },\n [storageKey, setPersistedLayout],\n );\n\n // Get the Renderer component from strategy\n const Renderer = strategy.Renderer;\n\n // Handle empty panels case\n if (panels.size === 0) {\n return (\n <div className={className} style={style}>\n {/* Empty state - no panels registered */}\n </div>\n );\n }\n\n return (\n <Renderer\n layout={currentLayout}\n panels={panels}\n onLayoutChange={handleLayoutChange}\n onLayoutPersist={handleLayoutPersist}\n className={className}\n style={style}\n />\n );\n}\n","/**\n * @file useLayoutRuleManager.ts\n * @description React hook that syncs LayoutRuleManager with component state for presets, selection, and reset.\n */\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport type {\n LayoutPreset,\n LayoutRuleManager,\n} from \"../utils/LayoutRuleManager\";\n\nexport interface UseLayoutRuleManagerResult<TRule> {\n presets: readonly LayoutPreset<TRule>[];\n selectedPresetId: string;\n setSelectedPresetId: (id: string) => void;\n layoutStorageKey: string;\n reset: () => void;\n getSelectedPreset: () => LayoutPreset<TRule> | undefined;\n hasPersistedLayout: () => boolean;\n}\n\n/**\n * Binds a LayoutRuleManager to React state so selection and reset trigger re-renders.\n * setSelectedPresetId updates both the manager (localStorage) and local state.\n *\n * @param manager - LayoutRuleManager instance (e.g. from LayoutRuleManager<SplitLayoutRule>)\n * @returns Presets, selectedPresetId, setSelectedPresetId, layoutStorageKey, reset, getSelectedPreset, hasPersistedLayout\n */\nexport function useLayoutRuleManager<TRule>(\n manager: LayoutRuleManager<TRule>,\n): UseLayoutRuleManagerResult<TRule> {\n const [selectedPresetId, setSelectedPresetIdState] = useState<string>(() =>\n manager.getSelectedPresetId(),\n );\n\n /** Sync state when manager instance changes (e.g. presets changed). */\n useEffect(() => {\n setSelectedPresetIdState(manager.getSelectedPresetId());\n }, [manager]);\n\n const setSelectedPresetId = useCallback(\n (id: string) => {\n manager.setSelectedPresetId(id);\n setSelectedPresetIdState(id);\n },\n [manager],\n );\n\n const reset = useCallback(() => {\n manager.reset();\n // Optionally force re-read selected id (no change) so consumers can react to reset\n setSelectedPresetIdState((prev) => prev);\n }, [manager]);\n\n const layoutStorageKey = useMemo(\n () => manager.getLayoutStorageKey(),\n [manager, selectedPresetId],\n );\n\n const getSelectedPreset = useCallback(\n () => manager.getSelectedPreset(),\n [manager],\n );\n\n const hasPersistedLayout = useCallback(\n () => manager.hasPersistedLayout(),\n [manager],\n );\n\n return useMemo(\n () => ({\n presets: manager.presets,\n selectedPresetId,\n setSelectedPresetId,\n layoutStorageKey,\n reset,\n getSelectedPreset,\n hasPersistedLayout,\n }),\n [\n manager,\n selectedPresetId,\n setSelectedPresetId,\n layoutStorageKey,\n reset,\n getSelectedPreset,\n hasPersistedLayout,\n ],\n );\n}\n","import type { LayoutStrategy, StrategyResolverOptions } from \"../types\";\n\n/**\n * Resolve which strategy to use based on preferred ID and available strategies\n * Ensures we always return a valid strategy, falling back to default if preferred is not available\n *\n * @param options - Resolver options\n * @returns Resolved strategy and its ID\n */\nexport function resolveStrategy(options: StrategyResolverOptions): {\n strategy: LayoutStrategy;\n id: string;\n} {\n const { preferredId, availableStrategies, defaultStrategy } = options;\n\n // If preferred ID is provided, try to find it\n if (preferredId) {\n const preferred = availableStrategies.find((s) => s.id === preferredId);\n if (preferred) {\n return { strategy: preferred, id: preferredId };\n }\n }\n\n // Fall back to default strategy if provided\n if (defaultStrategy) {\n return { strategy: defaultStrategy, id: defaultStrategy.id };\n }\n\n // Fall back to first available strategy\n if (availableStrategies.length > 0) {\n const first = availableStrategies[0];\n return { strategy: first, id: first.id };\n }\n\n // This should never happen if strategies are properly set up\n throw new Error(\n \"No layout strategy available. Please ensure at least one strategy is provided.\",\n );\n}\n","/** SSR-safe check for localStorage availability. */\nfunction hasStorage(): boolean {\n return typeof window !== \"undefined\" && !!window.localStorage;\n}\n\n/**\n * Generic preset shape: id, name, and strategy-specific rule.\n */\nexport interface LayoutPreset<TRule = unknown> {\n id: string;\n name: string;\n rule: TRule;\n}\n\n/**\n * Storage key configuration per strategy (split vs grid use different keys).\n */\nexport interface LayoutRuleManagerOptions {\n /** localStorage key for persisting the selected preset id. */\n presetIdStorageKey: string;\n /** Base key for layout persistence; actual key is `${layoutStorageKeyPrefix}_${presetId}`. */\n layoutStorageKeyPrefix: string;\n /**\n * When false, no localStorage read/write; preset selection kept in memory only.\n * Default true for backward compatibility.\n */\n persist?: boolean;\n}\n\n/**\n * Manages layout presets, selected preset persistence, and reset.\n * Persistence of the user-selected rule (preset id) is done via presetIdStorageKey.\n * When persist is false, selection is kept in memory only (no localStorage).\n */\nexport class LayoutRuleManager<TRule = unknown> {\n private readonly _presets: readonly LayoutPreset<TRule>[];\n private readonly options: LayoutRuleManagerOptions;\n /** In-memory selected preset id when persist is false. */\n private _selectedId?: string;\n\n constructor(\n presets: LayoutPreset<TRule>[],\n options: LayoutRuleManagerOptions,\n ) {\n this._presets = presets.length > 0 ? [...presets] : [];\n this.options = { ...options };\n }\n\n /** Whether persistence to localStorage is enabled. */\n private get persist(): boolean {\n return this.options.persist !== false;\n }\n\n /** Read-only preset list. */\n get presets(): readonly LayoutPreset<TRule>[] {\n return this._presets;\n }\n\n /**\n * Reads selected preset id from localStorage (presetIdStorageKey).\n * When persist is false, returns in-memory selection or first preset.\n * Validates against presets; falls back to first preset id when missing or invalid.\n */\n getSelectedPresetId(): string {\n if (this._presets.length === 0) {\n return \"\";\n }\n if (!this.persist) {\n return this._selectedId ?? this._presets[0]?.id ?? \"\";\n }\n if (!hasStorage()) {\n return this._presets[0]?.id ?? \"\";\n }\n try {\n const stored = window.localStorage.getItem(\n this.options.presetIdStorageKey,\n );\n const valid =\n stored && this._presets.some((p) => p.id === stored)\n ? stored\n : (this._presets[0]?.id ?? \"\");\n return valid;\n } catch {\n return this._presets[0]?.id ?? \"\";\n }\n }\n\n /**\n * Writes selected preset id to localStorage (presetIdStorageKey).\n * When persist is false, only updates in-memory selection.\n * Validates id exists in presets before writing.\n */\n setSelectedPresetId(id: string): void {\n if (!this._presets.some((p) => p.id === id)) return;\n if (!this.persist) {\n this._selectedId = id;\n return;\n }\n if (!hasStorage()) return;\n try {\n window.localStorage.setItem(this.options.presetIdStorageKey, id);\n } catch {\n // ignore\n }\n }\n\n /** Returns the preset for the currently selected id. */\n getSelectedPreset(): LayoutPreset<TRule> | undefined {\n const id = this.getSelectedPresetId();\n return this._presets.find((p) => p.id === id);\n }\n\n /**\n * Storage key for layout persistence for the current preset.\n * Used by LayoutHost: `${layoutStorageKeyPrefix}_${selectedPresetId}`.\n */\n getLayoutStorageKey(): string {\n const id = this.getSelectedPresetId();\n return `${this.options.layoutStorageKeyPrefix}_${id}`;\n }\n\n /**\n * Clears persisted layout data for the current preset (reset to preset rule).\n * After this, next load will use getInitialLayout() from preset rule.\n * No-op when persist is false (nothing to clear).\n */\n reset(): void {\n if (!this.persist || !hasStorage()) return;\n try {\n window.localStorage.removeItem(this.getLayoutStorageKey());\n } catch {\n // ignore\n }\n }\n\n /**\n * Returns whether the current preset has any persisted layout data.\n * Useful for UI \"custom\" badge or \"Reset to default\" visibility.\n * Returns false when persist is false.\n */\n hasPersistedLayout(): boolean {\n if (!this.persist || !hasStorage()) return false;\n try {\n const key = this.getLayoutStorageKey();\n const value = window.localStorage.getItem(key);\n return value != null && value.length > 0;\n } catch {\n return false;\n }\n }\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { useRef, useMemo, useEffect, useCallback, useState } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/tradingPanelIds.ts
|
|
5
|
+
var TRADING_PANEL_IDS = {
|
|
6
|
+
SYMBOL_INFO_BAR: "symbolInfoBar",
|
|
7
|
+
TRADING_VIEW: "tradingView",
|
|
8
|
+
ORDERBOOK: "orderbook",
|
|
9
|
+
DATA_LIST: "dataList",
|
|
10
|
+
ORDER_ENTRY: "orderEntry",
|
|
11
|
+
MARGIN: "orderEntryMargin",
|
|
12
|
+
ASSETS: "orderEntryAssets",
|
|
13
|
+
// MAIN: "orderEntryMain",
|
|
14
|
+
MARKETS: "markets",
|
|
15
|
+
HORIZONTAL_MARKETS: "horizontalMarkets"
|
|
16
|
+
};
|
|
17
|
+
function getTradingPanelIds() {
|
|
18
|
+
return Object.values(TRADING_PANEL_IDS);
|
|
19
|
+
}
|
|
20
|
+
function canUseLocalStorage() {
|
|
21
|
+
try {
|
|
22
|
+
return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function useLayoutPersistence(strategy, storageKey, dep) {
|
|
28
|
+
const debounceTimerRef = useRef(null);
|
|
29
|
+
const layout = useMemo(() => {
|
|
30
|
+
if (!storageKey || !canUseLocalStorage()) {
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const stored = localStorage.getItem(storageKey);
|
|
35
|
+
if (stored) {
|
|
36
|
+
return strategy.deserialize(stored);
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
}
|
|
40
|
+
return void 0;
|
|
41
|
+
}, [strategy, storageKey, dep]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
return () => {
|
|
44
|
+
if (debounceTimerRef.current) {
|
|
45
|
+
clearTimeout(debounceTimerRef.current);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}, []);
|
|
49
|
+
const setLayout = useCallback(
|
|
50
|
+
(newLayout) => {
|
|
51
|
+
if (!storageKey || !canUseLocalStorage()) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (debounceTimerRef.current) {
|
|
55
|
+
clearTimeout(debounceTimerRef.current);
|
|
56
|
+
}
|
|
57
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
58
|
+
try {
|
|
59
|
+
const serialized = strategy.serialize(newLayout);
|
|
60
|
+
localStorage.setItem(storageKey, serialized);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
}
|
|
63
|
+
}, 100);
|
|
64
|
+
},
|
|
65
|
+
[strategy, storageKey]
|
|
66
|
+
);
|
|
67
|
+
return [layout, setLayout];
|
|
68
|
+
}
|
|
69
|
+
function isMap(value) {
|
|
70
|
+
return value !== null && typeof value === "object" && typeof value.get === "function" && typeof value.keys === "function";
|
|
71
|
+
}
|
|
72
|
+
function LayoutHost(props) {
|
|
73
|
+
const {
|
|
74
|
+
strategy,
|
|
75
|
+
panels: panelsInput,
|
|
76
|
+
initialLayout,
|
|
77
|
+
onLayoutChange,
|
|
78
|
+
storageKey,
|
|
79
|
+
className,
|
|
80
|
+
style
|
|
81
|
+
} = props;
|
|
82
|
+
const panels = useMemo(() => {
|
|
83
|
+
if (isMap(panelsInput)) {
|
|
84
|
+
return panelsInput;
|
|
85
|
+
}
|
|
86
|
+
const map = /* @__PURE__ */ new Map();
|
|
87
|
+
Object.entries(panelsInput).forEach(([id, entry]) => {
|
|
88
|
+
if (entry && typeof entry === "object" && "node" in entry) {
|
|
89
|
+
map.set(id, entry);
|
|
90
|
+
} else {
|
|
91
|
+
map.set(id, { node: entry, props: {} });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return map;
|
|
95
|
+
}, [panelsInput]);
|
|
96
|
+
const panelIds = useMemo(() => Array.from(panels.keys()), [panels]);
|
|
97
|
+
const [persistedLayout, setPersistedLayout] = useLayoutPersistence(
|
|
98
|
+
strategy,
|
|
99
|
+
storageKey,
|
|
100
|
+
strategy.id
|
|
101
|
+
// Re-read when strategy changes
|
|
102
|
+
);
|
|
103
|
+
const resolvedLayout = useMemo(() => {
|
|
104
|
+
if (persistedLayout) {
|
|
105
|
+
return persistedLayout;
|
|
106
|
+
}
|
|
107
|
+
if (initialLayout) {
|
|
108
|
+
return initialLayout;
|
|
109
|
+
}
|
|
110
|
+
return strategy.defaultLayout(panelIds);
|
|
111
|
+
}, [persistedLayout, initialLayout, strategy, panelIds]);
|
|
112
|
+
const [currentLayout, setCurrentLayout] = useState(resolvedLayout);
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
setCurrentLayout(resolvedLayout);
|
|
115
|
+
}, [resolvedLayout]);
|
|
116
|
+
const handleLayoutChange = useCallback(
|
|
117
|
+
(newLayout) => {
|
|
118
|
+
setCurrentLayout(newLayout);
|
|
119
|
+
onLayoutChange?.(newLayout);
|
|
120
|
+
},
|
|
121
|
+
[onLayoutChange]
|
|
122
|
+
);
|
|
123
|
+
const handleLayoutPersist = useCallback(
|
|
124
|
+
(newLayout) => {
|
|
125
|
+
if (storageKey) {
|
|
126
|
+
setPersistedLayout(newLayout);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
[storageKey, setPersistedLayout]
|
|
130
|
+
);
|
|
131
|
+
const Renderer = strategy.Renderer;
|
|
132
|
+
if (panels.size === 0) {
|
|
133
|
+
return /* @__PURE__ */ jsx("div", { className, style });
|
|
134
|
+
}
|
|
135
|
+
return /* @__PURE__ */ jsx(
|
|
136
|
+
Renderer,
|
|
137
|
+
{
|
|
138
|
+
layout: currentLayout,
|
|
139
|
+
panels,
|
|
140
|
+
onLayoutChange: handleLayoutChange,
|
|
141
|
+
onLayoutPersist: handleLayoutPersist,
|
|
142
|
+
className,
|
|
143
|
+
style
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
function useLayoutRuleManager(manager) {
|
|
148
|
+
const [selectedPresetId, setSelectedPresetIdState] = useState(
|
|
149
|
+
() => manager.getSelectedPresetId()
|
|
150
|
+
);
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
setSelectedPresetIdState(manager.getSelectedPresetId());
|
|
153
|
+
}, [manager]);
|
|
154
|
+
const setSelectedPresetId = useCallback(
|
|
155
|
+
(id) => {
|
|
156
|
+
manager.setSelectedPresetId(id);
|
|
157
|
+
setSelectedPresetIdState(id);
|
|
158
|
+
},
|
|
159
|
+
[manager]
|
|
160
|
+
);
|
|
161
|
+
const reset = useCallback(() => {
|
|
162
|
+
manager.reset();
|
|
163
|
+
setSelectedPresetIdState((prev) => prev);
|
|
164
|
+
}, [manager]);
|
|
165
|
+
const layoutStorageKey = useMemo(
|
|
166
|
+
() => manager.getLayoutStorageKey(),
|
|
167
|
+
[manager, selectedPresetId]
|
|
168
|
+
);
|
|
169
|
+
const getSelectedPreset = useCallback(
|
|
170
|
+
() => manager.getSelectedPreset(),
|
|
171
|
+
[manager]
|
|
172
|
+
);
|
|
173
|
+
const hasPersistedLayout = useCallback(
|
|
174
|
+
() => manager.hasPersistedLayout(),
|
|
175
|
+
[manager]
|
|
176
|
+
);
|
|
177
|
+
return useMemo(
|
|
178
|
+
() => ({
|
|
179
|
+
presets: manager.presets,
|
|
180
|
+
selectedPresetId,
|
|
181
|
+
setSelectedPresetId,
|
|
182
|
+
layoutStorageKey,
|
|
183
|
+
reset,
|
|
184
|
+
getSelectedPreset,
|
|
185
|
+
hasPersistedLayout
|
|
186
|
+
}),
|
|
187
|
+
[
|
|
188
|
+
manager,
|
|
189
|
+
selectedPresetId,
|
|
190
|
+
setSelectedPresetId,
|
|
191
|
+
layoutStorageKey,
|
|
192
|
+
reset,
|
|
193
|
+
getSelectedPreset,
|
|
194
|
+
hasPersistedLayout
|
|
195
|
+
]
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/utils/strategyResolver.ts
|
|
200
|
+
function resolveStrategy(options) {
|
|
201
|
+
const { preferredId, availableStrategies, defaultStrategy } = options;
|
|
202
|
+
if (preferredId) {
|
|
203
|
+
const preferred = availableStrategies.find((s) => s.id === preferredId);
|
|
204
|
+
if (preferred) {
|
|
205
|
+
return { strategy: preferred, id: preferredId };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (defaultStrategy) {
|
|
209
|
+
return { strategy: defaultStrategy, id: defaultStrategy.id };
|
|
210
|
+
}
|
|
211
|
+
if (availableStrategies.length > 0) {
|
|
212
|
+
const first = availableStrategies[0];
|
|
213
|
+
return { strategy: first, id: first.id };
|
|
214
|
+
}
|
|
215
|
+
throw new Error(
|
|
216
|
+
"No layout strategy available. Please ensure at least one strategy is provided."
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/utils/LayoutRuleManager.ts
|
|
221
|
+
function hasStorage() {
|
|
222
|
+
return typeof window !== "undefined" && !!window.localStorage;
|
|
223
|
+
}
|
|
224
|
+
var LayoutRuleManager = class {
|
|
225
|
+
constructor(presets, options) {
|
|
226
|
+
this._presets = presets.length > 0 ? [...presets] : [];
|
|
227
|
+
this.options = { ...options };
|
|
228
|
+
}
|
|
229
|
+
/** Whether persistence to localStorage is enabled. */
|
|
230
|
+
get persist() {
|
|
231
|
+
return this.options.persist !== false;
|
|
232
|
+
}
|
|
233
|
+
/** Read-only preset list. */
|
|
234
|
+
get presets() {
|
|
235
|
+
return this._presets;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Reads selected preset id from localStorage (presetIdStorageKey).
|
|
239
|
+
* When persist is false, returns in-memory selection or first preset.
|
|
240
|
+
* Validates against presets; falls back to first preset id when missing or invalid.
|
|
241
|
+
*/
|
|
242
|
+
getSelectedPresetId() {
|
|
243
|
+
if (this._presets.length === 0) {
|
|
244
|
+
return "";
|
|
245
|
+
}
|
|
246
|
+
if (!this.persist) {
|
|
247
|
+
return this._selectedId ?? this._presets[0]?.id ?? "";
|
|
248
|
+
}
|
|
249
|
+
if (!hasStorage()) {
|
|
250
|
+
return this._presets[0]?.id ?? "";
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const stored = window.localStorage.getItem(
|
|
254
|
+
this.options.presetIdStorageKey
|
|
255
|
+
);
|
|
256
|
+
const valid = stored && this._presets.some((p) => p.id === stored) ? stored : this._presets[0]?.id ?? "";
|
|
257
|
+
return valid;
|
|
258
|
+
} catch {
|
|
259
|
+
return this._presets[0]?.id ?? "";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Writes selected preset id to localStorage (presetIdStorageKey).
|
|
264
|
+
* When persist is false, only updates in-memory selection.
|
|
265
|
+
* Validates id exists in presets before writing.
|
|
266
|
+
*/
|
|
267
|
+
setSelectedPresetId(id) {
|
|
268
|
+
if (!this._presets.some((p) => p.id === id)) return;
|
|
269
|
+
if (!this.persist) {
|
|
270
|
+
this._selectedId = id;
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (!hasStorage()) return;
|
|
274
|
+
try {
|
|
275
|
+
window.localStorage.setItem(this.options.presetIdStorageKey, id);
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/** Returns the preset for the currently selected id. */
|
|
280
|
+
getSelectedPreset() {
|
|
281
|
+
const id = this.getSelectedPresetId();
|
|
282
|
+
return this._presets.find((p) => p.id === id);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Storage key for layout persistence for the current preset.
|
|
286
|
+
* Used by LayoutHost: `${layoutStorageKeyPrefix}_${selectedPresetId}`.
|
|
287
|
+
*/
|
|
288
|
+
getLayoutStorageKey() {
|
|
289
|
+
const id = this.getSelectedPresetId();
|
|
290
|
+
return `${this.options.layoutStorageKeyPrefix}_${id}`;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Clears persisted layout data for the current preset (reset to preset rule).
|
|
294
|
+
* After this, next load will use getInitialLayout() from preset rule.
|
|
295
|
+
* No-op when persist is false (nothing to clear).
|
|
296
|
+
*/
|
|
297
|
+
reset() {
|
|
298
|
+
if (!this.persist || !hasStorage()) return;
|
|
299
|
+
try {
|
|
300
|
+
window.localStorage.removeItem(this.getLayoutStorageKey());
|
|
301
|
+
} catch {
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Returns whether the current preset has any persisted layout data.
|
|
306
|
+
* Useful for UI "custom" badge or "Reset to default" visibility.
|
|
307
|
+
* Returns false when persist is false.
|
|
308
|
+
*/
|
|
309
|
+
hasPersistedLayout() {
|
|
310
|
+
if (!this.persist || !hasStorage()) return false;
|
|
311
|
+
try {
|
|
312
|
+
const key = this.getLayoutStorageKey();
|
|
313
|
+
const value = window.localStorage.getItem(key);
|
|
314
|
+
return value != null && value.length > 0;
|
|
315
|
+
} catch {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
export { LayoutHost, LayoutRuleManager, TRADING_PANEL_IDS, getTradingPanelIds, resolveStrategy, useLayoutPersistence, useLayoutRuleManager };
|
|
322
|
+
//# sourceMappingURL=index.mjs.map
|
|
323
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tradingPanelIds.ts","../src/hooks/useLayoutPersistence.ts","../src/LayoutHost.tsx","../src/hooks/useLayoutRuleManager.ts","../src/utils/strategyResolver.ts","../src/utils/LayoutRuleManager.ts"],"names":["useMemo","useEffect","useCallback","useState"],"mappings":";;;;AAKO,IAAM,iBAAA,GAAoB;AAAA,EAC/B,eAAA,EAAiB,eAAA;AAAA,EACjB,YAAA,EAAc,aAAA;AAAA,EACd,SAAA,EAAW,WAAA;AAAA,EACX,SAAA,EAAW,UAAA;AAAA,EACX,WAAA,EAAa,YAAA;AAAA,EACb,MAAA,EAAQ,kBAAA;AAAA,EACR,MAAA,EAAQ,kBAAA;AAAA;AAAA,EAER,OAAA,EAAS,SAAA;AAAA,EACT,kBAAA,EAAoB;AACtB;AASO,SAAS,kBAAA,GAAwC;AACtD,EAAA,OAAO,MAAA,CAAO,OAAO,iBAAiB,CAAA;AACxC;ACrBA,SAAS,kBAAA,GAA8B;AACrC,EAAA,IAAI;AACF,IAAA,OACE,OAAO,MAAA,KAAW,WAAA,IAClB,OAAO,OAAO,YAAA,KAAiB,WAAA;AAAA,EAEnC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AASO,SAAS,oBAAA,CACd,QAAA,EACA,UAAA,EACA,GAAA,EACkD;AAElD,EAAA,MAAM,gBAAA,GAAmB,OAA6C,IAAI,CAAA;AAE1E,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAM;AAC3B,IAAA,IAAI,CAAC,UAAA,IAAc,CAAC,kBAAA,EAAmB,EAAG;AACxC,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,UAAU,CAAA;AAC9C,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,OAAO,QAAA,CAAS,YAAY,MAAM,CAAA;AAAA,MACpC;AAAA,IACF,SAAS,KAAA,EAAO;AAAA,IAEhB;AAEA,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,EAAG,CAAC,QAAA,EAAU,UAAA,EAAY,GAAG,CAAC,CAAA;AAG9B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,QAAA,YAAA,CAAa,iBAAiB,OAAO,CAAA;AAAA,MACvC;AAAA,IACF,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAGL,EAAA,MAAM,SAAA,GAAY,WAAA;AAAA,IAChB,CAAC,SAAA,KAAuB;AACtB,MAAA,IAAI,CAAC,UAAA,IAAc,CAAC,kBAAA,EAAmB,EAAG;AACxC,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,QAAA,YAAA,CAAa,iBAAiB,OAAO,CAAA;AAAA,MACvC;AAGA,MAAA,gBAAA,CAAiB,OAAA,GAAU,WAAW,MAAM;AAC1C,QAAA,IAAI;AACF,UAAA,MAAM,UAAA,GAAa,QAAA,CAAS,SAAA,CAAU,SAAS,CAAA;AAC/C,UAAA,YAAA,CAAa,OAAA,CAAQ,YAAY,UAAU,CAAA;AAAA,QAC7C,SAAS,KAAA,EAAO;AAAA,QAEhB;AAAA,MACF,GAAG,GAAG,CAAA;AAAA,IACR,CAAA;AAAA,IACA,CAAC,UAAU,UAAU;AAAA,GACvB;AAEA,EAAA,OAAO,CAAC,QAAQ,SAAS,CAAA;AAC3B;ACxEA,SAAS,MAAM,KAAA,EAA0D;AACvE,EAAA,OACE,KAAA,KAAU,IAAA,IACV,OAAO,KAAA,KAAU,QAAA,IACjB,OAAQ,KAAA,CAA0C,GAAA,KAAQ,UAAA,IAC1D,OAAQ,KAAA,CAA0C,IAAA,KAAS,UAAA;AAE/D;AAgBO,SAAS,WACd,KAAA,EACA;AACA,EAAA,MAAM;AAAA,IACJ,QAAA;AAAA,IACA,MAAA,EAAQ,WAAA;AAAA,IACR,aAAA;AAAA,IACA,cAAA;AAAA,IACA,UAAA;AAAA,IACA,SAAA;AAAA,IACA;AAAA,GACF,GAAI,KAAA;AAIJ,EAAA,MAAM,MAAA,GAAwBA,QAAQ,MAAM;AAC1C,IAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACtB,MAAA,OAAO,WAAA;AAAA,IACT;AAEA,IAAA,MAAM,GAAA,uBAAU,GAAA,EAAgC;AAChD,IAAA,MAAA,CAAO,OAAA,CAAQ,WAAW,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAC,EAAA,EAAI,KAAK,CAAA,KAAM;AAEnD,MAAA,IAAI,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,IAAY,UAAU,KAAA,EAAO;AACzD,QAAA,GAAA,CAAI,GAAA,CAAI,IAAI,KAA2B,CAAA;AAAA,MACzC,CAAA,MAAO;AACL,QAAA,GAAA,CAAI,GAAA,CAAI,IAAI,EAAE,IAAA,EAAM,OAA0B,KAAA,EAAO,IAAI,CAAA;AAAA,MAC3D;AAAA,IACF,CAAC,CAAA;AACD,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAGhB,EAAA,MAAM,QAAA,GAAWA,OAAAA,CAAQ,MAAM,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,IAAA,EAAM,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAGlE,EAAA,MAAM,CAAC,eAAA,EAAiB,kBAAkB,CAAA,GAAI,oBAAA;AAAA,IAC5C,QAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA,CAAS;AAAA;AAAA,GACX;AAGA,EAAA,MAAM,cAAA,GAAiBA,QAAiB,MAAM;AAC5C,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,OAAO,eAAA;AAAA,IACT;AACA,IAAA,IAAI,aAAA,EAAe;AACjB,MAAA,OAAO,aAAA;AAAA,IACT;AACA,IAAA,OAAO,QAAA,CAAS,cAAc,QAAQ,CAAA;AAAA,EACxC,GAAG,CAAC,eAAA,EAAiB,aAAA,EAAe,QAAA,EAAU,QAAQ,CAAC,CAAA;AAGvD,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAI,SAAkB,cAAc,CAAA;AAG1E,EAAAC,UAAU,MAAM;AACd,IAAA,gBAAA,CAAiB,cAAc,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,cAAc,CAAC,CAAA;AAGnB,EAAA,MAAM,kBAAA,GAAqBC,WAAAA;AAAA,IACzB,CAAC,SAAA,KAAuB;AACtB,MAAA,gBAAA,CAAiB,SAAS,CAAA;AAE1B,MAAA,cAAA,GAAiB,SAAS,CAAA;AAAA,IAC5B,CAAA;AAAA,IACA,CAAC,cAAc;AAAA,GACjB;AAGA,EAAA,MAAM,mBAAA,GAAsBA,WAAAA;AAAA,IAC1B,CAAC,SAAA,KAAuB;AAEtB,MAAA,IAAI,UAAA,EAAY;AACd,QAAA,kBAAA,CAAmB,SAAS,CAAA;AAAA,MAC9B;AAAA,IACF,CAAA;AAAA,IACA,CAAC,YAAY,kBAAkB;AAAA,GACjC;AAGA,EAAA,MAAM,WAAW,QAAA,CAAS,QAAA;AAG1B,EAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AACrB,IAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAsB,KAAA,EAE3B,CAAA;AAAA,EAEJ;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,MAAA,EAAQ,aAAA;AAAA,MACR,MAAA;AAAA,MACA,cAAA,EAAgB,kBAAA;AAAA,MAChB,eAAA,EAAiB,mBAAA;AAAA,MACjB,SAAA;AAAA,MACA;AAAA;AAAA,GACF;AAEJ;AChHO,SAAS,qBACd,OAAA,EACmC;AACnC,EAAA,MAAM,CAAC,gBAAA,EAAkB,wBAAwB,CAAA,GAAIC,QAAAA;AAAA,IAAiB,MACpE,QAAQ,mBAAA;AAAoB,GAC9B;AAGA,EAAAF,UAAU,MAAM;AACd,IAAA,wBAAA,CAAyB,OAAA,CAAQ,qBAAqB,CAAA;AAAA,EACxD,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,MAAM,mBAAA,GAAsBC,WAAAA;AAAA,IAC1B,CAAC,EAAA,KAAe;AACd,MAAA,OAAA,CAAQ,oBAAoB,EAAE,CAAA;AAC9B,MAAA,wBAAA,CAAyB,EAAE,CAAA;AAAA,IAC7B,CAAA;AAAA,IACA,CAAC,OAAO;AAAA,GACV;AAEA,EAAA,MAAM,KAAA,GAAQA,YAAY,MAAM;AAC9B,IAAA,OAAA,CAAQ,KAAA,EAAM;AAEd,IAAA,wBAAA,CAAyB,CAAC,SAAS,IAAI,CAAA;AAAA,EACzC,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,MAAM,gBAAA,GAAmBF,OAAAA;AAAA,IACvB,MAAM,QAAQ,mBAAA,EAAoB;AAAA,IAClC,CAAC,SAAS,gBAAgB;AAAA,GAC5B;AAEA,EAAA,MAAM,iBAAA,GAAoBE,WAAAA;AAAA,IACxB,MAAM,QAAQ,iBAAA,EAAkB;AAAA,IAChC,CAAC,OAAO;AAAA,GACV;AAEA,EAAA,MAAM,kBAAA,GAAqBA,WAAAA;AAAA,IACzB,MAAM,QAAQ,kBAAA,EAAmB;AAAA,IACjC,CAAC,OAAO;AAAA,GACV;AAEA,EAAA,OAAOF,OAAAA;AAAA,IACL,OAAO;AAAA,MACL,SAAS,OAAA,CAAQ,OAAA;AAAA,MACjB,gBAAA;AAAA,MACA,mBAAA;AAAA,MACA,gBAAA;AAAA,MACA,KAAA;AAAA,MACA,iBAAA;AAAA,MACA;AAAA,KACF,CAAA;AAAA,IACA;AAAA,MACE,OAAA;AAAA,MACA,gBAAA;AAAA,MACA,mBAAA;AAAA,MACA,gBAAA;AAAA,MACA,KAAA;AAAA,MACA,iBAAA;AAAA,MACA;AAAA;AACF,GACF;AACF;;;AC/EO,SAAS,gBAAgB,OAAA,EAG9B;AACA,EAAA,MAAM,EAAE,WAAA,EAAa,mBAAA,EAAqB,eAAA,EAAgB,GAAI,OAAA;AAG9D,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,MAAM,YAAY,mBAAA,CAAoB,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,WAAW,CAAA;AACtE,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,OAAO,EAAE,QAAA,EAAU,SAAA,EAAW,EAAA,EAAI,WAAA,EAAY;AAAA,IAChD;AAAA,EACF;AAGA,EAAA,IAAI,eAAA,EAAiB;AACnB,IAAA,OAAO,EAAE,QAAA,EAAU,eAAA,EAAiB,EAAA,EAAI,gBAAgB,EAAA,EAAG;AAAA,EAC7D;AAGA,EAAA,IAAI,mBAAA,CAAoB,SAAS,CAAA,EAAG;AAClC,IAAA,MAAM,KAAA,GAAQ,oBAAoB,CAAC,CAAA;AACnC,IAAA,OAAO,EAAE,QAAA,EAAU,KAAA,EAAO,EAAA,EAAI,MAAM,EAAA,EAAG;AAAA,EACzC;AAGA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR;AAAA,GACF;AACF;;;ACrCA,SAAS,UAAA,GAAsB;AAC7B,EAAA,OAAO,OAAO,MAAA,KAAW,WAAA,IAAe,CAAC,CAAC,MAAA,CAAO,YAAA;AACnD;AA+BO,IAAM,oBAAN,MAAyC;AAAA,EAM9C,WAAA,CACE,SACA,OAAA,EACA;AACA,IAAA,IAAA,CAAK,QAAA,GAAW,QAAQ,MAAA,GAAS,CAAA,GAAI,CAAC,GAAG,OAAO,IAAI,EAAC;AACrD,IAAA,IAAA,CAAK,OAAA,GAAU,EAAE,GAAG,OAAA,EAAQ;AAAA,EAC9B;AAAA;AAAA,EAGA,IAAY,OAAA,GAAmB;AAC7B,IAAA,OAAO,IAAA,CAAK,QAAQ,OAAA,KAAY,KAAA;AAAA,EAClC;AAAA;AAAA,EAGA,IAAI,OAAA,GAA0C;AAC5C,IAAA,OAAO,IAAA,CAAK,QAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAAA,GAA8B;AAC5B,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG;AAC9B,MAAA,OAAO,EAAA;AAAA,IACT;AACA,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,MAAA,OAAO,KAAK,WAAA,IAAe,IAAA,CAAK,QAAA,CAAS,CAAC,GAAG,EAAA,IAAM,EAAA;AAAA,IACrD;AACA,IAAA,IAAI,CAAC,YAAW,EAAG;AACjB,MAAA,OAAO,IAAA,CAAK,QAAA,CAAS,CAAC,CAAA,EAAG,EAAA,IAAM,EAAA;AAAA,IACjC;AACA,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,OAAO,YAAA,CAAa,OAAA;AAAA,QACjC,KAAK,OAAA,CAAQ;AAAA,OACf;AACA,MAAA,MAAM,QACJ,MAAA,IAAU,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAO,MAAM,IAC/C,MAAA,GACC,IAAA,CAAK,QAAA,CAAS,CAAC,GAAG,EAAA,IAAM,EAAA;AAC/B,MAAA,OAAO,KAAA;AAAA,IACT,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA,CAAK,QAAA,CAAS,CAAC,CAAA,EAAG,EAAA,IAAM,EAAA;AAAA,IACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAoB,EAAA,EAAkB;AACpC,IAAA,IAAI,CAAC,KAAK,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAO,EAAE,CAAA,EAAG;AAC7C,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,MAAA,IAAA,CAAK,WAAA,GAAc,EAAA;AACnB,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAC,YAAW,EAAG;AACnB,IAAA,IAAI;AACF,MAAA,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,IAAA,CAAK,OAAA,CAAQ,oBAAoB,EAAE,CAAA;AAAA,IACjE,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA,EAGA,iBAAA,GAAqD;AACnD,IAAA,MAAM,EAAA,GAAK,KAAK,mBAAA,EAAoB;AACpC,IAAA,OAAO,KAAK,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,mBAAA,GAA8B;AAC5B,IAAA,MAAM,EAAA,GAAK,KAAK,mBAAA,EAAoB;AACpC,IAAA,OAAO,CAAA,EAAG,IAAA,CAAK,OAAA,CAAQ,sBAAsB,IAAI,EAAE,CAAA,CAAA;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KAAA,GAAc;AACZ,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,CAAC,YAAW,EAAG;AACpC,IAAA,IAAI;AACF,MAAA,MAAA,CAAO,YAAA,CAAa,UAAA,CAAW,IAAA,CAAK,mBAAA,EAAqB,CAAA;AAAA,IAC3D,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAA,GAA8B;AAC5B,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,CAAC,UAAA,IAAc,OAAO,KAAA;AAC3C,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,KAAK,mBAAA,EAAoB;AACrC,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,GAAG,CAAA;AAC7C,MAAA,OAAO,KAAA,IAAS,IAAA,IAAQ,KAAA,CAAM,MAAA,GAAS,CAAA;AAAA,IACzC,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AACF","file":"index.mjs","sourcesContent":["/**\n * Trading page panel ID constants.\n * Used across layout strategies (split, grid) to identify trading panels.\n * Component mapping happens in the trading package.\n */\nexport const TRADING_PANEL_IDS = {\n SYMBOL_INFO_BAR: \"symbolInfoBar\",\n TRADING_VIEW: \"tradingView\",\n ORDERBOOK: \"orderbook\",\n DATA_LIST: \"dataList\",\n ORDER_ENTRY: \"orderEntry\",\n MARGIN: \"orderEntryMargin\",\n ASSETS: \"orderEntryAssets\",\n // MAIN: \"orderEntryMain\",\n MARKETS: \"markets\",\n HORIZONTAL_MARKETS: \"horizontalMarkets\",\n} as const;\n\nexport type TradingPanelId =\n (typeof TRADING_PANEL_IDS)[keyof typeof TRADING_PANEL_IDS];\n\n/**\n * Returns all trading panel IDs from layout-core.\n * Used by layout strategies (split, grid) to derive panel set; component mapping is in trading package.\n */\nexport function getTradingPanelIds(): readonly string[] {\n return Object.values(TRADING_PANEL_IDS);\n}\n","import { useMemo, useCallback, useRef, useEffect } from \"react\";\nimport type { LayoutModel, LayoutStrategy } from \"../types\";\n\n/**\n * Check if we're in a browser environment with localStorage available\n */\nfunction canUseLocalStorage(): boolean {\n try {\n return (\n typeof window !== \"undefined\" &&\n typeof window.localStorage !== \"undefined\"\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Hook for persisting layout state to localStorage\n * @param strategy - The layout strategy being used\n * @param storageKey - localStorage key (optional, if not provided returns undefined)\n * @param dep - Dependency to trigger re-read from storage when it changes\n * @returns Tuple of [currentLayout, setLayout]\n */\nexport function useLayoutPersistence<TLayout extends LayoutModel>(\n strategy: LayoutStrategy<TLayout>,\n storageKey?: string,\n dep?: unknown,\n): [TLayout | undefined, (layout: TLayout) => void] {\n // Debounce timer ref\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const layout = useMemo(() => {\n if (!storageKey || !canUseLocalStorage()) {\n return undefined;\n }\n\n try {\n const stored = localStorage.getItem(storageKey);\n if (stored) {\n return strategy.deserialize(stored);\n }\n } catch (error) {\n console.warn(`Failed to deserialize layout from ${storageKey}:`, error);\n }\n\n return undefined;\n }, [strategy, storageKey, dep]);\n\n // Cleanup debounce timer on unmount\n useEffect(() => {\n return () => {\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n };\n }, []);\n\n // Memoized setLayout with debouncing to avoid excessive writes\n const setLayout = useCallback(\n (newLayout: TLayout) => {\n if (!storageKey || !canUseLocalStorage()) {\n return;\n }\n\n // Clear existing debounce timer\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current);\n }\n\n // Debounce the write operation (100ms)\n debounceTimerRef.current = setTimeout(() => {\n try {\n const serialized = strategy.serialize(newLayout);\n localStorage.setItem(storageKey, serialized);\n } catch (error) {\n console.warn(`Failed to serialize layout to ${storageKey}:`, error);\n }\n }, 100);\n },\n [strategy, storageKey],\n );\n\n return [layout, setLayout];\n}\n","import React, { useMemo, useState, useEffect, useCallback } from \"react\";\nimport { useLayoutPersistence } from \"./hooks/useLayoutPersistence\";\nimport type {\n LayoutHostProps,\n LayoutModel,\n PanelRegistry,\n PanelRegistryEntry,\n} from \"./types\";\n\n/**\n * Check if a value is a Map instance (handles cross-realm scenarios)\n */\nfunction isMap(value: unknown): value is Map<string, PanelRegistryEntry> {\n return (\n value !== null &&\n typeof value === \"object\" &&\n typeof (value as Map<string, PanelRegistryEntry>).get === \"function\" &&\n typeof (value as Map<string, PanelRegistryEntry>).keys === \"function\"\n );\n}\n\n/**\n * LayoutHost component\n * Provides a unified interface for rendering layouts using any strategy\n *\n * @example\n * ```tsx\n * <LayoutHost\n * strategy={splitStrategy}\n * panels={panelMap}\n * storageKey=\"my_layout_state\"\n * onLayoutChange={(layout) => console.log('Layout changed', layout)}\n * />\n * ```\n */\nexport function LayoutHost<TLayout extends LayoutModel = LayoutModel>(\n props: LayoutHostProps<TLayout>,\n) {\n const {\n strategy,\n panels: panelsInput,\n initialLayout,\n onLayoutChange,\n storageKey,\n className,\n style,\n } = props;\n\n // Convert panels input to PanelRegistry (Map)\n // Uses duck-typing check for cross-realm compatibility\n const panels: PanelRegistry = useMemo(() => {\n if (isMap(panelsInput)) {\n return panelsInput;\n }\n // Convert Record to Map\n const map = new Map<string, PanelRegistryEntry>();\n Object.entries(panelsInput).forEach(([id, entry]) => {\n // Handle both old format (ReactNode) and new format (PanelRegistryEntry)\n if (entry && typeof entry === \"object\" && \"node\" in entry) {\n map.set(id, entry as PanelRegistryEntry);\n } else {\n map.set(id, { node: entry as React.ReactNode, props: {} });\n }\n });\n return map;\n }, [panelsInput]);\n\n // Get panel IDs from registry\n const panelIds = useMemo(() => Array.from(panels.keys()), [panels]);\n\n // Initialize layout state from persistence\n const [persistedLayout, setPersistedLayout] = useLayoutPersistence(\n strategy,\n storageKey,\n strategy.id, // Re-read when strategy changes\n );\n\n // Compute the resolved layout: persisted > initial > default\n const resolvedLayout = useMemo<TLayout>(() => {\n if (persistedLayout) {\n return persistedLayout;\n }\n if (initialLayout) {\n return initialLayout;\n }\n return strategy.defaultLayout(panelIds) as TLayout;\n }, [persistedLayout, initialLayout, strategy, panelIds]);\n\n // Internal layout state - initialized with resolved layout\n const [currentLayout, setCurrentLayout] = useState<TLayout>(resolvedLayout);\n\n // Sync when resolved layout changes (strategy switch, initial load, etc.)\n useEffect(() => {\n setCurrentLayout(resolvedLayout);\n }, [resolvedLayout]);\n\n // Handle layout changes - only updates internal state, no persistence\n const handleLayoutChange = useCallback(\n (newLayout: TLayout) => {\n setCurrentLayout(newLayout);\n // Notify parent\n onLayoutChange?.(newLayout);\n },\n [onLayoutChange],\n );\n\n // Handle layout persist - persists layout to storage\n const handleLayoutPersist = useCallback(\n (newLayout: TLayout) => {\n // Persist if storage key is provided\n if (storageKey) {\n setPersistedLayout(newLayout);\n }\n },\n [storageKey, setPersistedLayout],\n );\n\n // Get the Renderer component from strategy\n const Renderer = strategy.Renderer;\n\n // Handle empty panels case\n if (panels.size === 0) {\n return (\n <div className={className} style={style}>\n {/* Empty state - no panels registered */}\n </div>\n );\n }\n\n return (\n <Renderer\n layout={currentLayout}\n panels={panels}\n onLayoutChange={handleLayoutChange}\n onLayoutPersist={handleLayoutPersist}\n className={className}\n style={style}\n />\n );\n}\n","/**\n * @file useLayoutRuleManager.ts\n * @description React hook that syncs LayoutRuleManager with component state for presets, selection, and reset.\n */\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport type {\n LayoutPreset,\n LayoutRuleManager,\n} from \"../utils/LayoutRuleManager\";\n\nexport interface UseLayoutRuleManagerResult<TRule> {\n presets: readonly LayoutPreset<TRule>[];\n selectedPresetId: string;\n setSelectedPresetId: (id: string) => void;\n layoutStorageKey: string;\n reset: () => void;\n getSelectedPreset: () => LayoutPreset<TRule> | undefined;\n hasPersistedLayout: () => boolean;\n}\n\n/**\n * Binds a LayoutRuleManager to React state so selection and reset trigger re-renders.\n * setSelectedPresetId updates both the manager (localStorage) and local state.\n *\n * @param manager - LayoutRuleManager instance (e.g. from LayoutRuleManager<SplitLayoutRule>)\n * @returns Presets, selectedPresetId, setSelectedPresetId, layoutStorageKey, reset, getSelectedPreset, hasPersistedLayout\n */\nexport function useLayoutRuleManager<TRule>(\n manager: LayoutRuleManager<TRule>,\n): UseLayoutRuleManagerResult<TRule> {\n const [selectedPresetId, setSelectedPresetIdState] = useState<string>(() =>\n manager.getSelectedPresetId(),\n );\n\n /** Sync state when manager instance changes (e.g. presets changed). */\n useEffect(() => {\n setSelectedPresetIdState(manager.getSelectedPresetId());\n }, [manager]);\n\n const setSelectedPresetId = useCallback(\n (id: string) => {\n manager.setSelectedPresetId(id);\n setSelectedPresetIdState(id);\n },\n [manager],\n );\n\n const reset = useCallback(() => {\n manager.reset();\n // Optionally force re-read selected id (no change) so consumers can react to reset\n setSelectedPresetIdState((prev) => prev);\n }, [manager]);\n\n const layoutStorageKey = useMemo(\n () => manager.getLayoutStorageKey(),\n [manager, selectedPresetId],\n );\n\n const getSelectedPreset = useCallback(\n () => manager.getSelectedPreset(),\n [manager],\n );\n\n const hasPersistedLayout = useCallback(\n () => manager.hasPersistedLayout(),\n [manager],\n );\n\n return useMemo(\n () => ({\n presets: manager.presets,\n selectedPresetId,\n setSelectedPresetId,\n layoutStorageKey,\n reset,\n getSelectedPreset,\n hasPersistedLayout,\n }),\n [\n manager,\n selectedPresetId,\n setSelectedPresetId,\n layoutStorageKey,\n reset,\n getSelectedPreset,\n hasPersistedLayout,\n ],\n );\n}\n","import type { LayoutStrategy, StrategyResolverOptions } from \"../types\";\n\n/**\n * Resolve which strategy to use based on preferred ID and available strategies\n * Ensures we always return a valid strategy, falling back to default if preferred is not available\n *\n * @param options - Resolver options\n * @returns Resolved strategy and its ID\n */\nexport function resolveStrategy(options: StrategyResolverOptions): {\n strategy: LayoutStrategy;\n id: string;\n} {\n const { preferredId, availableStrategies, defaultStrategy } = options;\n\n // If preferred ID is provided, try to find it\n if (preferredId) {\n const preferred = availableStrategies.find((s) => s.id === preferredId);\n if (preferred) {\n return { strategy: preferred, id: preferredId };\n }\n }\n\n // Fall back to default strategy if provided\n if (defaultStrategy) {\n return { strategy: defaultStrategy, id: defaultStrategy.id };\n }\n\n // Fall back to first available strategy\n if (availableStrategies.length > 0) {\n const first = availableStrategies[0];\n return { strategy: first, id: first.id };\n }\n\n // This should never happen if strategies are properly set up\n throw new Error(\n \"No layout strategy available. Please ensure at least one strategy is provided.\",\n );\n}\n","/** SSR-safe check for localStorage availability. */\nfunction hasStorage(): boolean {\n return typeof window !== \"undefined\" && !!window.localStorage;\n}\n\n/**\n * Generic preset shape: id, name, and strategy-specific rule.\n */\nexport interface LayoutPreset<TRule = unknown> {\n id: string;\n name: string;\n rule: TRule;\n}\n\n/**\n * Storage key configuration per strategy (split vs grid use different keys).\n */\nexport interface LayoutRuleManagerOptions {\n /** localStorage key for persisting the selected preset id. */\n presetIdStorageKey: string;\n /** Base key for layout persistence; actual key is `${layoutStorageKeyPrefix}_${presetId}`. */\n layoutStorageKeyPrefix: string;\n /**\n * When false, no localStorage read/write; preset selection kept in memory only.\n * Default true for backward compatibility.\n */\n persist?: boolean;\n}\n\n/**\n * Manages layout presets, selected preset persistence, and reset.\n * Persistence of the user-selected rule (preset id) is done via presetIdStorageKey.\n * When persist is false, selection is kept in memory only (no localStorage).\n */\nexport class LayoutRuleManager<TRule = unknown> {\n private readonly _presets: readonly LayoutPreset<TRule>[];\n private readonly options: LayoutRuleManagerOptions;\n /** In-memory selected preset id when persist is false. */\n private _selectedId?: string;\n\n constructor(\n presets: LayoutPreset<TRule>[],\n options: LayoutRuleManagerOptions,\n ) {\n this._presets = presets.length > 0 ? [...presets] : [];\n this.options = { ...options };\n }\n\n /** Whether persistence to localStorage is enabled. */\n private get persist(): boolean {\n return this.options.persist !== false;\n }\n\n /** Read-only preset list. */\n get presets(): readonly LayoutPreset<TRule>[] {\n return this._presets;\n }\n\n /**\n * Reads selected preset id from localStorage (presetIdStorageKey).\n * When persist is false, returns in-memory selection or first preset.\n * Validates against presets; falls back to first preset id when missing or invalid.\n */\n getSelectedPresetId(): string {\n if (this._presets.length === 0) {\n return \"\";\n }\n if (!this.persist) {\n return this._selectedId ?? this._presets[0]?.id ?? \"\";\n }\n if (!hasStorage()) {\n return this._presets[0]?.id ?? \"\";\n }\n try {\n const stored = window.localStorage.getItem(\n this.options.presetIdStorageKey,\n );\n const valid =\n stored && this._presets.some((p) => p.id === stored)\n ? stored\n : (this._presets[0]?.id ?? \"\");\n return valid;\n } catch {\n return this._presets[0]?.id ?? \"\";\n }\n }\n\n /**\n * Writes selected preset id to localStorage (presetIdStorageKey).\n * When persist is false, only updates in-memory selection.\n * Validates id exists in presets before writing.\n */\n setSelectedPresetId(id: string): void {\n if (!this._presets.some((p) => p.id === id)) return;\n if (!this.persist) {\n this._selectedId = id;\n return;\n }\n if (!hasStorage()) return;\n try {\n window.localStorage.setItem(this.options.presetIdStorageKey, id);\n } catch {\n // ignore\n }\n }\n\n /** Returns the preset for the currently selected id. */\n getSelectedPreset(): LayoutPreset<TRule> | undefined {\n const id = this.getSelectedPresetId();\n return this._presets.find((p) => p.id === id);\n }\n\n /**\n * Storage key for layout persistence for the current preset.\n * Used by LayoutHost: `${layoutStorageKeyPrefix}_${selectedPresetId}`.\n */\n getLayoutStorageKey(): string {\n const id = this.getSelectedPresetId();\n return `${this.options.layoutStorageKeyPrefix}_${id}`;\n }\n\n /**\n * Clears persisted layout data for the current preset (reset to preset rule).\n * After this, next load will use getInitialLayout() from preset rule.\n * No-op when persist is false (nothing to clear).\n */\n reset(): void {\n if (!this.persist || !hasStorage()) return;\n try {\n window.localStorage.removeItem(this.getLayoutStorageKey());\n } catch {\n // ignore\n }\n }\n\n /**\n * Returns whether the current preset has any persisted layout data.\n * Useful for UI \"custom\" badge or \"Reset to default\" visibility.\n * Returns false when persist is false.\n */\n hasPersistedLayout(): boolean {\n if (!this.persist || !hasStorage()) return false;\n try {\n const key = this.getLayoutStorageKey();\n const value = window.localStorage.getItem(key);\n return value != null && value.length > 0;\n } catch {\n return false;\n }\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kodiak-finance/orderly-layout-core",
|
|
3
|
+
"version": "2.9.1",
|
|
4
|
+
"description": "Core layout strategy protocol and runtime glue for Orderly layout system",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"Orderly",
|
|
9
|
+
"Layout",
|
|
10
|
+
"Strategy"
|
|
11
|
+
],
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/react": "^18.3.2",
|
|
21
|
+
"@types/react-dom": "^18.3.0",
|
|
22
|
+
"react": "^18.2.0",
|
|
23
|
+
"react-dom": "^18.2.0",
|
|
24
|
+
"tsup": "^8.5.1",
|
|
25
|
+
"typescript": "^5.1.6",
|
|
26
|
+
"tsconfig": "0.11.34-alpha.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": ">=18",
|
|
30
|
+
"react-dom": ">=18"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"dev": "tsup --watch",
|
|
34
|
+
"build": "tsup",
|
|
35
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
36
|
+
}
|
|
37
|
+
}
|