fix(02): wire calendar-controls plugin and Zustand selectors
Bug A — navigation no-op: replace $app.calendarState private-API poking with the official @schedule-x/calendar-controls plugin. CalendarShell creates the plugin once via useState stable initialiser and passes it to ViewToolbar as `controls`. ViewToolbar calls controls.setDate(PlainDate) and controls.setView(id) for all navigation and view-switching. Step size matches the active view: day→±1 day, week→±1 week, month-*→±1 month. Bug B — popover-open calendar flash: replace the unselected useCalendarStore() destructuring in CalendarShell and ViewToolbar with per-field selectors. Neither component now subscribes to openEventId, so popover open/close no longer triggers a re-render that rebuilds the Schedule-X config. - Add @schedule-x/calendar-controls@4.6.0 dependency - Update CalendarShell.test.tsx: add vi.mock for calendar-controls - typecheck, vitest (37/37), build all pass
This commit is contained in:
@@ -37,6 +37,7 @@ import {
|
||||
type CalendarType,
|
||||
} from '@schedule-x/calendar'
|
||||
import { createEventsServicePlugin } from '@schedule-x/events-service'
|
||||
import { createCalendarControlsPlugin } from '@schedule-x/calendar-controls'
|
||||
|
||||
import { fetchMe, fetchEvents } from '../api/client.js'
|
||||
import { hydrateEvents } from '../lib/hydrateEvents.js'
|
||||
@@ -67,7 +68,13 @@ function isPhone(): boolean {
|
||||
// ── Component ──────────────────────────────────────────────────────────────
|
||||
|
||||
export function CalendarShell() {
|
||||
const { calendarRange, setCalendarRange, setOpenEventId, selectedView } = useCalendarStore()
|
||||
// Use per-field selectors so CalendarShell does NOT subscribe to openEventId.
|
||||
// Without selectors, any popover open/close triggers a full re-render here,
|
||||
// which rebuilds the Schedule-X config and causes a visible calendar flash (Bug B).
|
||||
const calendarRange = useCalendarStore((s) => s.calendarRange)
|
||||
const setCalendarRange = useCalendarStore((s) => s.setCalendarRange)
|
||||
const setOpenEventId = useCalendarStore((s) => s.setOpenEventId)
|
||||
const selectedView = useCalendarStore((s) => s.selectedView)
|
||||
const { start, end } = calendarRange
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@@ -89,6 +96,7 @@ export function CalendarShell() {
|
||||
|
||||
// Create plugins once (stable across renders)
|
||||
const eventsService = useState(() => createEventsServicePlugin())[0]
|
||||
const calendarControls = useState(() => createCalendarControlsPlugin())[0]
|
||||
|
||||
// Build members list from /api/me for AppNav + ColorLegend
|
||||
const members = useMemo(() => {
|
||||
@@ -140,7 +148,7 @@ export function CalendarShell() {
|
||||
},
|
||||
},
|
||||
},
|
||||
[eventsService],
|
||||
[eventsService, calendarControls],
|
||||
)
|
||||
|
||||
// Sync TanStack Query result into Schedule-X eventsService (Pitfall 4 guard)
|
||||
@@ -198,7 +206,7 @@ export function CalendarShell() {
|
||||
}}
|
||||
>
|
||||
{/* ViewToolbar */}
|
||||
<ViewToolbar calendarApp={calendar} />
|
||||
<ViewToolbar controls={calendarControls} />
|
||||
|
||||
{/* Main calendar area */}
|
||||
<div style={{ flex: 1, minHeight: 0, position: 'relative', overflow: 'hidden' }}>
|
||||
|
||||
Reference in New Issue
Block a user