Conversation
When `delay` changed from undefined to a number, `delayPassed` stayed true (set at init), so the fallback was never hidden during the new delay period. Conversely, changing from a number to undefined left `delayPassed` false, hiding the fallback permanently. Reset `delayPassed` inside the watchEffect so it tracks prop changes: - Set false + start timeout when delay becomes defined. - Set true immediately when delay becomes undefined.
The inner `nextFrame` RAF was declared inside the outer `AnimationFrame.request` callback. Its return value (a cleanup function) was ignored by `requestAnimationFrame`, so `nextFrame` was never canceled when the watch re-ran or the component unmounted. Hoist `nextFrame` to the outer scope and cancel both frames in the watch cleanup, matching the correct pattern already used in `handlePanelRef`.
`Timeout.start` and `Timeout.clear` referenced `window.setTimeout` and `window.clearTimeout`, which throw in SSR environments where `window` is undefined. Use the global `setTimeout`/`clearTimeout` instead, which are available in both browser and Node.js runtimes.
…egend FieldsetLegend.onBeforeUnmount unconditionally called setLegendId(undefined), which cleared aria-labelledby even when another legend was still mounted and owned the current legendId. Guard the cleanup so it only clears when this legend's id matches the current legendId.
|
Cursor Agent can help with this pull request. Just |
📝 WalkthroughWalkthroughThis pull request refines control flow and cleanup logic across several components. It adds tests for delay-based visibility transitions, improves legend ID management during unmounting, enhances animation frame cleanup tracking, and normalizes timeout utility functions to use global scope rather than explicit window references. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Comment |
commit: |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/core/src/fieldset/legend/FieldsetLegend.test.ts (1)
72-98: Add a companion test for active legend unmount behavior.Please add one more case defining expected behavior when the active legend unmounts while another legend is still mounted (should it clear or transfer
aria-labelledby). This will lock in the full lifecycle contract.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/fieldset/legend/FieldsetLegend.test.ts` around lines 72 - 98, Add a companion test that verifies behavior when the active legend unmounts and another legend remains: create a TestComponent using FieldsetRoot and FieldsetLegend like the existing test but start with only the main legend mounted, then set showExtra to true so the extra legend mounts after main (making the extra legend the active one), assert the fieldset aria-labelledby points to the newly mounted extra legend, then set showExtra back to false to unmount the active extra legend and assert aria-labelledby transfers back to the remaining legend (legend-main); reference the same symbols FieldsetRoot, FieldsetLegend, showExtra, TestComponent and the fieldset variable in the new test.packages/core/src/utils/useTimeout.ts (1)
19-22: Prefer portable timer ID typing instead of double-casting.Line 22's
as unknown as TimeoutIdbypasses type safety. UseReturnType<typeof setTimeout>(and anullsentinel) to keep this SSR-safe and avoid unsafe casts. This applies to the timer logic across lines 19–22 and 29–32.♻️ Suggested refactor
-type TimeoutId = number - -const EMPTY = 0 as TimeoutId +type TimeoutId = ReturnType<typeof setTimeout> | null + +const EMPTY: TimeoutId = null @@ - this.currentId = setTimeout(() => { + this.currentId = setTimeout(() => { this.currentId = EMPTY fn() - }, delay) as unknown as TimeoutId + }, delay)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/utils/useTimeout.ts` around lines 19 - 22, The timer IDs are being double-cast with "as unknown as TimeoutId" for this.currentId in the setTimeout callback (and similarly in the clear/reset logic around the other timer assignments), which bypasses type safety; change the type of currentId to use ReturnType<typeof setTimeout> | null (use null as the sentinel instead of EMPTY) and assign setTimeout directly without casts, updating any checks/clears (e.g., places referencing currentId, the setTimeout call, and the clearTimeout usage around lines like the setTimeout and clear/reset blocks) to handle null safely and call clearTimeout(currentId) only when currentId is not null.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/core/src/fieldset/legend/FieldsetLegend.test.ts`:
- Around line 72-98: Add a companion test that verifies behavior when the active
legend unmounts and another legend remains: create a TestComponent using
FieldsetRoot and FieldsetLegend like the existing test but start with only the
main legend mounted, then set showExtra to true so the extra legend mounts after
main (making the extra legend the active one), assert the fieldset
aria-labelledby points to the newly mounted extra legend, then set showExtra
back to false to unmount the active extra legend and assert aria-labelledby
transfers back to the remaining legend (legend-main); reference the same symbols
FieldsetRoot, FieldsetLegend, showExtra, TestComponent and the fieldset variable
in the new test.
In `@packages/core/src/utils/useTimeout.ts`:
- Around line 19-22: The timer IDs are being double-cast with "as unknown as
TimeoutId" for this.currentId in the setTimeout callback (and similarly in the
clear/reset logic around the other timer assignments), which bypasses type
safety; change the type of currentId to use ReturnType<typeof setTimeout> | null
(use null as the sentinel instead of EMPTY) and assign setTimeout directly
without casts, updating any checks/clears (e.g., places referencing currentId,
the setTimeout call, and the clearTimeout usage around lines like the setTimeout
and clear/reset blocks) to handle null safely and call clearTimeout(currentId)
only when currentId is not null.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7c4c0e10-aa7f-468e-a162-aacab4581043
📒 Files selected for processing (6)
packages/core/src/avatar/fallback/AvatarFallback.test.tspackages/core/src/avatar/fallback/AvatarFallback.vuepackages/core/src/collapsible/panel/useCollapsiblePanel.tspackages/core/src/fieldset/legend/FieldsetLegend.test.tspackages/core/src/fieldset/legend/FieldsetLegend.vuepackages/core/src/utils/useTimeout.ts
Fixes several high-confidence bugs across Avatar, Collapsible, Fieldset, and shared utilities to improve dynamic behavior, prevent memory leaks, and ensure SSR compatibility.
The bugs involved incorrect state management for dynamic props (
AvatarFallback), improper cleanup ofrequestAnimationFramecalls (useCollapsiblePanel), reliance on browser-specific globals in a universal utility (useTimeout), and incorrect shared state management during component unmounts (FieldsetLegend). Each fix addresses a specific edge case or environment incompatibility.Summary by CodeRabbit
Bug Fixes
Tests
Refactor