import type { SchedulerEvent } from '../data/scheduler.types'; import type { SchedulerModule, SchedulerRuntime } from '../scheduler-runtime'; import { type BarSource } from './event-selection-service'; /** * The slice of {@link EventSelectionService} this service drives. * * Structural, like {@link BarSource}, and for the same reason: keyboard * navigation needs to *ask* for selection changes, not own them, and typing the * dependency this narrowly means the two services can be constructed in either * order and tested apart. The real service satisfies it without declaring that * it does. */ export interface SchedulerSelectionPort { /** Replaces (or extends) the selection. */ select(ids: string[], additive?: boolean): void; /** Clears the selection. */ clear(): void; /** The currently selected events. */ getSelected(): readonly SchedulerEvent[]; } /** Payload of {@link SchedulerEventName.BeforeDelete} and `AfterDelete`. */ export interface SchedulerDeletePayload { readonly event: SchedulerEvent; } /** * Keyboard navigation, selection and deletion over event bars. * * Bars carry `tabindex=0`, so the browser's own focus model does the hard part; * this service only translates keys into focus moves and selection calls. Every * handled key calls `preventDefault`, which is what stops Space from scrolling * the page and the arrows from scrolling the grid out from under the focused * bar. * * ## Two different notions of "next" * * Horizontal movement is a **model** question -- the next event on this resource * by start time -- and is answered through the event index, bounded by the * timeline's own range. It therefore reaches events that are scrolled out of * view, which is what makes arrowing along a resource usable. * * Vertical movement is a **visual** question -- the adjacent row -- and rows only * exist in the rendered placements, in the grid's row order. So Up/Down walk the * resources that have bars in the current frame. The consequence, deliberate and * worth knowing: a resource with no events in view is skipped rather than * focused, because there is nothing on it to focus. */ export declare class SchedulerKeyboardService implements SchedulerModule { private readonly runtime; private readonly abort; private readonly bars; private readonly selection; /** Reused by index queries so a held-down arrow key allocates nothing. */ private readonly queryScratch; /** * @param runtime - Shared scheduler state; the index is read for navigation * and written by deletion. * @param layerEl - Layer holding the bars. The listener is delegated here, so * it only ever sees keys pressed while focus is inside the scheduler and * cannot hijack typing anywhere else in the grid. * @param bars - Renderer accessor, used to move focus and to read row order. * @param selection - Selection service, typed as {@link SchedulerSelectionPort}. */ constructor(runtime: SchedulerRuntime, layerEl: HTMLElement, bars: BarSource, selection: SchedulerSelectionPort); /** Moves DOM focus to an event's bar, when that bar is currently rendered. */ focusEvent(id: string): boolean; /** Removes the listener. Focus is left wherever the user put it. */ destroy(): void; private readonly onKeyDown; /** * The previous or next event on the same resource, by start time. * * With no bar focused this falls back to the first bar of the frame, so a * user who tabs into the layer and presses an arrow lands somewhere sensible * instead of nowhere. * * The index query is bounded by the timeline's own range rather than by * infinity: an event outside the current range cannot be scrolled to without * changing the view, so focusing it would move focus somewhere invisible. * * Ties on `start` are broken by id, which keeps the traversal total -- * otherwise two events starting at the same instant would trap the focus * bouncing between them. */ private horizontal; /** * The nearest event on the adjacent rendered resource. * * "Nearest" is measured on `start`, not on pixels, so the choice is stable * across zoom levels and view changes -- two bars that look equally close in a * year view are not equally close in an hour view, but their start times rank * the same either way. */ private vertical; /** First bar of the current frame, used as the entry point for keyboard navigation. */ private firstRenderedEventId; /** * Toggles one event in the selection. * * Expressed through {@link SchedulerSelectionPort.select} rather than a * dedicated `toggle`, so the port stays at three members and the selection * service remains the only writer of the selection set -- including its * single/multiple mode enforcement and its change notification. */ private toggle; /** * Deletes the selected events. * * Gated on `drag.managed` because that flag is the scheduler's single answer * to "may this component mutate the data?". An unmanaged host owns * persistence, and silently dropping rows out of its index would desynchronise * it from its own store. * * Vetoed events survive and stay selected, so a partial rejection leaves the * user looking at exactly what was refused. */ private deleteSelected; } //# sourceMappingURL=scheduler-keyboard-service.d.ts.map