refactor: migrate shared type imports

This commit is contained in:
2026-03-27 00:33:52 -07:00
parent a92631bf52
commit 49a582b4fc
66 changed files with 793 additions and 479 deletions

View File

@@ -47,7 +47,10 @@ type ControllerBindingCaptureResult =
dpadDirection: ControllerDpadFallback;
};
function isActiveButton(button: ControllerButtonState | undefined, triggerDeadzone: number): boolean {
function isActiveButton(
button: ControllerButtonState | undefined,
triggerDeadzone: number,
): boolean {
if (!button) return false;
return Boolean(button.pressed) || button.value >= triggerDeadzone;
}
@@ -90,7 +93,10 @@ export function createControllerBindingCapture(options: {
});
}
function arm(nextTarget: ControllerBindingCaptureTarget, snapshot: ControllerBindingCaptureSnapshot): void {
function arm(
nextTarget: ControllerBindingCaptureTarget,
snapshot: ControllerBindingCaptureSnapshot,
): void {
target = nextTarget;
resetBlockedState(snapshot);
}
@@ -139,7 +145,10 @@ export function createControllerBindingCapture(options: {
}
// After dpad early-return, only 'discrete' | 'axis' remain
const narrowedTarget: Extract<ControllerBindingCaptureTarget, { bindingType: 'discrete' | 'axis' }> = target;
const narrowedTarget: Extract<
ControllerBindingCaptureTarget,
{ bindingType: 'discrete' | 'axis' }
> = target;
for (let index = 0; index < snapshot.buttons.length; index += 1) {
if (!isActiveButton(snapshot.buttons[index], options.triggerDeadzone)) continue;

View File

@@ -194,13 +194,7 @@ export function createKeyboardHandlers(
(isBackslashConfigured && e.key === '\\') ||
(toggleKey.length === 1 && e.key === toggleKey);
return (
keyMatches &&
!e.ctrlKey &&
!e.altKey &&
!e.metaKey &&
!e.repeat
);
return keyMatches && !e.ctrlKey && !e.altKey && !e.metaKey && !e.repeat;
}
function isStatsOverlayToggle(e: KeyboardEvent): boolean {

View File

@@ -3,10 +3,7 @@ import test from 'node:test';
import type { SubtitleSidebarConfig } from '../../types';
import { createMouseHandlers } from './mouse.js';
import {
YOMITAN_POPUP_HIDDEN_EVENT,
YOMITAN_POPUP_SHOWN_EVENT,
} from '../yomitan-popup.js';
import { YOMITAN_POPUP_HIDDEN_EVENT, YOMITAN_POPUP_SHOWN_EVENT } from '../yomitan-popup.js';
function createClassList() {
const classes = new Set<string>();
@@ -118,9 +115,15 @@ test('secondary hover pauses on enter, reveals secondary subtitle, and resumes o
});
await handlers.handleSecondaryMouseEnter();
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), true);
assert.equal(
ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'),
true,
);
await handlers.handleSecondaryMouseLeave();
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), false);
assert.equal(
ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'),
false,
);
assert.deepEqual(mpvCommands, [
['set_property', 'pause', 'yes'],
@@ -186,7 +189,10 @@ test('secondary leave toward primary subtitle container clears the secondary hov
} as unknown as MouseEvent);
assert.equal(ctx.state.isOverSubtitle, false);
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), false);
assert.equal(
ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'),
false,
);
assert.deepEqual(mpvCommands, [['set_property', 'pause', 'yes']]);
});
@@ -237,7 +243,10 @@ test('primary hover pauses on enter without revealing secondary subtitle', async
});
await handlers.handlePrimaryMouseEnter();
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), false);
assert.equal(
ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'),
false,
);
await handlers.handlePrimaryMouseLeave();
assert.deepEqual(mpvCommands, [
@@ -394,7 +403,10 @@ test('restorePointerInteractionState reapplies the secondary hover class from po
mousemove?.({ clientX: 10, clientY: 20 } as MouseEvent);
assert.equal(ctx.state.isOverSubtitle, true);
assert.equal(ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'), true);
assert.equal(
ctx.dom.secondarySubContainer.classList.contains('secondary-sub-hover-active'),
true,
);
} finally {
Object.defineProperty(globalThis, 'document', { configurable: true, value: originalDocument });
Object.defineProperty(globalThis, 'window', { configurable: true, value: originalWindow });

View File

@@ -228,10 +228,7 @@ export function createMouseHandlers(
syncOverlayMouseIgnoreState(ctx);
}
async function handleMouseEnter(
_event?: MouseEvent,
showSecondaryHover = false,
): Promise<void> {
async function handleMouseEnter(_event?: MouseEvent, showSecondaryHover = false): Promise<void> {
ctx.state.isOverSubtitle = true;
if (showSecondaryHover) {
ctx.dom.secondarySubContainer.classList.add('secondary-sub-hover-active');
@@ -267,10 +264,7 @@ export function createMouseHandlers(
pausedBySubtitleHover = true;
}
async function handleMouseLeave(
_event?: MouseEvent,
hideSecondaryHover = false,
): Promise<void> {
async function handleMouseLeave(_event?: MouseEvent, hideSecondaryHover = false): Promise<void> {
const relatedTarget = _event?.relatedTarget ?? null;
const otherContainer = hideSecondaryHover
? ctx.dom.subtitleContainer

View File

@@ -118,10 +118,14 @@ export function getDefaultControllerBinding(actionId: ControllerBindingActionId)
if (!definition) {
return { kind: 'none' } as const;
}
return JSON.parse(JSON.stringify(definition.defaultBinding)) as ResolvedControllerConfig['bindings'][ControllerBindingActionId];
return JSON.parse(
JSON.stringify(definition.defaultBinding),
) as ResolvedControllerConfig['bindings'][ControllerBindingActionId];
}
export function getDefaultDpadFallback(actionId: ControllerBindingActionId): ControllerDpadFallback {
export function getDefaultDpadFallback(
actionId: ControllerBindingActionId,
): ControllerDpadFallback {
const definition = getControllerBindingDefinition(actionId);
if (!definition || definition.defaultBinding.kind !== 'axis') return 'none';
const binding = definition.defaultBinding;
@@ -249,7 +253,11 @@ export function createControllerConfigForm(options: {
if (definition.bindingType === 'axis') {
renderAxisStickRow(definition, binding as ResolvedControllerAxisBinding, learningActionId);
renderAxisDpadRow(definition, binding as ResolvedControllerAxisBinding, dpadLearningActionId);
renderAxisDpadRow(
definition,
binding as ResolvedControllerAxisBinding,
dpadLearningActionId,
);
} else {
renderDiscreteRow(definition, binding, learningActionId);
}
@@ -265,7 +273,12 @@ export function createControllerConfigForm(options: {
const isExpanded = expandedRowKey === rowKey;
const isLearning = learningActionId === definition.id;
const row = createRow(definition.label, formatFriendlyBindingLabel(binding), binding.kind === 'none', isExpanded);
const row = createRow(
definition.label,
formatFriendlyBindingLabel(binding),
binding.kind === 'none',
isExpanded,
);
row.addEventListener('click', () => {
expandedRowKey = expandedRowKey === rowKey ? null : rowKey;
render();
@@ -277,9 +290,18 @@ export function createControllerConfigForm(options: {
? 'Press a button, trigger, or move a stick\u2026'
: `Currently: ${formatControllerBindingSummary(binding)}`;
const panel = createEditPanel(hint, isLearning, {
onLearn: (e) => { e.stopPropagation(); options.onLearn(definition.id, definition.bindingType); },
onClear: (e) => { e.stopPropagation(); options.onClear(definition.id); },
onReset: (e) => { e.stopPropagation(); options.onReset(definition.id); },
onLearn: (e) => {
e.stopPropagation();
options.onLearn(definition.id, definition.bindingType);
},
onClear: (e) => {
e.stopPropagation();
options.onClear(definition.id);
},
onReset: (e) => {
e.stopPropagation();
options.onReset(definition.id);
},
});
options.container.appendChild(panel);
}
@@ -294,7 +316,12 @@ export function createControllerConfigForm(options: {
const isExpanded = expandedRowKey === rowKey;
const isLearning = learningActionId === definition.id;
const row = createRow(`${definition.label} (Stick)`, formatFriendlyStickLabel(binding), binding.kind === 'none', isExpanded);
const row = createRow(
`${definition.label} (Stick)`,
formatFriendlyStickLabel(binding),
binding.kind === 'none',
isExpanded,
);
row.addEventListener('click', () => {
expandedRowKey = expandedRowKey === rowKey ? null : rowKey;
render();
@@ -305,9 +332,18 @@ export function createControllerConfigForm(options: {
const summary = binding.kind === 'none' ? 'Disabled' : `Axis ${binding.axisIndex}`;
const hint = isLearning ? 'Move a stick or trigger\u2026' : `Currently: ${summary}`;
const panel = createEditPanel(hint, isLearning, {
onLearn: (e) => { e.stopPropagation(); options.onLearn(definition.id, 'axis'); },
onClear: (e) => { e.stopPropagation(); options.onClear(definition.id); },
onReset: (e) => { e.stopPropagation(); options.onReset(definition.id); },
onLearn: (e) => {
e.stopPropagation();
options.onLearn(definition.id, 'axis');
},
onClear: (e) => {
e.stopPropagation();
options.onClear(definition.id);
},
onReset: (e) => {
e.stopPropagation();
options.onReset(definition.id);
},
});
options.container.appendChild(panel);
}
@@ -322,9 +358,15 @@ export function createControllerConfigForm(options: {
const isExpanded = expandedRowKey === rowKey;
const isLearning = dpadLearningActionId === definition.id;
const dpadFallback: ControllerDpadFallback = binding.kind === 'none' ? 'none' : binding.dpadFallback;
const dpadFallback: ControllerDpadFallback =
binding.kind === 'none' ? 'none' : binding.dpadFallback;
const badgeText = DPAD_FALLBACK_LABELS[dpadFallback];
const row = createRow(`${definition.label} (D-pad)`, badgeText, dpadFallback === 'none', isExpanded);
const row = createRow(
`${definition.label} (D-pad)`,
badgeText,
dpadFallback === 'none',
isExpanded,
);
row.addEventListener('click', () => {
expandedRowKey = expandedRowKey === rowKey ? null : rowKey;
render();
@@ -336,15 +378,29 @@ export function createControllerConfigForm(options: {
? 'Press a D-pad direction\u2026'
: `Currently: ${DPAD_FALLBACK_LABELS[dpadFallback]}`;
const panel = createEditPanel(hint, isLearning, {
onLearn: (e) => { e.stopPropagation(); options.onDpadLearn(definition.id); },
onClear: (e) => { e.stopPropagation(); options.onDpadClear(definition.id); },
onReset: (e) => { e.stopPropagation(); options.onDpadReset(definition.id); },
onLearn: (e) => {
e.stopPropagation();
options.onDpadLearn(definition.id);
},
onClear: (e) => {
e.stopPropagation();
options.onDpadClear(definition.id);
},
onReset: (e) => {
e.stopPropagation();
options.onDpadReset(definition.id);
},
});
options.container.appendChild(panel);
}
}
function createRow(labelText: string, badgeText: string, isDisabled: boolean, isExpanded: boolean): HTMLDivElement {
function createRow(
labelText: string,
badgeText: string,
isDisabled: boolean,
isExpanded: boolean,
): HTMLDivElement {
const row = document.createElement('div');
row.className = 'controller-config-row';
if (isExpanded) row.classList.add('expanded');

View File

@@ -66,7 +66,10 @@ function createFakeElement() {
if (!match) return null;
const testId = match[1];
for (const child of el.children) {
if (typeof child.getAttribute === 'function' && child.getAttribute('data-testid') === testId) {
if (
typeof child.getAttribute === 'function' &&
child.getAttribute('data-testid') === testId
) {
return child;
}
if (typeof child.querySelector === 'function') {
@@ -105,7 +108,10 @@ function installFakeDom() {
return {
restore: () => {
Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
Object.defineProperty(globalThis, 'document', {
configurable: true,
value: previousDocument,
});
},
};
}

View File

@@ -31,8 +31,9 @@ export function createControllerSelectModal(
let lastRenderedActiveGamepadId: string | null = null;
let lastRenderedPreferredId = '';
type ControllerBindingKey = keyof NonNullable<typeof ctx.state.controllerConfig>['bindings'];
type ControllerBindingValue =
NonNullable<NonNullable<typeof ctx.state.controllerConfig>['bindings']>[ControllerBindingKey];
type ControllerBindingValue = NonNullable<
NonNullable<typeof ctx.state.controllerConfig>['bindings']
>[ControllerBindingKey];
let learningActionId: ControllerBindingKey | null = null;
let dpadLearningActionId: ControllerBindingKey | null = null;
let bindingCapture: ReturnType<typeof createControllerBindingCapture> | null = null;
@@ -198,7 +199,9 @@ export function createControllerSelectModal(
lastRenderedPreferredId = preferredId;
}
async function saveControllerConfig(update: Parameters<typeof window.electronAPI.saveControllerConfig>[0]) {
async function saveControllerConfig(
update: Parameters<typeof window.electronAPI.saveControllerConfig>[0],
) {
await window.electronAPI.saveControllerConfig(update);
if (!ctx.state.controllerConfig) return;
if (update.preferredGamepadId !== undefined) {
@@ -304,7 +307,10 @@ export function createControllerSelectModal(
if (result.bindingType === 'dpad') {
void saveDpadFallback(result.actionId as ControllerBindingKey, result.dpadDirection);
} else {
void saveBinding(result.actionId as ControllerBindingKey, result.binding as ControllerBindingValue);
void saveBinding(
result.actionId as ControllerBindingKey,
result.binding as ControllerBindingValue,
);
}
}
}

View File

@@ -90,10 +90,7 @@ test('findActiveSubtitleCueIndex prefers current subtitle timing over near-futur
{ startTime: 233.05, endTime: 236, text: 'next' },
];
assert.equal(
findActiveSubtitleCueIndex(cues, { text: 'previous', startTime: 231 }, 233, 0),
0,
);
assert.equal(findActiveSubtitleCueIndex(cues, { text: 'previous', startTime: 231 }, 233, 0), 0);
});
test('subtitle sidebar modal opens from snapshot and clicking cue seeks playback', async () => {
@@ -1217,10 +1214,22 @@ test('subtitle sidebar polling schedules serialized timeouts instead of interval
assert.equal(timeoutCount > 0, true);
assert.equal(intervalCount, 0);
} finally {
Object.defineProperty(globalThis, 'setTimeout', { configurable: true, value: previousSetTimeout });
Object.defineProperty(globalThis, 'clearTimeout', { configurable: true, value: previousClearTimeout });
Object.defineProperty(globalThis, 'setInterval', { configurable: true, value: previousSetInterval });
Object.defineProperty(globalThis, 'clearInterval', { configurable: true, value: previousClearInterval });
Object.defineProperty(globalThis, 'setTimeout', {
configurable: true,
value: previousSetTimeout,
});
Object.defineProperty(globalThis, 'clearTimeout', {
configurable: true,
value: previousClearTimeout,
});
Object.defineProperty(globalThis, 'setInterval', {
configurable: true,
value: previousSetInterval,
});
Object.defineProperty(globalThis, 'clearInterval', {
configurable: true,
value: previousClearInterval,
});
Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow });
Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument });
}
@@ -1564,17 +1573,13 @@ test('subtitle sidebar embedded layout reserves and releases mpv right margin',
assert.ok(
mpvCommands.some(
(command) =>
command[0] === 'set_property' &&
command[1] === 'osd-align-x' &&
command[2] === 'left',
command[0] === 'set_property' && command[1] === 'osd-align-x' && command[2] === 'left',
),
);
assert.ok(
mpvCommands.some(
(command) =>
command[0] === 'set_property' &&
command[1] === 'osd-align-y' &&
command[2] === 'top',
command[0] === 'set_property' && command[1] === 'osd-align-y' && command[2] === 'top',
),
);
assert.ok(
@@ -1597,7 +1602,11 @@ test('subtitle sidebar embedded layout reserves and releases mpv right margin',
assert.deepEqual(mpvCommands.at(-5), ['set_property', 'video-margin-ratio-right', 0]);
assert.deepEqual(mpvCommands.at(-4), ['set_property', 'osd-align-x', 'left']);
assert.deepEqual(mpvCommands.at(-3), ['set_property', 'osd-align-y', 'top']);
assert.deepEqual(mpvCommands.at(-2), ['set_property', 'user-data/osc/margins', '{"l":0,"r":0,"t":0,"b":0}']);
assert.deepEqual(mpvCommands.at(-2), [
'set_property',
'user-data/osc/margins',
'{"l":0,"r":0,"t":0,"b":0}',
]);
assert.deepEqual(mpvCommands.at(-1), ['set_property', 'video-pan-x', 0]);
assert.equal(bodyClassList.contains('subtitle-sidebar-embedded-open'), false);
assert.deepEqual(rootStyleCalls.at(-1), ['--subtitle-sidebar-reserved-width', '0px']);

View File

@@ -1,8 +1,4 @@
import type {
SubtitleCue,
SubtitleData,
SubtitleSidebarSnapshot,
} from '../../types';
import type { SubtitleCue, SubtitleData, SubtitleSidebarSnapshot } from '../../types';
import type { ModalStateReader, RendererContext } from '../context';
import { syncOverlayMouseIgnoreState } from '../overlay-mouse-ignore.js';
@@ -76,8 +72,7 @@ export function findActiveSubtitleCueIndex(
if (typeof currentTimeSec === 'number' && Number.isFinite(currentTimeSec)) {
const activeOrUpcomingCue = cues.findIndex(
(cue) =>
cue.endTime > currentTimeSec &&
cue.startTime <= currentTimeSec + ACTIVE_CUE_LOOKAHEAD_SEC,
cue.endTime > currentTimeSec && cue.startTime <= currentTimeSec + ACTIVE_CUE_LOOKAHEAD_SEC,
);
if (activeOrUpcomingCue >= 0) {
return activeOrUpcomingCue;
@@ -109,8 +104,7 @@ export function findActiveSubtitleCueIndex(
return -1;
}
const hasTiming =
typeof current.startTime === 'number' && Number.isFinite(current.startTime);
const hasTiming = typeof current.startTime === 'number' && Number.isFinite(current.startTime);
if (preferredCueIndex >= 0) {
if (!hasTiming && currentTimeSec === null) {
@@ -213,16 +207,8 @@ export function createSubtitleSidebarModal(
'video-margin-ratio-right',
Number(ratio.toFixed(4)),
]);
window.electronAPI.sendMpvCommand([
'set_property',
'osd-align-x',
'left',
]);
window.electronAPI.sendMpvCommand([
'set_property',
'osd-align-y',
'top',
]);
window.electronAPI.sendMpvCommand(['set_property', 'osd-align-x', 'left']);
window.electronAPI.sendMpvCommand(['set_property', 'osd-align-y', 'top']);
window.electronAPI.sendMpvCommand([
'set_property',
'user-data/osc/margins',
@@ -302,13 +288,14 @@ export function createSubtitleSidebarModal(
}
const list = ctx.dom.subtitleSidebarList;
const active = list.children[ctx.state.subtitleSidebarActiveCueIndex] as HTMLElement | undefined;
const active = list.children[ctx.state.subtitleSidebarActiveCueIndex] as
| HTMLElement
| undefined;
if (!active) {
return;
}
const targetScrollTop =
active.offsetTop - (list.clientHeight - active.clientHeight) / 2;
const targetScrollTop = active.offsetTop - (list.clientHeight - active.clientHeight) / 2;
const nextScrollTop = Math.max(0, targetScrollTop);
if (previousActiveCueIndex < 0) {
list.scrollTop = nextScrollTop;
@@ -363,9 +350,9 @@ export function createSubtitleSidebarModal(
}
if (ctx.state.subtitleSidebarActiveCueIndex >= 0) {
const current = ctx.dom.subtitleSidebarList.children[ctx.state.subtitleSidebarActiveCueIndex] as
| HTMLElement
| undefined;
const current = ctx.dom.subtitleSidebarList.children[
ctx.state.subtitleSidebarActiveCueIndex
] as HTMLElement | undefined;
current?.classList.add('active');
}
}
@@ -476,7 +463,11 @@ export function createSubtitleSidebarModal(
async function autoOpenSubtitleSidebarOnStartup(): Promise<void> {
const snapshot = await refreshSnapshot();
if (!snapshot.config.enabled || !snapshot.config.autoOpen || ctx.state.subtitleSidebarModalOpen) {
if (
!snapshot.config.enabled ||
!snapshot.config.autoOpen ||
ctx.state.subtitleSidebarModalOpen
) {
return;
}
await openSubtitleSidebarModal();
@@ -512,10 +503,7 @@ export function createSubtitleSidebarModal(
return;
}
updateActiveCue(
{ text: data.text, startTime: data.startTime },
data.startTime ?? null,
);
updateActiveCue({ text: data.text, startTime: data.startTime }, data.startTime ?? null);
}
function wireDomEvents(): void {

View File

@@ -28,13 +28,13 @@ export function createYoutubeTrackPickerModal(
function setStatus(message: string, isError = false): void {
ctx.state.youtubePickerStatus = message;
ctx.dom.youtubePickerStatus.textContent = message;
ctx.dom.youtubePickerStatus.style.color = isError
? '#ed8796'
: '#a5adcb';
ctx.dom.youtubePickerStatus.style.color = isError ? '#ed8796' : '#a5adcb';
}
function getTrackLabel(trackId: string): string {
return ctx.state.youtubePickerPayload?.tracks.find((track) => track.id === trackId)?.label ?? '';
return (
ctx.state.youtubePickerPayload?.tracks.find((track) => track.id === trackId)?.label ?? ''
);
}
function renderTrackList(): void {
@@ -82,10 +82,7 @@ export function createYoutubeTrackPickerModal(
if (track.id === primaryTrackId) continue;
ctx.dom.youtubePickerSecondarySelect.appendChild(createOption(track.id, track.label));
}
if (
primaryTrackId &&
ctx.dom.youtubePickerSecondarySelect.value === primaryTrackId
) {
if (primaryTrackId && ctx.dom.youtubePickerSecondarySelect.value === primaryTrackId) {
ctx.dom.youtubePickerSecondarySelect.value = '';
}
}
@@ -126,7 +123,9 @@ export function createYoutubeTrackPickerModal(
setStatus('Select the subtitle tracks to download.');
}
async function resolveSelection(action: 'use-selected' | 'continue-without-subtitles'): Promise<void> {
async function resolveSelection(
action: 'use-selected' | 'continue-without-subtitles',
): Promise<void> {
if (resolveSelectionInFlight) {
return;
}
@@ -238,7 +237,9 @@ export function createYoutubeTrackPickerModal(
return true;
}
void resolveSelection(
payloadHasTracks(ctx.state.youtubePickerPayload) ? 'use-selected' : 'continue-without-subtitles',
payloadHasTracks(ctx.state.youtubePickerPayload)
? 'use-selected'
: 'continue-without-subtitles',
);
return true;
}
@@ -269,7 +270,9 @@ export function createYoutubeTrackPickerModal(
ctx.dom.youtubePickerContinueButton.addEventListener('click', () => {
void resolveSelection(
payloadHasTracks(ctx.state.youtubePickerPayload) ? 'use-selected' : 'continue-without-subtitles',
payloadHasTracks(ctx.state.youtubePickerPayload)
? 'use-selected'
: 'continue-without-subtitles',
);
});

View File

@@ -7,14 +7,14 @@ function isBlockingOverlayModalOpen(state: RendererState): boolean {
return Boolean(
state.controllerSelectModalOpen ||
state.controllerDebugModalOpen ||
state.jimakuModalOpen ||
state.youtubePickerModalOpen ||
state.kikuModalOpen ||
state.runtimeOptionsModalOpen ||
state.subsyncModalOpen ||
state.sessionHelpModalOpen ||
(state.subtitleSidebarModalOpen && !embeddedSidebarOpen),
state.controllerDebugModalOpen ||
state.jimakuModalOpen ||
state.youtubePickerModalOpen ||
state.kikuModalOpen ||
state.runtimeOptionsModalOpen ||
state.subsyncModalOpen ||
state.sessionHelpModalOpen ||
(state.subtitleSidebarModalOpen && !embeddedSidebarOpen),
);
}

View File

@@ -552,8 +552,14 @@ async function init(): Promise<void> {
ctx.dom.subtitleContainer.addEventListener('mouseenter', mouseHandlers.handlePrimaryMouseEnter);
ctx.dom.subtitleContainer.addEventListener('mouseleave', mouseHandlers.handlePrimaryMouseLeave);
ctx.dom.secondarySubContainer.addEventListener('mouseenter', mouseHandlers.handleSecondaryMouseEnter);
ctx.dom.secondarySubContainer.addEventListener('mouseleave', mouseHandlers.handleSecondaryMouseLeave);
ctx.dom.secondarySubContainer.addEventListener(
'mouseenter',
mouseHandlers.handleSecondaryMouseEnter,
);
ctx.dom.secondarySubContainer.addEventListener(
'mouseleave',
mouseHandlers.handleSecondaryMouseLeave,
);
mouseHandlers.setupResizeHandler();
mouseHandlers.setupPointerTracking();

View File

@@ -296,7 +296,7 @@ body {
.youtube-picker-content {
width: min(820px, 92%);
background:
radial-gradient(circle at top right, rgba(198, 160, 246, 0.10), transparent 34%),
radial-gradient(circle at top right, rgba(198, 160, 246, 0.1), transparent 34%),
linear-gradient(180deg, rgba(36, 39, 58, 0.98), rgba(30, 32, 48, 0.98));
border-color: rgba(138, 173, 244, 0.25);
}
@@ -1342,8 +1342,14 @@ iframe[id^='yomitan-popup'] {
}
@keyframes configEditSlideIn {
from { max-height: 0; opacity: 0; }
to { max-height: 120px; opacity: 1; }
from {
max-height: 0;
opacity: 0;
}
to {
max-height: 120px;
opacity: 1;
}
}
.controller-config-edit-inner {
@@ -1365,8 +1371,13 @@ iframe[id^='yomitan-popup'] {
}
@keyframes configLearnPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
}
.controller-config-edit-actions {
@@ -1404,7 +1415,9 @@ iframe[id^='yomitan-popup'] {
color: #6e738d;
font-size: 12px;
cursor: pointer;
transition: background 120ms ease, color 120ms ease;
transition:
background 120ms ease,
color 120ms ease;
}
.btn-secondary:hover {
@@ -1497,14 +1510,13 @@ body.subtitle-sidebar-embedded-open .subtitle-sidebar-modal {
max-height: calc(100vh - 28px);
height: auto;
margin-left: auto;
font-family:
var(
--subtitle-sidebar-font-family,
'M PLUS 1',
'Noto Sans CJK JP',
'Hiragino Sans',
sans-serif
);
font-family: var(
--subtitle-sidebar-font-family,
'M PLUS 1',
'Noto Sans CJK JP',
'Hiragino Sans',
sans-serif
);
font-size: var(--subtitle-sidebar-font-size, 16px);
background: var(--subtitle-sidebar-background-color, rgba(73, 77, 100, 0.9));
color: var(--subtitle-sidebar-text-color, #cad3f5);

View File

@@ -981,18 +981,9 @@ test('JLPT CSS rules use underline-only styling in renderer stylesheet', () => {
cssText,
'body.subtitle-sidebar-embedded-open #secondarySubContainer.secondary-sub-hover',
);
assert.match(
secondaryEmbeddedHoverBlock,
/right:\s*var\(--subtitle-sidebar-reserved-width\);/,
);
assert.match(
secondaryEmbeddedHoverBlock,
/max-width:\s*none;/,
);
assert.match(
secondaryEmbeddedHoverBlock,
/transform:\s*none;/,
);
assert.match(secondaryEmbeddedHoverBlock, /right:\s*var\(--subtitle-sidebar-reserved-width\);/);
assert.match(secondaryEmbeddedHoverBlock, /max-width:\s*none;/);
assert.match(secondaryEmbeddedHoverBlock, /transform:\s*none;/);
assert.doesNotMatch(
secondaryEmbeddedHoverBlock,
/transform:\s*translateX\(calc\(var\(--subtitle-sidebar-reserved-width\)\s*\*\s*-0\.5\)\);/,