Skip to content

Commit d7009e6

Browse files
rita-codesclaude
andcommitted
[scheduler-premium] Apply review: clear the selection of a dependency whose type becomes unknown
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 6ba3e28 commit d7009e6

5 files changed

Lines changed: 59 additions & 17 deletions

File tree

‎packages/x-scheduler-internals-premium/src/event-timeline-premium-selectors/eventTimelinePremiumDependencySelectors.ts‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ import type {
99
} from '../models';
1010
import type { EventTimelinePremiumState as State } from '../use-event-timeline-premium';
1111
import {
12-
classifyDependencyEvent,
1312
groupByEventId,
13+
isDependencyActive,
1414
isDependencyReadOnly,
15-
isDependencyType,
1615
} from '../internals/utils/dependency-utils';
1716

1817
// Typed against the two slices they read, so the scheduling plugin (generic over
@@ -25,12 +24,8 @@ const activeModelListSelector = createSelectorMemoized(
2524
(dependencyModelLookup, processedEventLookup) =>
2625
// `dependencyModelLookup` already deduped duplicate ids (last wins) while
2726
// preserving insertion order, so no separate dedup pass is needed here.
28-
Array.from(dependencyModelLookup.values()).filter(
29-
(dependency) =>
30-
isDependencyType(dependency.type) &&
31-
[dependency.source, dependency.target].every(
32-
(eventId) => classifyDependencyEvent(processedEventLookup, eventId) === 'ok',
33-
),
27+
Array.from(dependencyModelLookup.values()).filter((dependency) =>
28+
isDependencyActive(processedEventLookup, dependency),
3429
),
3530
);
3631

‎packages/x-scheduler-internals-premium/src/internals/utils/dependency-utils.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,19 @@ export function classifyDependencyEvent(
169169
}
170170
return 'ok';
171171
}
172+
173+
/**
174+
* Whether the timeline renders the dependency: a supported type and two endpoint
175+
* events that are known and non-recurring. Shared by the active-list selector and
176+
* the selection cleanup, so a dependency the arrows drop can never stay selected.
177+
*/
178+
export function isDependencyActive(
179+
processedEventLookup: Map<SchedulerEventId, SchedulerProcessedEvent>,
180+
dependency: SchedulerDependency,
181+
): boolean {
182+
return (
183+
isDependencyType(dependency.type) &&
184+
classifyDependencyEvent(processedEventLookup, dependency.source) === 'ok' &&
185+
classifyDependencyEvent(processedEventLookup, dependency.target) === 'ok'
186+
);
187+
}

‎packages/x-scheduler-internals-premium/src/use-event-timeline-premium/EventTimelinePremiumStore.ts‎

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ import {
3333
EVENT_TIMELINE_PREMIUM_PRESET_DEFINITIONS,
3434
getPresetPxPerDay,
3535
} from '../internals/utils/preset-utils';
36-
import {
37-
buildDependenciesState,
38-
classifyDependencyEvent,
39-
} from '../internals/utils/dependency-utils';
36+
import { buildDependenciesState, isDependencyActive } from '../internals/utils/dependency-utils';
4037

4138
// Sorted by descending px/day (most zoomed-in first). Each preset's `(timeResolution,
4239
// tickWidth)` must produce a unique px/day — otherwise the order is decided by
@@ -243,11 +240,7 @@ export class EventTimelinePremiumStore<
243240
return;
244241
}
245242
const dependency = dependencyModelLookup.get(selection.id);
246-
if (
247-
dependency === undefined ||
248-
classifyDependencyEvent(processedEventLookup, dependency.source) !== 'ok' ||
249-
classifyDependencyEvent(processedEventLookup, dependency.target) !== 'ok'
250-
) {
243+
if (dependency === undefined || !isDependencyActive(processedEventLookup, dependency)) {
251244
this.setSelection(null);
252245
}
253246
};

‎packages/x-scheduler-internals-premium/src/use-event-timeline-premium/tests/dependencies.EventTimelinePremiumStore.test.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,24 @@ describe('Dependencies - EventTimelinePremiumStore', () => {
877877
expect(store.state.selection).to.equal(null);
878878
});
879879

880+
it('should clear the selection when the type of the selected dependency becomes unknown', () => {
881+
const store = new EventTimelinePremiumStore(
882+
{ ...DEFAULT_PARAMS, dependencies: [DEP_AB] },
883+
adapter,
884+
);
885+
store.setSelectedDependencyId('dep-1');
886+
887+
// Same id, unsupported type: the arrows drop it, so the selection must go too.
888+
expect(() => {
889+
store.updateStateFromParameters(
890+
{ ...DEFAULT_PARAMS, dependencies: [{ ...DEP_AB, type: 'FS' as any }] },
891+
adapter,
892+
);
893+
}).toWarnDev(['MUI X Scheduler: The dependency "dep-1" has the unknown type "FS".']);
894+
895+
expect(store.state.selection).to.equal(null);
896+
});
897+
880898
describe('dev warnings', () => {
881899
it('should warn and keep the feature disabled when onDependenciesChange is provided without dependencies', () => {
882900
let store!: EventTimelinePremiumStore<any, any>;

‎packages/x-scheduler-premium/src/event-timeline-premium/tests/dependencyTerminals.EventTimelinePremium.test.tsx‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,6 +1649,26 @@ describe('<EventTimelinePremium /> dependency terminals', () => {
16491649
expect(store.state.selection).to.equal(null);
16501650
});
16511651

1652+
it('should clear the selection when the type of the selected dependency becomes unknown', async () => {
1653+
const dependency = buildDependency('dep-1', 'event-a', 'event-b');
1654+
const view = await renderTimeline({ events: [eventA, eventB], dependencies: [dependency] });
1655+
1656+
fireEvent.click(document.querySelector('[data-dependency-hit="dep-1"]')!);
1657+
expect(view.store.state.selection).to.deep.equal({ type: 'dependency', id: 'dep-1' });
1658+
1659+
// Same id, unsupported type: the arrows drop it and the terminals must not
1660+
// resolve the edges of a type they cannot draw.
1661+
expect(() => {
1662+
view.setProps({ dependencies: [{ ...dependency, type: 'FS' as any }] });
1663+
}).toWarnDev(['MUI X Scheduler: The dependency "dep-1" has the unknown type "FS".']);
1664+
1665+
await waitFor(() => {
1666+
expect(getArrowPaths()).to.have.length(0);
1667+
});
1668+
expect(document.querySelector('[data-dependency-delete-button]')).to.equal(null);
1669+
expect(view.store.state.selection).to.equal(null);
1670+
});
1671+
16521672
it('should discard the creation gesture when the timeline unmounts mid-drag', async () => {
16531673
const { store, unmount } = await renderTimeline({
16541674
events: [eventA, eventB],

0 commit comments

Comments
 (0)