From abd13d21ccbd90e376d9878b031f3b910f468f12 Mon Sep 17 00:00:00 2001 From: LiZhenhai-MBP14 <5935568+jackhai9@users.noreply.github.com> Date: Thu, 27 Aug 2026 12:58:08 +0800 Subject: [PATCH] fix: gray out inactive ladder actions --- .../specs/control-flows.pw.js | 49 +++++-------------- scripts/binance-orderbook-trade.user.js | 28 ++++------- src/binance-orderbook-trade/index.user.js | 26 +++------- .../ladder-options.test.js | 4 +- .../source-regressions.test.js | 9 ++-- 5 files changed, 36 insertions(+), 80 deletions(-) diff --git a/e2e/binance-orderbook/specs/control-flows.pw.js b/e2e/binance-orderbook/specs/control-flows.pw.js index 6a6a66f..8a4e353 100644 --- a/e2e/binance-orderbook/specs/control-flows.pw.js +++ b/e2e/binance-orderbook/specs/control-flows.pw.js @@ -23,6 +23,14 @@ async function readRect(locator) { return rect; } +async function expectStandardDisabledStyle(locator) { + await expect(locator).toHaveCSS('background-color', 'rgb(245, 245, 245)'); + await expect(locator).toHaveCSS('border-color', 'rgb(213, 217, 226)'); + await expect(locator).toHaveCSS('color', 'rgb(183, 189, 198)'); + await expect(locator).toHaveCSS('opacity', '0.65'); + await expect(locator).toHaveCSS('cursor', 'not-allowed'); +} + test('native open and close tabs drive one stable panel direction selector', async ({ page }) => { const scenario = createCancelScenario({ positions: POSITION_SETS.current, @@ -156,31 +164,16 @@ test('starting a ladder preserves action slots and turns only the active action await expect(stop).toHaveCount(0); const startLongRect = await readRect(startLong); const startShortRect = await readRect(startShort); - const startShortTone = await startShort.evaluate((button) => { - const style = getComputedStyle(button); - return { - backgroundColor: style.backgroundColor, - borderColor: style.borderColor, - color: style.color, - }; - }); const cancelRect = await readRect(cancel); await installInteractionProbe(page, '[data-ladder-action="OPEN_LONG"]'); await startLong.click(); await expect(startLong).toHaveCount(0); await expect(startShort).toBeDisabled(); await expect(stop).toBeEnabled(); + await expectStandardDisabledStyle(startShort); const stopRect = await readRect(stop); expect(stopRect).toEqual(startLongRect); expect(await readRect(startShort)).toEqual(startShortRect); - expect(await startShort.evaluate((button) => { - const style = getComputedStyle(button); - return { - backgroundColor: style.backgroundColor, - borderColor: style.borderColor, - color: style.color, - }; - })).toEqual(startShortTone); expect(await readRect(cancel)).toEqual(cancelRect); const submissionsBeforeStop = (await readFixtureState(page)).events .filter((event) => event.type === 'order-submitted').length; @@ -213,16 +206,16 @@ test('starting close short keeps unavailable close long in the standard disabled const closeShort = panel.getByRole('button', { name: '阶梯平空' }); await expect(closeLong).toBeDisabled(); - await expect(closeLong).not.toHaveAttribute('data-ladder-preserve-tone', 'true'); + await expectStandardDisabledStyle(closeLong); await expect(closeShort).toBeEnabled(); await closeShort.click(); await expect(panel.getByRole('button', { name: '停止平空' })).toBeEnabled(); await expect(closeLong).toBeDisabled(); - await expect(closeLong).not.toHaveAttribute('data-ladder-preserve-tone', 'true'); + await expectStandardDisabledStyle(closeLong); expect(errors).toEqual([]); }); -test('starting close short preserves close long tone only when a long position exists', async ({ page }) => { +test('starting close short disables close long with the standard disabled style', async ({ page }) => { const scenario = createCancelScenario({ positions: [ { symbol: CURRENT_SYMBOL, side: 'LONG', quantity: '0.1' }, @@ -237,26 +230,10 @@ test('starting close short preserves close long tone only when a long position e await expect(closeLong).toBeEnabled(); await expect(closeShort).toBeEnabled(); - const closeLongTone = await closeLong.evaluate((button) => { - const style = getComputedStyle(button); - return { - backgroundColor: style.backgroundColor, - borderColor: style.borderColor, - color: style.color, - }; - }); await closeShort.click(); await expect(panel.getByRole('button', { name: '停止平空' })).toBeEnabled(); await expect(closeLong).toBeDisabled(); - await expect(closeLong).toHaveAttribute('data-ladder-preserve-tone', 'true'); - expect(await closeLong.evaluate((button) => { - const style = getComputedStyle(button); - return { - backgroundColor: style.backgroundColor, - borderColor: style.borderColor, - color: style.color, - }; - })).toEqual(closeLongTone); + await expectStandardDisabledStyle(closeLong); expect(errors).toEqual([]); }); diff --git a/scripts/binance-orderbook-trade.user.js b/scripts/binance-orderbook-trade.user.js index 6177104..eec7791 100644 --- a/scripts/binance-orderbook-trade.user.js +++ b/scripts/binance-orderbook-trade.user.js @@ -3,7 +3,7 @@ // @namespace binance.orderbook.trade // @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E // @icon64 data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E -// @version 2.7.131 +// @version 2.7.132 // @author jackhai9 // @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板 // @match https://www.binance.com/*/futures/* @@ -1973,7 +1973,7 @@ style.id = styleId; style.textContent = ` button[${NATIVE_ACTION_DISABLED_ATTR}="true"], - #${PANEL_ID} button:disabled:not([data-ladder-preserve-tone="true"]) { + #${PANEL_ID} button:disabled { background: ${DISABLED_CONTROL_BG} !important; color: ${DISABLED_CONTROL_TEXT} !important; border-color: ${DISABLED_CONTROL_BORDER} !important; @@ -5648,23 +5648,17 @@ "" ].join(""); } - function ladderActionButton(actionType, label, tone, disabled = false, preserveTone = false) { + function ladderActionButton(actionType, label, tone, disabled = false) { const isBuyTone = tone === "BUY"; const borderColor = isBuyTone ? "var(--color-Buy)" : "var(--color-Sell)"; const background = isBuyTone ? "var(--color-GreenAlpha01)" : "var(--color-RedAlpha01)"; - const disabledAttrs = disabled ? ` disabled aria-disabled="true"${preserveTone ? ' data-ladder-preserve-tone="true"' : ""}` : ""; + const disabledAttrs = disabled ? ' disabled aria-disabled="true"' : ""; const cursor = disabled ? "not-allowed" : "pointer"; return ``; } - function ladderExecutionButton(actionType, label, tone, disabled = false, preserveTone = false) { + function ladderExecutionButton(actionType, label, tone, disabled = false) { if (activeLadderActionType !== actionType) { - return ladderActionButton( - actionType, - label, - tone, - disabled, - Boolean(activeLadderActionType) && preserveTone - ); + return ladderActionButton(actionType, label, tone, disabled); } return ``; } @@ -5691,8 +5685,8 @@ ladderOptionRow(PANEL_COPY.field.interval, PANEL_COPY.tooltip.interval, LADDER_STEP_OPTIONS, getLadderStep(tradeMode, symbol, precision), "step", "") ], actionButtons: [ - ladderExecutionButton("OPEN_LONG", PANEL_COPY.action.openLong, "BUY", actionDisabled, true), - ladderExecutionButton("OPEN_SHORT", PANEL_COPY.action.openShort, "SELL", actionDisabled, true) + ladderExecutionButton("OPEN_LONG", PANEL_COPY.action.openLong, "BUY", actionDisabled), + ladderExecutionButton("OPEN_SHORT", PANEL_COPY.action.openShort, "SELL", actionDisabled) ] }; } @@ -5717,15 +5711,13 @@ "CLOSE_LONG", PANEL_COPY.action.closeLong, "SELL", - closeLongDisabled, - Boolean(closeContext?.knowsLong && closeContext?.hasLong) + closeLongDisabled ), ladderExecutionButton( "CLOSE_SHORT", PANEL_COPY.action.closeShort, "BUY", - closeShortDisabled, - Boolean(closeContext?.knowsShort && closeContext?.hasShort) + closeShortDisabled ) ] }; diff --git a/src/binance-orderbook-trade/index.user.js b/src/binance-orderbook-trade/index.user.js index 1e02dad..381f542 100644 --- a/src/binance-orderbook-trade/index.user.js +++ b/src/binance-orderbook-trade/index.user.js @@ -3,7 +3,7 @@ // @namespace binance.orderbook.trade // @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E // @icon64 data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E -// @version 2.7.131 +// @version 2.7.132 // @author jackhai9 // @description 单击订单簿价格,按当前开仓/平仓 tab 自动填数量并执行下单,内置数量倍率面板 // @match https://www.binance.com/*/futures/* @@ -393,7 +393,7 @@ import { style.id = styleId; style.textContent = ` button[${NATIVE_ACTION_DISABLED_ATTR}="true"], - #${PANEL_ID} button:disabled:not([data-ladder-preserve-tone="true"]) { + #${PANEL_ID} button:disabled { background: ${DISABLED_CONTROL_BG} !important; color: ${DISABLED_CONTROL_TEXT} !important; border-color: ${DISABLED_CONTROL_BORDER} !important; @@ -4811,26 +4811,18 @@ import { ].join(''); } - function ladderActionButton(actionType, label, tone, disabled = false, preserveTone = false) { + function ladderActionButton(actionType, label, tone, disabled = false) { const isBuyTone = tone === 'BUY'; const borderColor = isBuyTone ? 'var(--color-Buy)' : 'var(--color-Sell)'; const background = isBuyTone ? 'var(--color-GreenAlpha01)' : 'var(--color-RedAlpha01)'; - const disabledAttrs = disabled - ? ` disabled aria-disabled="true"${preserveTone ? ' data-ladder-preserve-tone="true"' : ''}` - : ''; + const disabledAttrs = disabled ? ' disabled aria-disabled="true"' : ''; const cursor = disabled ? 'not-allowed' : 'pointer'; return ``; } - function ladderExecutionButton(actionType, label, tone, disabled = false, preserveTone = false) { + function ladderExecutionButton(actionType, label, tone, disabled = false) { if (activeLadderActionType !== actionType) { - return ladderActionButton( - actionType, - label, - tone, - disabled, - Boolean(activeLadderActionType) && preserveTone, - ); + return ladderActionButton(actionType, label, tone, disabled); } return ``; } @@ -4858,8 +4850,8 @@ import { ladderOptionRow(PANEL_COPY.field.interval, PANEL_COPY.tooltip.interval, LADDER_STEP_OPTIONS, getLadderStep(tradeMode, symbol, precision), 'step', ''), ], actionButtons: [ - ladderExecutionButton('OPEN_LONG', PANEL_COPY.action.openLong, 'BUY', actionDisabled, true), - ladderExecutionButton('OPEN_SHORT', PANEL_COPY.action.openShort, 'SELL', actionDisabled, true), + ladderExecutionButton('OPEN_LONG', PANEL_COPY.action.openLong, 'BUY', actionDisabled), + ladderExecutionButton('OPEN_SHORT', PANEL_COPY.action.openShort, 'SELL', actionDisabled), ], }; } @@ -4886,14 +4878,12 @@ import { PANEL_COPY.action.closeLong, 'SELL', closeLongDisabled, - Boolean(closeContext?.knowsLong && closeContext?.hasLong), ), ladderExecutionButton( 'CLOSE_SHORT', PANEL_COPY.action.closeShort, 'BUY', closeShortDisabled, - Boolean(closeContext?.knowsShort && closeContext?.hasShort), ), ], }; diff --git a/test/unit/binance-orderbook-trade/ladder-options.test.js b/test/unit/binance-orderbook-trade/ladder-options.test.js index cc9b17a..8f6c930 100644 --- a/test/unit/binance-orderbook-trade/ladder-options.test.js +++ b/test/unit/binance-orderbook-trade/ladder-options.test.js @@ -246,8 +246,8 @@ test('ladder quantity levels and step options share one stable five-slot grid', }); test('close ladder buttons match the Binance native long-short order', () => { - const closeLongButton = "'CLOSE_LONG',\n PANEL_COPY.action.closeLong,\n 'SELL',\n closeLongDisabled,\n Boolean(closeContext?.knowsLong && closeContext?.hasLong)"; - const closeShortButton = "'CLOSE_SHORT',\n PANEL_COPY.action.closeShort,\n 'BUY',\n closeShortDisabled,\n Boolean(closeContext?.knowsShort && closeContext?.hasShort)"; + const closeLongButton = "'CLOSE_LONG',\n PANEL_COPY.action.closeLong,\n 'SELL',\n closeLongDisabled,\n )"; + const closeShortButton = "'CLOSE_SHORT',\n PANEL_COPY.action.closeShort,\n 'BUY',\n closeShortDisabled,\n )"; assert.notEqual(source.indexOf(closeLongButton), -1); assert.notEqual(source.indexOf(closeShortButton), -1); diff --git a/test/unit/binance-orderbook-trade/source-regressions.test.js b/test/unit/binance-orderbook-trade/source-regressions.test.js index 6f9e681..e516a86 100644 --- a/test/unit/binance-orderbook-trade/source-regressions.test.js +++ b/test/unit/binance-orderbook-trade/source-regressions.test.js @@ -883,11 +883,8 @@ test('cancel current-symbol open orders are single-flight and follow the native assert.match(panelBody, /noOrdersFeedback: cancelNoOrdersFeedbackActive/); assert.match(source, /let activeLadderActionType = null/); assert.match(source, /let activeLadderPanelContext = null/); - assert.match(source, /data-ladder-preserve-tone="true"/); - assert.match(source, /button:disabled:not\(\[data-ladder-preserve-tone="true"\]\)/); - assert.match(source, /Boolean\(activeLadderActionType\) && preserveTone/); - assert.match(source, /Boolean\(closeContext\?\.knowsLong && closeContext\?\.hasLong\)/); - assert.match(source, /Boolean\(closeContext\?\.knowsShort && closeContext\?\.hasShort\)/); + assert.match(source, /#\$\{PANEL_ID\} button:disabled/); + assert.doesNotMatch(source, /data-ladder-preserve-tone|preserveTone/); assert.match(panelBody, /controlSections\.actionButtons/); assert.doesNotMatch(panelBody, /grid-column:span 2/); assert.match(panelBody, /grid-template-columns:repeat\(3,minmax\(0,1fr\)\)/); @@ -933,7 +930,7 @@ test('cancel current-symbol open orders are single-flight and follow the native const actionRowsBody = readFunctionBody('getLadderControlSections'); assert.match(actionRowsBody, /actionDisabled = ladderRunning \|\| cancelCurrentSymbolOpenOrdersBlocksLadderActions/); assert.doesNotMatch(actionRowsBody, /!!cancelCurrentSymbolOpenOrdersTask/); - assert.match(actionRowsBody, /ladderExecutionButton\('OPEN_LONG',[\s\S]*actionDisabled, true\)/); + assert.match(actionRowsBody, /ladderExecutionButton\('OPEN_LONG',[\s\S]*actionDisabled\)/); assert.match(startBody, /activeLadderActionType = actionType/); assert.match(startBody, /activeLadderPanelContext = \{[\s\S]*mode: spec\.mode,[\s\S]*symbol: actionSymbol,[\s\S]*precision: readCurrentOrderbookPrecisionValue\(\)/); assert.match(startBody, /finally\(\(\) => \{[\s\S]*activeLadderActionType = null/);