refactor(web): extract range selection bootstrap
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { finalizeRangeSelection, updateRangeSelectionDraft } from "./timelineRangeSelection.js";
|
import { createRangeSelectionState, finalizeRangeSelection, updateRangeSelectionDraft } from "./timelineRangeSelection.js";
|
||||||
|
|
||||||
type TestRangeState = {
|
type TestRangeState = {
|
||||||
isSelecting: boolean;
|
isSelecting: boolean;
|
||||||
@@ -11,6 +11,23 @@ type TestRangeState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe("timelineRangeSelection", () => {
|
describe("timelineRangeSelection", () => {
|
||||||
|
it("creates a selection draft with currentDate anchored to the start date", () => {
|
||||||
|
expect(
|
||||||
|
createRangeSelectionState<TestRangeState>(
|
||||||
|
"res-1",
|
||||||
|
new Date("2025-01-15T12:00:00.000Z"),
|
||||||
|
100,
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
isSelecting: true,
|
||||||
|
resourceId: "res-1",
|
||||||
|
startDate: new Date("2025-01-15T12:00:00.000Z"),
|
||||||
|
currentDate: new Date("2025-01-15T12:00:00.000Z"),
|
||||||
|
suggestedProjectId: null,
|
||||||
|
startClientX: 100,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("ignores updates when no full day boundary was crossed", () => {
|
it("ignores updates when no full day boundary was crossed", () => {
|
||||||
const state: TestRangeState = {
|
const state: TestRangeState = {
|
||||||
isSelecting: true,
|
isSelecting: true,
|
||||||
|
|||||||
@@ -18,6 +18,22 @@ export type RangeSelectionResult = {
|
|||||||
anchorY: number;
|
anchorY: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function createRangeSelectionState<TState extends RangeStateLike>(
|
||||||
|
resourceId: string,
|
||||||
|
startDate: Date,
|
||||||
|
startClientX: number,
|
||||||
|
suggestedProjectId?: string | null,
|
||||||
|
): TState {
|
||||||
|
return {
|
||||||
|
isSelecting: true,
|
||||||
|
resourceId,
|
||||||
|
startDate,
|
||||||
|
currentDate: startDate,
|
||||||
|
suggestedProjectId: suggestedProjectId ?? null,
|
||||||
|
startClientX,
|
||||||
|
} as TState;
|
||||||
|
}
|
||||||
|
|
||||||
export function updateRangeSelectionDraft<TState extends RangeStateLike>(
|
export function updateRangeSelectionDraft<TState extends RangeStateLike>(
|
||||||
state: TState,
|
state: TState,
|
||||||
currentClientX: number,
|
currentClientX: number,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
updateMultiSelectDraft,
|
updateMultiSelectDraft,
|
||||||
} from "./timelineMultiSelect.js";
|
} from "./timelineMultiSelect.js";
|
||||||
import { reconcileOptimisticEntries } from "./timelineOptimisticAllocations.js";
|
import { reconcileOptimisticEntries } from "./timelineOptimisticAllocations.js";
|
||||||
import { finalizeRangeSelection, updateRangeSelectionDraft } from "./timelineRangeSelection.js";
|
import { createRangeSelectionState, finalizeRangeSelection, updateRangeSelectionDraft } from "./timelineRangeSelection.js";
|
||||||
import { getTouchPoint, resolveTouchDragDecision } from "./timelineTouch.js";
|
import { getTouchPoint, resolveTouchDragDecision } from "./timelineTouch.js";
|
||||||
|
|
||||||
const DRAG_CLICK_THRESHOLD_PX = 5;
|
const DRAG_CLICK_THRESHOLD_PX = 5;
|
||||||
@@ -826,14 +826,12 @@ export function useTimelineDrag({
|
|||||||
if (dragStateRef.current.isDragging || allocDragRef.current.isActive) return;
|
if (dragStateRef.current.isDragging || allocDragRef.current.isActive) return;
|
||||||
if (e.button !== 0) return;
|
if (e.button !== 0) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const state: RangeState = {
|
const state = createRangeSelectionState<RangeState>(
|
||||||
isSelecting: true,
|
opts.resourceId,
|
||||||
resourceId: opts.resourceId,
|
opts.startDate,
|
||||||
startDate: opts.startDate,
|
e.clientX,
|
||||||
currentDate: opts.startDate,
|
opts.suggestedProjectId,
|
||||||
suggestedProjectId: opts.suggestedProjectId ?? null,
|
);
|
||||||
startClientX: e.clientX,
|
|
||||||
};
|
|
||||||
rangeStateRef.current = state;
|
rangeStateRef.current = state;
|
||||||
setRangeState(state);
|
setRangeState(state);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -153,6 +153,10 @@ export const rules = [
|
|||||||
file: "apps/web/src/hooks/timelineRangeSelection.ts",
|
file: "apps/web/src/hooks/timelineRangeSelection.ts",
|
||||||
maxLines: 90,
|
maxLines: 90,
|
||||||
required: [
|
required: [
|
||||||
|
{
|
||||||
|
pattern: /\bexport function createRangeSelectionState\b/,
|
||||||
|
message: "timeline range helpers must keep selection bootstrap centralized",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pattern: /\bexport function updateRangeSelectionDraft\b/,
|
pattern: /\bexport function updateRangeSelectionDraft\b/,
|
||||||
message: "timeline range helpers must keep preview date derivation centralized",
|
message: "timeline range helpers must keep preview date derivation centralized",
|
||||||
@@ -304,7 +308,7 @@ export const rules = [
|
|||||||
],
|
],
|
||||||
forbidden: [
|
forbidden: [
|
||||||
{
|
{
|
||||||
pattern: /\bfunction (?:toPxValue|joinTransforms|captureLivePreviewTargets|renderLivePreview|scheduleLivePreview|clearLivePreview|datesMatch|preserveLivePreview)\b/,
|
pattern: /\bfunction (?:toPxValue|joinTransforms|captureLivePreviewTargets|renderLivePreview|scheduleLivePreview|clearLivePreview|datesMatch|preserveLivePreview|createRangeSelectionState)\b/,
|
||||||
message: "timeline drag must not re-inline live preview helper implementations",
|
message: "timeline drag must not re-inline live preview helper implementations",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ describe("architecture guardrails", () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
assert.deepEqual(evaluateRule(rangeRule, "export function updateRangeSelectionDraft() {}\n"), [
|
assert.deepEqual(evaluateRule(rangeRule, "export function updateRangeSelectionDraft() {}\n"), [
|
||||||
|
"apps/web/src/hooks/timelineRangeSelection.ts: missing guardrail anchor: timeline range helpers must keep selection bootstrap centralized",
|
||||||
"apps/web/src/hooks/timelineRangeSelection.ts: missing guardrail anchor: timeline range helpers must keep ordered range finalization centralized",
|
"apps/web/src/hooks/timelineRangeSelection.ts: missing guardrail anchor: timeline range helpers must keep ordered range finalization centralized",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user