@kodiak-finance/orderly-ui-leverage 2.8.18 → 2.8.19

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.mjs CHANGED
@@ -1,14 +1,742 @@
1
1
  import { i18n, useTranslation, Trans } from '@kodiak-finance/orderly-i18n';
2
2
  import { registerSimpleSheet, registerSimpleDialog, useScreen, TokenIcon, Text, cn, Badge, Divider, Flex, modal, inputFormatter, ReduceIcon, Input, PlusIcon, Box, Slider, Button, toast } from '@kodiak-finance/orderly-ui';
3
- import pe, { useState, useMemo, useCallback, useId } from 'react';
3
+ import React, { useState, useMemo, useCallback, useId } from 'react';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
5
  import { useSymbolLeverage, useLeverage, useSymbolsInfo, useAccountInfo, useMarkPricesStream, usePortfolio, useLocalStorage, usePositionStream } from '@kodiak-finance/orderly-hooks';
6
6
  import { positions, account } from '@kodiak-finance/orderly-perp';
7
7
  import { OrderSide } from '@kodiak-finance/orderly-types';
8
8
  import { zero } from '@kodiak-finance/orderly-utils';
9
9
 
10
- var X=e=>{let{Icon:o,onClick:t,disabled:s}=e;return jsx(o,{onClick:s?void 0:t,className:cn("oui-m-2 oui-text-white oui-transition-all",s?"oui-cursor-not-allowed oui-opacity-20":"oui-cursor-pointer oui-opacity-100")})},O=e=>{let o=pe.useMemo(()=>[inputFormatter.numberFormatter,inputFormatter.dpFormatter(0)],[]),t=useId();return jsxs("label",{htmlFor:t,className:cn("oui-w-full","oui-rounded","oui-bg-base-6","oui-flex","oui-items-center","oui-justify-between","oui-outline","oui-outline-offset-0","oui-outline-1","oui-outline-transparent","focus-within:oui-outline-primary-light","oui-input-root"),children:[jsx(X,{Icon:ReduceIcon,onClick:e.onLeverageReduce,disabled:e.isReduceDisabled}),jsxs(Flex,{itemAlign:"center",justify:"center",className:"oui-mr-4",children:[jsx(Input,{value:e.value,id:t,autoComplete:"off",classNames:{input:cn("oui-text-right oui-text-[24px]"),root:cn("oui-w-12","oui-px-0","oui-outline","oui-outline-offset-0","oui-outline-1","oui-outline-transparent","focus-within:oui-outline-primary-none")},formatters:o,onChange:e.onInputChange}),jsx("div",{className:cn("oui-ml-1 oui-mt-1 oui-select-none","oui-text-base oui-text-base-contrast-36"),children:"x"})]}),jsx(X,{Icon:PlusIcon,onClick:e.onLeverageIncrease,disabled:e.isIncreaseDisabled})]})},A=e=>{let{currentLeverage:o}=e,{t}=useTranslation();return jsxs(Flex,{itemAlign:"start",direction:"column",mb:0,children:[jsx(T,{currentLeverage:o}),jsx(O,{...e}),jsx(z,{...e}),jsx(E,{...e}),jsx(G,{...e})]})},G=e=>{let{t:o}=useTranslation();return jsxs(Flex,{direction:"row",gap:2,width:"100%",mt:0,pt:5,children:[jsx(Button,{variant:"contained",color:"gray",fullWidth:true,onClick:e.onCancel,"data-testid":"oui-testid-leverage-cancel-btn",size:e.isMobile?"md":"lg",children:o("common.cancel")}),jsx(Button,{fullWidth:true,loading:e.isLoading,onClick:e.onSave,"data-testid":"oui-testid-leverage-save-btn",disabled:e.disabled,size:e.isMobile?"md":"lg",children:o("common.save")})]})},T=e=>{let{t:o}=useTranslation(),{currentLeverage:t}=e;return jsx(Flex,{justify:"center",width:"100%",mb:2,children:jsxs(Flex,{gap:1,children:[`${o("common.current")}:`,jsx(Text.numeral,{unit:"x",size:"sm",intensity:80,dp:0,children:t??"--"})]})})},z=e=>{let{value:o,onLeverageChange:t}=e;return jsx(Flex,{itemAlign:"center",justify:"between",width:"100%",mt:4,className:"oui-text-base-contrast-80",children:e.toggles.map(s=>jsx(Flex,{itemAlign:"center",justify:"center",className:cn("oui-box-border oui-cursor-pointer oui-rounded-md oui-border oui-border-solid oui-bg-clip-padding oui-px-3 oui-py-2.5 oui-transition-all",o===s?"oui-border-primary oui-bg-base-6":"oui-border-line-12"),onClick:()=>t?.(s),children:jsxs(Flex,{itemAlign:"center",justify:"center",className:cn("oui-h-3 oui-w-9 oui-select-none"),children:[s,"x"]})},s))})},E=e=>{let{leverageLevers:o,maxLeverage:t=0,className:s,value:v,showSliderTip:n}=e;return jsxs(Box,{pt:4,width:"100%",className:s,children:[jsx(Slider,{max:t,min:1,markCount:5,value:[v],onValueChange:r=>{e.onLeverageChange(r[0]),e.setShowSliderTip(true);},color:"primary",onValueCommit:r=>{e.onValueCommit?.(r),e.setShowSliderTip(false);},showTip:n,tipFormatter:r=>`${r}x`}),jsx(Flex,{justify:"between",width:"100%",pt:3,children:o?.map((r,m)=>jsx("button",{onClick:()=>{e.onLeverageChange(r),e.onValueCommit?.([r]);},className:cn("oui-pb-3 oui-text-2xs",m===0?"oui-pr-2":m===5?"oui-pl-0":"oui-ml-2 oui-px-0",e.value>=r&&"oui-text-primary-light"),"data-testid":`oui-testid-leverage-${r}-btn`,children:`${r}x`},r))})]})};var q=e=>{let[o,t]=useState(false),{t:s}=useTranslation(),{curLeverage:v,maxLeverage:n,isLoading:r,leverageLevers:m,update:y}=useLeverage(),R=useMemo(()=>m.map(u=>({label:`${u}x`,value:u})),[m]),[i,p]=useState(v??0),h=100/((R?.length||0)-1),F=u=>{p(u);},w=()=>{p(u=>u+1);},x=()=>{p(u=>u-1);},N=useCallback(u=>{let k=Number.parseInt(u.target.value),H=Number.isNaN(k)?"":k;p(H);},[n]),g=async()=>{try{y({leverage:i}).then(()=>{e?.close?.(),toast.success(s("leverage.updated"));},u=>{toast.error(u.message);});}catch{}},d=i<=1,f=i>=n,C=!i||i<1||i>n,V=useMemo(()=>[5,10,20,50,100].filter(u=>u<=n),[n]);return {leverageLevers:m,currentLeverage:v,value:i,marks:R,onLeverageChange:F,onLeverageIncrease:w,onLeverageReduce:x,onInputChange:N,isReduceDisabled:d,isIncreaseDisabled:f,disabled:C,step:h,onCancel:e?.close,onSave:g,isLoading:r,showSliderTip:o,setShowSliderTip:t,maxLeverage:n,toggles:V}};var W=e=>{let o=q({close:e.close});return jsx(A,{...o})};var te=e=>{let{curLeverage:o=1,symbol:t,side:s}=e,[v,n]=useState(false),[r,m]=useState(o),{t:y}=useTranslation(),{isMobile:R}=useScreen(),{maxLeverage:i,update:p,isLoading:h}=useSymbolLeverage(t),{position:F,maxPositionNotional:w,maxPositionLeverage:x,overMaxPositionLeverage:N,overRequiredMargin:g}=ze({symbol:t,leverage:r,maxLeverage:i}),d=useMemo(()=>Ge(i),[i]),f=useMemo(()=>d.map(c=>({label:`${c}x`,value:c}))||[],[d]),C=useMemo(()=>100/((f?.length||0)-1),[f]),V=c=>{m(c);},u=()=>{m(c=>c+1);},k=()=>{m(c=>c-1);},H=useCallback(c=>{let $=Number.parseInt(c.target.value),de=Number.isNaN($)?"":$;m(de);},[i]),le=async()=>{try{p?.({leverage:r,symbol:t}).then(c=>{c.success?(e?.close?.(),toast.success(y("leverage.updated"))):toast.error(c.message);},c=>{toast.error(c.message);});}catch{}},ue=async()=>{modal.confirm({title:y("leverage.confirm"),content:jsx(Text,{intensity:54,children:y("leverage.confirm.content")}),onOk:le,onCancel:()=>Promise.resolve()});},ce=r<=1,me=r>=i,ge=s?s===OrderSide.BUY:F?.position_qty&&F.position_qty>0,ve=!r||r<1||r>i||g||N;return {leverageLevers:d,currentLeverage:o,value:r,marks:f,onLeverageChange:V,onLeverageIncrease:u,onLeverageReduce:k,onInputChange:H,isReduceDisabled:ce,isIncreaseDisabled:me,disabled:ve,step:C,onCancel:e?.close,onSave:ue,isLoading:h,showSliderTip:v,setShowSliderTip:n,maxLeverage:i,toggles:d,symbol:t,maxPositionNotional:w,maxPositionLeverage:x,overMaxPositionLeverage:N,overRequiredMargin:g,isBuy:ge,isMobile:R}},Ge=e=>{if(e===10)return [1,3,5,8,10];if(e===50)return [1,10,20,35,50];let o=1,t=5,s=(e-o)/(t-1),v=[];for(let n=0;n<t;n++)v.push(Math.floor(o+s*n));return v};function ze(e){let{symbol:o,leverage:t,maxLeverage:s}=e,v=useSymbolsInfo(),{data:n}=useAccountInfo(),{data:r}=useMarkPricesStream(),{totalCollateral:m}=usePortfolio(),[y,R]=useLocalStorage("unPnlPriceBasis","markPrice"),[i]=usePositionStream("all",{calcMode:y}),p=useMemo(()=>{if(o&&i?.rows?.length)return i.rows.find(g=>g.symbol===o)},[i,o]),h=useMemo(()=>{let g=n?.imr_factor?.[o],d=p?.notional;if(g&&d){let f=positions.maxPositionLeverage({IMRFactor:g,notional:d});return Math.min(f,s)}return s},[p,s,o]),F=useMemo(()=>{let g=n?.imr_factor?.[o];if(t&&g)return positions.maxPositionNotional({leverage:t,IMRFactor:g})},[t,o]),w=useMemo(()=>t>h,[t,h]),x=useMemo(()=>{if(!n||!r||!v)return zero;let g=t?i?.rows.map(C=>C.symbol===o?{...C,leverage:t}:C):i?.rows,d=account.totalInitialMarginWithQty({positions:g,markPrices:r,IMR_Factors:n.imr_factor,maxLeverage:n.max_leverage,symbolInfo:v});return account.freeCollateral({totalCollateral:m,totalInitialMarginWithOrders:d})},[i,v,n,r,m,t,o]),N=useMemo(()=>x.eq(0)||x.isNegative(),[x]);return {position:p,freeCollateral:x,maxPositionNotional:F,maxPositionLeverage:h,overMaxPositionLeverage:w,overRequiredMargin:N}}var ie=e=>{let{t:o}=useTranslation();return jsxs("div",{className:"oui-flex oui-flex-col oui-gap-3 lg:oui-gap-4",children:[jsxs("div",{className:"oui-flex oui-items-center oui-gap-2",children:[jsx(TokenIcon,{symbol:e.symbol,className:"oui-size-5"}),jsx(Text.formatted,{rule:"symbol",formatString:"base-type",size:e.isMobile?"xs":"base",weight:"semibold",intensity:98,children:e.symbol}),jsxs("div",{className:cn(["oui-ml-auto oui-flex oui-items-center oui-gap-1"]),children:[jsx(Badge,{color:e.isBuy?"success":"danger",size:"xs",children:e.isBuy?o("common.long"):o("common.short")}),jsx(Ke,{leverage:e.currentLeverage})]})]}),jsx(Divider,{}),jsxs(Flex,{itemAlign:"start",direction:"column",mb:0,children:[jsx(T,{currentLeverage:e.currentLeverage}),jsx(O,{...e}),jsx(z,{...e}),jsx(E,{...e}),jsx(Divider,{className:"oui-mb-3 oui-w-full"}),jsxs("div",{className:"oui-flex oui-flex-col oui-gap-1 oui-pb-4 oui-text-xs oui-font-normal oui-text-base-contrast-54",children:[jsx("div",{children:jsx(Trans,{i18nKey:"leverage.maxAvailableLeverage.tips",values:{leverage:e.maxPositionLeverage},components:[jsx(Text.numeral,{dp:0,suffix:"x",as:"span",className:"oui-text-base-contrast"},"0")]})}),jsx("div",{children:o("leverage.actualPositionLeverage.tips")})]}),jsxs("div",{className:cn(["-oui-mb-2",e.overRequiredMargin||e.overMaxPositionLeverage?"oui-block oui-text-xs oui-font-normal":"oui-hidden"]),children:[e.overRequiredMargin&&jsx("div",{children:jsx(Text,{color:"warning",children:o("leverage.overRequiredMargin.tips")})}),e.overMaxPositionLeverage&&jsx("div",{children:jsx(Text,{color:"warning",children:jsx(Trans,{i18nKey:"leverage.overMaxPositionLeverage.tips",values:{leverage:e.maxPositionLeverage},components:[jsx(Text.numeral,{dp:0,suffix:"X",as:"span"},"0")]})})})]}),jsx(G,{...e})]})]})},Ke=({leverage:e})=>jsxs("div",{className:cn("oui-flex oui-h-[18px] oui-items-center oui-gap-1","oui-cursor-pointer oui-rounded oui-bg-line-6 oui-px-2","oui-text-2xs oui-font-semibold oui-text-base-contrast-36"),children:[jsx(Text,{children:"Cross"}),jsx(Text.numeral,{dp:0,size:"2xs",unit:"X",children:e})]});var B=e=>{let o=te(e);return jsx(ie,{...o})};var Je="SymbolLeverageSheetId",Ze="SymbolLeverageDialogId";registerSimpleSheet(Je,B,{title:()=>i18n.t("leverage.adjustedLeverage"),classNames:{}});registerSimpleDialog(Ze,B,{title:()=>i18n.t("leverage.adjustedLeverage"),classNames:{content:"oui-w-[420px]"}});var to="LeverageWidgetWithDialog",ro="LeverageWidgetWithSheet";registerSimpleDialog(to,W,{title:()=>i18n.t("leverage.maxAccountLeverage"),size:"md"});registerSimpleSheet(ro,W,{title:()=>i18n.t("leverage.maxAccountLeverage")});
10
+ // src/index.ts
11
+ var IconButton = (props) => {
12
+ const { Icon, onClick, disabled } = props;
13
+ return /* @__PURE__ */ jsx(
14
+ Icon,
15
+ {
16
+ onClick: disabled ? void 0 : onClick,
17
+ className: cn(
18
+ "oui-m-2 oui-text-white oui-transition-all",
19
+ disabled ? "oui-cursor-not-allowed oui-opacity-20" : "oui-cursor-pointer oui-opacity-100"
20
+ )
21
+ }
22
+ );
23
+ };
24
+ var LeverageInput = (props) => {
25
+ const formatters = React.useMemo(
26
+ () => [inputFormatter.numberFormatter, inputFormatter.dpFormatter(0)],
27
+ []
28
+ );
29
+ const id = useId();
30
+ return /* @__PURE__ */ jsxs(
31
+ "label",
32
+ {
33
+ htmlFor: id,
34
+ className: cn(
35
+ "oui-w-full",
36
+ "oui-rounded",
37
+ "oui-bg-base-6",
38
+ "oui-flex",
39
+ "oui-items-center",
40
+ "oui-justify-between",
41
+ "oui-outline",
42
+ "oui-outline-offset-0",
43
+ "oui-outline-1",
44
+ "oui-outline-transparent",
45
+ "focus-within:oui-outline-primary-light",
46
+ "oui-input-root"
47
+ ),
48
+ children: [
49
+ /* @__PURE__ */ jsx(
50
+ IconButton,
51
+ {
52
+ Icon: ReduceIcon,
53
+ onClick: props.onLeverageReduce,
54
+ disabled: props.isReduceDisabled
55
+ }
56
+ ),
57
+ /* @__PURE__ */ jsxs(Flex, { itemAlign: "center", justify: "center", className: "oui-mr-4", children: [
58
+ /* @__PURE__ */ jsx(
59
+ Input,
60
+ {
61
+ value: props.value,
62
+ id,
63
+ autoComplete: "off",
64
+ classNames: {
65
+ input: cn("oui-text-right oui-text-[24px]"),
66
+ root: cn(
67
+ "oui-w-12",
68
+ "oui-px-0",
69
+ "oui-outline",
70
+ "oui-outline-offset-0",
71
+ "oui-outline-1",
72
+ "oui-outline-transparent",
73
+ "focus-within:oui-outline-primary-none"
74
+ )
75
+ },
76
+ formatters,
77
+ onChange: props.onInputChange
78
+ }
79
+ ),
80
+ /* @__PURE__ */ jsx(
81
+ "div",
82
+ {
83
+ className: cn(
84
+ "oui-ml-1 oui-mt-1 oui-select-none",
85
+ "oui-text-base oui-text-base-contrast-36"
86
+ ),
87
+ children: "x"
88
+ }
89
+ )
90
+ ] }),
91
+ /* @__PURE__ */ jsx(
92
+ IconButton,
93
+ {
94
+ Icon: PlusIcon,
95
+ onClick: props.onLeverageIncrease,
96
+ disabled: props.isIncreaseDisabled
97
+ }
98
+ )
99
+ ]
100
+ }
101
+ );
102
+ };
103
+ var Leverage = (props) => {
104
+ const { currentLeverage } = props;
105
+ const { t } = useTranslation();
106
+ return /* @__PURE__ */ jsxs(Flex, { itemAlign: "start", direction: "column", mb: 0, children: [
107
+ /* @__PURE__ */ jsx(LeverageHeader, { currentLeverage }),
108
+ /* @__PURE__ */ jsx(LeverageInput, { ...props }),
109
+ /* @__PURE__ */ jsx(LeverageSelector, { ...props }),
110
+ /* @__PURE__ */ jsx(LeverageSlider, { ...props }),
111
+ /* @__PURE__ */ jsx(LeverageFooter, { ...props })
112
+ ] });
113
+ };
114
+ var LeverageFooter = (props) => {
115
+ const { t } = useTranslation();
116
+ return /* @__PURE__ */ jsxs(Flex, { direction: "row", gap: 2, width: "100%", mt: 0, pt: 5, children: [
117
+ /* @__PURE__ */ jsx(
118
+ Button,
119
+ {
120
+ variant: "contained",
121
+ color: "gray",
122
+ fullWidth: true,
123
+ onClick: props.onCancel,
124
+ "data-testid": "oui-testid-leverage-cancel-btn",
125
+ size: props.isMobile ? "md" : "lg",
126
+ children: t("common.cancel")
127
+ }
128
+ ),
129
+ /* @__PURE__ */ jsx(
130
+ Button,
131
+ {
132
+ fullWidth: true,
133
+ loading: props.isLoading,
134
+ onClick: props.onSave,
135
+ "data-testid": "oui-testid-leverage-save-btn",
136
+ disabled: props.disabled,
137
+ size: props.isMobile ? "md" : "lg",
138
+ children: t("common.save")
139
+ }
140
+ )
141
+ ] });
142
+ };
143
+ var LeverageHeader = (props) => {
144
+ const { t } = useTranslation();
145
+ const { currentLeverage } = props;
146
+ return /* @__PURE__ */ jsx(Flex, { justify: "center", width: "100%", mb: 2, children: /* @__PURE__ */ jsxs(Flex, { gap: 1, children: [
147
+ `${t("common.current")}:`,
148
+ /* @__PURE__ */ jsx(Text.numeral, { unit: "x", size: "sm", intensity: 80, dp: 0, children: currentLeverage ?? "--" })
149
+ ] }) });
150
+ };
151
+ var LeverageSelector = (props) => {
152
+ const { value, onLeverageChange } = props;
153
+ return /* @__PURE__ */ jsx(
154
+ Flex,
155
+ {
156
+ itemAlign: "center",
157
+ justify: "between",
158
+ width: "100%",
159
+ mt: 4,
160
+ className: "oui-text-base-contrast-80",
161
+ children: props.toggles.map((option) => /* @__PURE__ */ jsx(
162
+ Flex,
163
+ {
164
+ itemAlign: "center",
165
+ justify: "center",
166
+ className: cn(
167
+ `oui-box-border oui-cursor-pointer oui-rounded-md oui-border oui-border-solid oui-bg-clip-padding oui-px-3 oui-py-2.5 oui-transition-all`,
168
+ value === option ? "oui-border-primary oui-bg-base-6" : "oui-border-line-12"
169
+ ),
170
+ onClick: () => onLeverageChange?.(option),
171
+ children: /* @__PURE__ */ jsxs(
172
+ Flex,
173
+ {
174
+ itemAlign: "center",
175
+ justify: "center",
176
+ className: cn(`oui-h-3 oui-w-9 oui-select-none`),
177
+ children: [
178
+ option,
179
+ "x"
180
+ ]
181
+ }
182
+ )
183
+ },
184
+ option
185
+ ))
186
+ }
187
+ );
188
+ };
189
+ var getMarkPosition = (item, index, max, total) => {
190
+ const min = 1;
191
+ const maxSteps = max - min;
192
+ const percentPerStep = 100 / maxSteps;
193
+ const position = percentPerStep * (item - min);
194
+ if (index === 0)
195
+ return Math.min(position + 2, 100);
196
+ if (index === total - 1)
197
+ return Math.max(position - 3, 0);
198
+ return position;
199
+ };
200
+ var LeverageSlider = (props) => {
201
+ const {
202
+ leverageLevers,
203
+ maxLeverage = 0,
204
+ className,
205
+ value,
206
+ showSliderTip,
207
+ marks
208
+ } = props;
209
+ const sliderMax = leverageLevers.length > 0 ? Math.max(...leverageLevers) : maxLeverage;
210
+ return /* @__PURE__ */ jsxs(Box, { pt: 4, width: "100%", className, children: [
211
+ /* @__PURE__ */ jsx(
212
+ Slider,
213
+ {
214
+ max: maxLeverage,
215
+ min: 1,
216
+ marks,
217
+ value: [value],
218
+ onValueChange: (e) => {
219
+ props.onLeverageChange(e[0]);
220
+ props.setShowSliderTip(true);
221
+ },
222
+ color: "primary",
223
+ onValueCommit: (e) => {
224
+ props.onValueCommit?.(e);
225
+ props.setShowSliderTip(false);
226
+ },
227
+ showTip: showSliderTip,
228
+ tipFormatter: (value2) => {
229
+ return `${value2}x`;
230
+ }
231
+ }
232
+ ),
233
+ /* @__PURE__ */ jsx(Flex, { justify: "between", width: "100%", pt: 3, children: leverageLevers?.map((item, index) => {
234
+ const position = getMarkPosition(
235
+ item,
236
+ index,
237
+ sliderMax,
238
+ leverageLevers.length
239
+ );
240
+ return /* @__PURE__ */ jsx(
241
+ "button",
242
+ {
243
+ onClick: () => {
244
+ props.onLeverageChange(item);
245
+ props.onValueCommit?.([item]);
246
+ },
247
+ className: cn(
248
+ "oui-absolute oui-pb-3 oui-text-2xs oui-transform oui--translate-x-1/2",
249
+ Number(props.value) >= Number(item) ? "oui-text-primary-light" : "oui-text-base-contrast-54"
250
+ ),
251
+ style: {
252
+ left: `${position}%`
253
+ },
254
+ "data-testid": `oui-testid-leverage-${item}-btn`,
255
+ children: `${item}x`
256
+ },
257
+ item
258
+ );
259
+ }) })
260
+ ] });
261
+ };
262
+ var useLeverageScript = (options) => {
263
+ const [showSliderTip, setShowSliderTip] = useState(false);
264
+ const { t } = useTranslation();
265
+ const { curLeverage, maxLeverage, isLoading, leverageLevers, update } = useLeverage();
266
+ const marks = useMemo(() => {
267
+ return leverageLevers.map((e) => ({
268
+ label: `${e}x`,
269
+ value: e
270
+ }));
271
+ }, [leverageLevers]);
272
+ const [leverage, setLeverage] = useState(curLeverage ?? 0);
273
+ const step = 100 / ((marks?.length || 0) - 1);
274
+ const onLeverageChange = (leverage2) => {
275
+ setLeverage(leverage2);
276
+ };
277
+ const onLeverageIncrease = () => {
278
+ setLeverage((prev) => prev + 1);
279
+ };
280
+ const onLeverageReduce = () => {
281
+ setLeverage((prev) => prev - 1);
282
+ };
283
+ const onInputChange = useCallback(
284
+ (e) => {
285
+ const parsed = Number.parseInt(e.target.value);
286
+ const value = Number.isNaN(parsed) ? "" : parsed;
287
+ setLeverage(value);
288
+ },
289
+ [maxLeverage]
290
+ );
291
+ const onSave = async () => {
292
+ try {
293
+ update({ leverage }).then(
294
+ () => {
295
+ options?.close?.();
296
+ toast.success(t("leverage.updated"));
297
+ },
298
+ (err) => {
299
+ toast.error(err.message);
300
+ }
301
+ );
302
+ } catch (err) {
303
+ }
304
+ };
305
+ const isReduceDisabled = leverage <= 1;
306
+ const isIncreaseDisabled = leverage >= maxLeverage;
307
+ const disabled = !leverage || leverage < 1 || leverage > maxLeverage;
308
+ const toggles = useMemo(() => {
309
+ return [5, 10, 20, 50, 100].filter((e) => e <= maxLeverage);
310
+ }, [maxLeverage]);
311
+ return {
312
+ leverageLevers,
313
+ currentLeverage: curLeverage,
314
+ value: leverage,
315
+ marks,
316
+ onLeverageChange,
317
+ onLeverageIncrease,
318
+ onLeverageReduce,
319
+ onInputChange,
320
+ isReduceDisabled,
321
+ isIncreaseDisabled,
322
+ disabled,
323
+ step,
324
+ onCancel: options?.close,
325
+ onSave,
326
+ isLoading,
327
+ showSliderTip,
328
+ setShowSliderTip,
329
+ maxLeverage,
330
+ toggles
331
+ };
332
+ };
333
+ var LeverageEditor = (props) => {
334
+ const state = useLeverageScript({ close: props.close });
335
+ return /* @__PURE__ */ jsx(Leverage, { ...state });
336
+ };
337
+ var useSymbolLeverageScript = (options) => {
338
+ const { curLeverage = 1, symbol, side } = options;
339
+ const [showSliderTip, setShowSliderTip] = useState(false);
340
+ const [leverage, setLeverage] = useState(curLeverage);
341
+ const { t } = useTranslation();
342
+ const { isMobile } = useScreen();
343
+ const {
344
+ maxLeverage: originalMaxLeverage,
345
+ update,
346
+ isLoading
347
+ } = useSymbolLeverage(symbol);
348
+ const maxLeverage = originalMaxLeverage;
349
+ const {
350
+ position,
351
+ maxPositionNotional,
352
+ maxPositionLeverage,
353
+ overMaxPositionLeverage,
354
+ overRequiredMargin
355
+ } = useCalc({ symbol, leverage, maxLeverage });
356
+ const formattedLeverageLevers = useMemo(() => {
357
+ return generateLeverageLeversForSelector(maxLeverage);
358
+ }, [maxLeverage]);
359
+ const leverageLevers = useMemo(() => {
360
+ return generateLeverageLevers(maxLeverage);
361
+ }, [maxLeverage]);
362
+ const marks = useMemo(() => {
363
+ return leverageLevers.map((e) => ({
364
+ label: `${e}x`,
365
+ value: e
366
+ })) || [];
367
+ }, [leverageLevers]);
368
+ const step = useMemo(() => {
369
+ return 100 / ((marks?.length || 0) - 1);
370
+ }, [marks]);
371
+ const onLeverageChange = (leverage2) => {
372
+ setLeverage(leverage2);
373
+ };
374
+ const onLeverageIncrease = () => {
375
+ setLeverage((prev) => prev + 1);
376
+ };
377
+ const onLeverageReduce = () => {
378
+ setLeverage((prev) => prev - 1);
379
+ };
380
+ const onInputChange = useCallback(
381
+ (e) => {
382
+ const parsed = Number.parseInt(e.target.value);
383
+ if (!Number.isNaN(parsed)) {
384
+ setLeverage(parsed);
385
+ }
386
+ },
387
+ []
388
+ );
389
+ const onConfirmSave = async () => {
390
+ try {
391
+ update?.({ leverage, symbol }).then(
392
+ (res) => {
393
+ if (res.success) {
394
+ options?.close?.();
395
+ toast.success(t("leverage.updated"));
396
+ } else {
397
+ toast.error(res.message);
398
+ }
399
+ },
400
+ (err) => {
401
+ toast.error(err.message);
402
+ }
403
+ );
404
+ } catch (err) {
405
+ }
406
+ };
407
+ const onSave = async () => {
408
+ modal.confirm({
409
+ title: t("leverage.confirm"),
410
+ content: /* @__PURE__ */ jsx(Text, { intensity: 54, children: t("leverage.confirm.content") }),
411
+ onOk: onConfirmSave,
412
+ onCancel: () => {
413
+ return Promise.resolve();
414
+ }
415
+ });
416
+ };
417
+ const isReduceDisabled = leverage <= 1;
418
+ const isIncreaseDisabled = leverage >= maxLeverage;
419
+ const isBuy = side ? side === OrderSide.BUY : position?.position_qty && position.position_qty > 0;
420
+ const disabled = !leverage || leverage < 1 || leverage > maxLeverage || overRequiredMargin || overMaxPositionLeverage;
421
+ return {
422
+ leverageLevers,
423
+ currentLeverage: curLeverage,
424
+ // Keep the displayed leverage fixed until the user confirms the change.
425
+ value: leverage,
426
+ // Input and slider reflect the temporary value being edited.
427
+ marks,
428
+ onLeverageChange,
429
+ onLeverageIncrease,
430
+ onLeverageReduce,
431
+ onInputChange,
432
+ isReduceDisabled,
433
+ isIncreaseDisabled,
434
+ disabled,
435
+ step,
436
+ onCancel: options?.close,
437
+ onSave,
438
+ isLoading,
439
+ showSliderTip,
440
+ setShowSliderTip,
441
+ maxLeverage,
442
+ toggles: formattedLeverageLevers,
443
+ symbol,
444
+ maxPositionNotional,
445
+ maxPositionLeverage,
446
+ overMaxPositionLeverage,
447
+ overRequiredMargin,
448
+ isBuy,
449
+ isMobile
450
+ };
451
+ };
452
+ var generateLeverageLeversForSelector = (max) => {
453
+ if (max === 10) {
454
+ return [1, 3, 5, 8, 10];
455
+ } else if (max === 50) {
456
+ return [1, 10, 20, 35, 50];
457
+ }
458
+ const min = 1;
459
+ const parts = 5;
460
+ const step = (max - min) / (parts - 1);
461
+ const result = [];
462
+ for (let i = 0; i < parts; i++) {
463
+ result.push(Math.floor(min + step * i));
464
+ }
465
+ return result;
466
+ };
467
+ var generateEvenlyDistributedMarks = (max) => {
468
+ const result = [];
469
+ if (max % 5 === 0) {
470
+ const step = max / 5;
471
+ for (let i = 0; i < 6; i++) {
472
+ const value = step * i;
473
+ result.push(value === 0 ? 1 : value);
474
+ }
475
+ } else {
476
+ result.push(1);
477
+ const quarter = max * 0.25;
478
+ const half = max * 0.5;
479
+ const threeQuarter = max * 0.75;
480
+ const quarterRounded = Math.round(quarter);
481
+ const halfRounded = Math.round(half);
482
+ const threeQuarterRounded = Math.round(threeQuarter);
483
+ if (quarterRounded > 1 && quarterRounded !== halfRounded) {
484
+ result.push(quarterRounded);
485
+ }
486
+ if (halfRounded > 1) {
487
+ result.push(halfRounded);
488
+ }
489
+ if (threeQuarterRounded > halfRounded && threeQuarterRounded < max) {
490
+ result.push(threeQuarterRounded);
491
+ }
492
+ if (max > 1) {
493
+ result.push(max);
494
+ }
495
+ }
496
+ return result;
497
+ };
498
+ var generateLeverageLevers = (max) => {
499
+ switch (max) {
500
+ case 10:
501
+ return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
502
+ case 20:
503
+ return [1, 5, 10, 15, 20];
504
+ case 50:
505
+ return [1, 10, 20, 30, 40, 50];
506
+ case 100:
507
+ return [1, 20, 40, 60, 80, 100];
508
+ }
509
+ const result = [];
510
+ if (max < 10) {
511
+ for (let i = 1; i <= max; i++) {
512
+ result.push(i);
513
+ }
514
+ } else {
515
+ result.push(...generateEvenlyDistributedMarks(max));
516
+ }
517
+ return result;
518
+ };
519
+ function useCalc(inputs) {
520
+ const { symbol, leverage, maxLeverage } = inputs;
521
+ const symbolsInfo = useSymbolsInfo();
522
+ const { data: accountInfo } = useAccountInfo();
523
+ const { data: markPrices } = useMarkPricesStream();
524
+ const { totalCollateral } = usePortfolio();
525
+ const [unPnlPriceBasis, setUnPnlPriceBasic] = useLocalStorage(
526
+ "unPnlPriceBasis",
527
+ "markPrice"
528
+ );
529
+ const [positions$1] = usePositionStream("all", {
530
+ calcMode: unPnlPriceBasis
531
+ });
532
+ const position = useMemo(() => {
533
+ if (symbol && positions$1?.rows?.length) {
534
+ return positions$1.rows.find((item) => item.symbol === symbol);
535
+ }
536
+ }, [positions$1, symbol]);
537
+ const maxPositionLeverage = useMemo(() => {
538
+ const IMRFactor = accountInfo?.imr_factor?.[symbol];
539
+ const notional = position?.notional;
540
+ if (IMRFactor && notional) {
541
+ const maxPositionLeverage2 = positions.maxPositionLeverage({
542
+ IMRFactor,
543
+ notional
544
+ });
545
+ return Math.min(maxPositionLeverage2, maxLeverage);
546
+ }
547
+ return maxLeverage;
548
+ }, [position, maxLeverage, symbol]);
549
+ const maxPositionNotional = useMemo(() => {
550
+ const IMRFactor = accountInfo?.imr_factor?.[symbol];
551
+ if (leverage && IMRFactor) {
552
+ return positions.maxPositionNotional({
553
+ leverage,
554
+ IMRFactor
555
+ });
556
+ }
557
+ }, [leverage, symbol]);
558
+ const overMaxPositionLeverage = useMemo(() => {
559
+ return leverage > maxPositionLeverage;
560
+ }, [leverage, maxPositionLeverage]);
561
+ const freeCollateral = useMemo(() => {
562
+ if (!accountInfo || !markPrices || !symbolsInfo) {
563
+ return zero;
564
+ }
565
+ const positionList = leverage ? positions$1?.rows.map((item) => {
566
+ if (item.symbol === symbol) {
567
+ return {
568
+ ...item,
569
+ leverage
570
+ };
571
+ }
572
+ return item;
573
+ }) : positions$1?.rows;
574
+ const totalInitialMarginWithOrders = account.totalInitialMarginWithQty({
575
+ positions: positionList,
576
+ markPrices,
577
+ IMR_Factors: accountInfo.imr_factor,
578
+ // not used
579
+ maxLeverage: accountInfo.max_leverage,
580
+ symbolInfo: symbolsInfo
581
+ });
582
+ const freeCollateral2 = account.freeCollateral({
583
+ totalCollateral,
584
+ totalInitialMarginWithOrders
585
+ });
586
+ return freeCollateral2;
587
+ }, [
588
+ positions$1,
589
+ symbolsInfo,
590
+ accountInfo,
591
+ markPrices,
592
+ totalCollateral,
593
+ leverage,
594
+ symbol
595
+ ]);
596
+ const overRequiredMargin = useMemo(() => {
597
+ return freeCollateral.eq(0) || freeCollateral.isNegative();
598
+ }, [freeCollateral]);
599
+ return {
600
+ position,
601
+ freeCollateral,
602
+ maxPositionNotional,
603
+ maxPositionLeverage,
604
+ overMaxPositionLeverage,
605
+ overRequiredMargin
606
+ };
607
+ }
608
+ var SymbolLeverage = (props) => {
609
+ const { t } = useTranslation();
610
+ return /* @__PURE__ */ jsxs("div", { className: "oui-flex oui-flex-col oui-gap-3 lg:oui-gap-4", children: [
611
+ /* @__PURE__ */ jsxs("div", { className: "oui-flex oui-items-center oui-gap-2", children: [
612
+ /* @__PURE__ */ jsx(TokenIcon, { symbol: props.symbol, className: "oui-size-5" }),
613
+ /* @__PURE__ */ jsx(
614
+ Text.formatted,
615
+ {
616
+ rule: "symbol",
617
+ formatString: "base-type",
618
+ size: props.isMobile ? "xs" : "base",
619
+ weight: "semibold",
620
+ intensity: 98,
621
+ children: props.symbol
622
+ }
623
+ ),
624
+ /* @__PURE__ */ jsxs(
625
+ "div",
626
+ {
627
+ className: cn(["oui-ml-auto oui-flex oui-items-center oui-gap-1"]),
628
+ children: [
629
+ /* @__PURE__ */ jsx(Badge, { color: props.isBuy ? "success" : "danger", size: "xs", children: props.isBuy ? t("common.long") : t("common.short") }),
630
+ /* @__PURE__ */ jsx(LeverageBadge, { leverage: props.currentLeverage })
631
+ ]
632
+ }
633
+ )
634
+ ] }),
635
+ /* @__PURE__ */ jsx(Divider, {}),
636
+ /* @__PURE__ */ jsxs(Flex, { itemAlign: "start", direction: "column", mb: 0, children: [
637
+ /* @__PURE__ */ jsx(LeverageHeader, { currentLeverage: props.currentLeverage }),
638
+ /* @__PURE__ */ jsx(LeverageInput, { ...props }),
639
+ /* @__PURE__ */ jsx(LeverageSelector, { ...props }),
640
+ /* @__PURE__ */ jsx(LeverageSlider, { ...props }),
641
+ /* @__PURE__ */ jsx(Divider, { className: "oui-mb-3 oui-w-full" }),
642
+ /* @__PURE__ */ jsxs("div", { className: "oui-flex oui-flex-col oui-gap-1 oui-pb-4 oui-text-xs oui-font-normal oui-text-base-contrast-54", children: [
643
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
644
+ Trans,
645
+ {
646
+ i18nKey: "leverage.maxAvailableLeverage.tips",
647
+ values: { leverage: props.maxPositionLeverage },
648
+ components: [
649
+ // @ts-ignore
650
+ /* @__PURE__ */ jsx(
651
+ Text.numeral,
652
+ {
653
+ dp: 0,
654
+ suffix: "x",
655
+ as: "span",
656
+ className: "oui-text-base-contrast"
657
+ },
658
+ "0"
659
+ )
660
+ ]
661
+ }
662
+ ) }),
663
+ /* @__PURE__ */ jsx("div", { children: t("leverage.actualPositionLeverage.tips") })
664
+ ] }),
665
+ /* @__PURE__ */ jsxs(
666
+ "div",
667
+ {
668
+ className: cn([
669
+ "-oui-mb-2",
670
+ props.overRequiredMargin || props.overMaxPositionLeverage ? "oui-block oui-text-xs oui-font-normal" : "oui-hidden"
671
+ ]),
672
+ children: [
673
+ props.overRequiredMargin && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Text, { color: "warning", children: t("leverage.overRequiredMargin.tips") }) }),
674
+ props.overMaxPositionLeverage && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Text, { color: "warning", children: /* @__PURE__ */ jsx(
675
+ Trans,
676
+ {
677
+ i18nKey: "leverage.overMaxPositionLeverage.tips",
678
+ values: { leverage: props.maxPositionLeverage },
679
+ components: [
680
+ // @ts-ignore
681
+ /* @__PURE__ */ jsx(Text.numeral, { dp: 0, suffix: "X", as: "span" }, "0")
682
+ ]
683
+ }
684
+ ) }) })
685
+ ]
686
+ }
687
+ ),
688
+ /* @__PURE__ */ jsx(LeverageFooter, { ...props })
689
+ ] })
690
+ ] });
691
+ };
692
+ var LeverageBadge = ({ leverage }) => {
693
+ return /* @__PURE__ */ jsxs(
694
+ "div",
695
+ {
696
+ className: cn(
697
+ "oui-flex oui-h-[18px] oui-items-center oui-gap-1",
698
+ "oui-cursor-pointer oui-rounded oui-bg-line-6 oui-px-2",
699
+ "oui-text-2xs oui-font-semibold oui-text-base-contrast-36"
700
+ ),
701
+ children: [
702
+ /* @__PURE__ */ jsx(Text, { children: "Cross" }),
703
+ /* @__PURE__ */ jsx(Text.numeral, { dp: 0, size: "2xs", unit: "X", children: leverage })
704
+ ]
705
+ }
706
+ );
707
+ };
708
+ var SymbolLeverageWidget = (props) => {
709
+ const state = useSymbolLeverageScript(props);
710
+ return /* @__PURE__ */ jsx(SymbolLeverage, { ...state });
711
+ };
11
712
 
12
- export { A as Leverage, W as LeverageEditor, T as LeverageHeader, E as LeverageSlider, to as LeverageWidgetWithDialogId, ro as LeverageWidgetWithSheetId, Ze as SymbolLeverageDialogId, Je as SymbolLeverageSheetId, B as SymbolLeverageWidget, q as useLeverageScript };
713
+ // src/symbolLeverage/index.ts
714
+ var SymbolLeverageSheetId = "SymbolLeverageSheetId";
715
+ var SymbolLeverageDialogId = "SymbolLeverageDialogId";
716
+ registerSimpleSheet(SymbolLeverageSheetId, SymbolLeverageWidget, {
717
+ title: () => i18n.t("leverage.adjustedLeverage"),
718
+ classNames: {
719
+ // content: "oui-p-5",
720
+ }
721
+ });
722
+ registerSimpleDialog(SymbolLeverageDialogId, SymbolLeverageWidget, {
723
+ title: () => i18n.t("leverage.adjustedLeverage"),
724
+ classNames: {
725
+ content: "oui-w-[420px]"
726
+ }
727
+ });
728
+
729
+ // src/index.ts
730
+ var LeverageWidgetWithDialogId = "LeverageWidgetWithDialog";
731
+ var LeverageWidgetWithSheetId = "LeverageWidgetWithSheet";
732
+ registerSimpleDialog(LeverageWidgetWithDialogId, LeverageEditor, {
733
+ title: () => i18n.t("leverage.maxAccountLeverage"),
734
+ size: "md"
735
+ });
736
+ registerSimpleSheet(LeverageWidgetWithSheetId, LeverageEditor, {
737
+ title: () => i18n.t("leverage.maxAccountLeverage")
738
+ });
739
+
740
+ export { Leverage, LeverageEditor, LeverageHeader, LeverageSlider, LeverageWidgetWithDialogId, LeverageWidgetWithSheetId, SymbolLeverageDialogId, SymbolLeverageSheetId, SymbolLeverageWidget, useLeverageScript };
13
741
  //# sourceMappingURL=out.js.map
14
742
  //# sourceMappingURL=index.mjs.map