cli-menu-kit 0.1.23 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/api.d.ts +23 -5
  2. package/dist/api.js +16 -4
  3. package/dist/component-factories.d.ts +59 -0
  4. package/dist/component-factories.js +141 -0
  5. package/dist/components/display/header-v2.d.ts +13 -0
  6. package/dist/components/display/header-v2.js +43 -0
  7. package/dist/components/display/hints-v2.d.ts +10 -0
  8. package/dist/components/display/hints-v2.js +34 -0
  9. package/dist/components/display/hints.d.ts +56 -0
  10. package/dist/components/display/hints.js +81 -0
  11. package/dist/components/display/index.d.ts +3 -0
  12. package/dist/components/display/index.js +15 -1
  13. package/dist/components/display/input-prompt.d.ts +35 -0
  14. package/dist/components/display/input-prompt.js +36 -0
  15. package/dist/components/display/list.d.ts +49 -0
  16. package/dist/components/display/list.js +86 -0
  17. package/dist/components/display/summary.js +119 -15
  18. package/dist/components/display/table.d.ts +42 -0
  19. package/dist/components/display/table.js +107 -0
  20. package/dist/components/menus/boolean-menu.js +2 -1
  21. package/dist/components/menus/checkbox-menu.d.ts +2 -1
  22. package/dist/components/menus/checkbox-menu.js +30 -59
  23. package/dist/components/menus/checkbox-table-menu.d.ts +12 -0
  24. package/dist/components/menus/checkbox-table-menu.js +395 -0
  25. package/dist/components/menus/index.d.ts +1 -0
  26. package/dist/components/menus/index.js +3 -1
  27. package/dist/components/menus/radio-menu-split.d.ts +33 -0
  28. package/dist/components/menus/radio-menu-split.js +248 -0
  29. package/dist/components/menus/radio-menu-v2.d.ts +11 -0
  30. package/dist/components/menus/radio-menu-v2.js +150 -0
  31. package/dist/components/menus/radio-menu.d.ts +2 -1
  32. package/dist/components/menus/radio-menu.js +60 -123
  33. package/dist/core/hint-manager.d.ts +29 -0
  34. package/dist/core/hint-manager.js +65 -0
  35. package/dist/core/renderer.d.ts +2 -1
  36. package/dist/core/renderer.js +22 -6
  37. package/dist/core/screen-manager.d.ts +54 -0
  38. package/dist/core/screen-manager.js +119 -0
  39. package/dist/core/state-manager.d.ts +27 -0
  40. package/dist/core/state-manager.js +56 -0
  41. package/dist/core/terminal.d.ts +4 -1
  42. package/dist/core/terminal.js +37 -4
  43. package/dist/core/virtual-scroll.d.ts +65 -0
  44. package/dist/core/virtual-scroll.js +120 -0
  45. package/dist/i18n/languages/en.js +4 -1
  46. package/dist/i18n/languages/zh.js +4 -1
  47. package/dist/i18n/registry.d.ts +4 -3
  48. package/dist/i18n/registry.js +12 -4
  49. package/dist/i18n/types.d.ts +3 -0
  50. package/dist/index.d.ts +5 -4
  51. package/dist/index.js +30 -4
  52. package/dist/layout.d.ts +68 -0
  53. package/dist/layout.js +134 -0
  54. package/dist/page-layout.d.ts +92 -0
  55. package/dist/page-layout.js +156 -0
  56. package/dist/types/display.types.d.ts +6 -0
  57. package/dist/types/menu.types.d.ts +57 -5
  58. package/package.json +1 -1
  59. package/dist/types/layout.types.d.ts +0 -56
  60. package/dist/types/layout.types.js +0 -36
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Hint Manager - Manages hints from multiple components with priority
3
+ *
4
+ * Allows multiple components to set hints with different priorities.
5
+ * The highest priority hint is displayed.
6
+ */
7
+ import { EventEmitter } from 'events';
8
+ export declare class HintManager extends EventEmitter {
9
+ private entries;
10
+ /**
11
+ * Set a hint with a token and priority
12
+ * @param token - Unique identifier for this hint source
13
+ * @param text - Hint text to display
14
+ * @param priority - Higher priority hints are displayed first (default: 0)
15
+ */
16
+ set(token: string, text: string, priority?: number): void;
17
+ /**
18
+ * Clear a hint by token
19
+ */
20
+ clear(token: string): void;
21
+ /**
22
+ * Get the current highest priority hint
23
+ */
24
+ current(): string;
25
+ /**
26
+ * Clear all hints
27
+ */
28
+ clearAll(): void;
29
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /**
3
+ * Hint Manager - Manages hints from multiple components with priority
4
+ *
5
+ * Allows multiple components to set hints with different priorities.
6
+ * The highest priority hint is displayed.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.HintManager = void 0;
10
+ const events_1 = require("events");
11
+ class HintManager extends events_1.EventEmitter {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.entries = new Map();
15
+ }
16
+ /**
17
+ * Set a hint with a token and priority
18
+ * @param token - Unique identifier for this hint source
19
+ * @param text - Hint text to display
20
+ * @param priority - Higher priority hints are displayed first (default: 0)
21
+ */
22
+ set(token, text, priority = 0) {
23
+ this.entries.set(token, {
24
+ text,
25
+ priority,
26
+ timestamp: Date.now()
27
+ });
28
+ this.emit('change', this.current());
29
+ }
30
+ /**
31
+ * Clear a hint by token
32
+ */
33
+ clear(token) {
34
+ if (this.entries.delete(token)) {
35
+ this.emit('change', this.current());
36
+ }
37
+ }
38
+ /**
39
+ * Get the current highest priority hint
40
+ */
41
+ current() {
42
+ const list = Array.from(this.entries.values());
43
+ if (list.length === 0) {
44
+ return '';
45
+ }
46
+ // Sort by priority (descending), then by timestamp (most recent first)
47
+ list.sort((a, b) => {
48
+ if (b.priority !== a.priority) {
49
+ return b.priority - a.priority;
50
+ }
51
+ return b.timestamp - a.timestamp;
52
+ });
53
+ return list[0].text;
54
+ }
55
+ /**
56
+ * Clear all hints
57
+ */
58
+ clearAll() {
59
+ if (this.entries.size > 0) {
60
+ this.entries.clear();
61
+ this.emit('change', '');
62
+ }
63
+ }
64
+ }
65
+ exports.HintManager = HintManager;
@@ -43,8 +43,9 @@ export declare function renderSeparator(char?: string, width?: number): void;
43
43
  * Render a section label (menu grouping)
44
44
  * @param label - Label text (optional)
45
45
  * @param width - Total width of the separator (default: 30)
46
+ * @param align - Alignment of the label (default: 'center')
46
47
  */
47
- export declare function renderSectionLabel(label?: string, width?: number): void;
48
+ export declare function renderSectionLabel(label?: string, width?: number, align?: 'left' | 'center' | 'right'): void;
48
49
  /**
49
50
  * Render a message with icon
50
51
  * @param type - Message type (success, error, warning, info, question)
@@ -135,17 +135,33 @@ function renderSeparator(char = '─', width) {
135
135
  * Render a section label (menu grouping)
136
136
  * @param label - Label text (optional)
137
137
  * @param width - Total width of the separator (default: 30)
138
+ * @param align - Alignment of the label (default: 'center')
138
139
  */
139
- function renderSectionLabel(label, width = 30) {
140
+ function renderSectionLabel(label, width = 30, align = 'center') {
140
141
  if (label) {
141
- const totalWidth = width; // Use configured width
142
- const padding = 2; // Spaces around label
142
+ const totalWidth = width;
143
143
  const labelWithPadding = ` ${label} `;
144
144
  const labelLength = labelWithPadding.length;
145
145
  const dashesTotal = totalWidth - labelLength;
146
- const dashesLeft = Math.floor(dashesTotal / 2);
147
- const dashesRight = dashesTotal - dashesLeft;
148
- const line = ` ${colors_js_1.uiColors.separator}${'─'.repeat(dashesLeft)}${labelWithPadding}${'─'.repeat(dashesRight)}${colors_js_1.colors.reset}`;
146
+ let dashesLeft;
147
+ let dashesRight;
148
+ switch (align) {
149
+ case 'left':
150
+ dashesLeft = 0;
151
+ dashesRight = dashesTotal;
152
+ break;
153
+ case 'right':
154
+ dashesLeft = dashesTotal;
155
+ dashesRight = 0;
156
+ break;
157
+ case 'center':
158
+ default:
159
+ dashesLeft = Math.floor(dashesTotal / 2);
160
+ dashesRight = dashesTotal - dashesLeft;
161
+ break;
162
+ }
163
+ // Use primary color (cyan) and bold for phase labels
164
+ const line = ` ${colors_js_1.colors.cyan}${colors_js_1.colors.bold}${'─'.repeat(dashesLeft)}${labelWithPadding}${'─'.repeat(dashesRight)}${colors_js_1.colors.reset}`;
149
165
  (0, terminal_js_1.writeLine)(line);
150
166
  }
151
167
  else {
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Screen Manager - Manages screen regions with fixed-height layout
3
+ *
4
+ * Uses absolute cursor positioning and per-region caching for efficient updates.
5
+ * Only updates regions that have changed (diff-based rendering).
6
+ */
7
+ export interface Rect {
8
+ top: number;
9
+ left: number;
10
+ width: number;
11
+ height: number;
12
+ }
13
+ export declare class ScreenManager {
14
+ private cache;
15
+ private regions;
16
+ private isAltScreen;
17
+ /**
18
+ * Enter alternate screen buffer and hide cursor
19
+ */
20
+ enter(): void;
21
+ /**
22
+ * Exit alternate screen buffer and show cursor
23
+ */
24
+ exit(): void;
25
+ /**
26
+ * Move cursor to absolute position (1-based)
27
+ */
28
+ moveTo(row: number, col: number): void;
29
+ /**
30
+ * Register a fixed-height region
31
+ */
32
+ registerRegion(id: string, rect: Rect): void;
33
+ /**
34
+ * Render a region with diff-based updates
35
+ * Only updates lines that have changed
36
+ */
37
+ renderRegion(id: string, lines: string[]): void;
38
+ /**
39
+ * Clear a region (fill with spaces)
40
+ */
41
+ clearRegion(id: string): void;
42
+ /**
43
+ * Invalidate all cached content (forces full re-render on next update)
44
+ */
45
+ invalidateAll(): void;
46
+ /**
47
+ * Reset manager state
48
+ */
49
+ reset(): void;
50
+ /**
51
+ * Get region rect
52
+ */
53
+ getRegion(id: string): Rect | undefined;
54
+ }
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ /**
3
+ * Screen Manager - Manages screen regions with fixed-height layout
4
+ *
5
+ * Uses absolute cursor positioning and per-region caching for efficient updates.
6
+ * Only updates regions that have changed (diff-based rendering).
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.ScreenManager = void 0;
10
+ const CSI = '\x1b[';
11
+ /**
12
+ * Fit text to exact width by padding or truncating
13
+ */
14
+ function fitText(text, width) {
15
+ // Simple implementation - can be enhanced with ANSI-aware width calculation
16
+ const stripped = text.replace(/\x1b\[[0-9;]*m/g, ''); // Strip ANSI codes for length calc
17
+ const len = stripped.length;
18
+ if (len === width)
19
+ return text;
20
+ if (len < width)
21
+ return text + ' '.repeat(width - len);
22
+ // Truncate - preserve ANSI codes at start if present
23
+ const ansiMatch = text.match(/^(\x1b\[[0-9;]*m)*/);
24
+ const prefix = ansiMatch ? ansiMatch[0] : '';
25
+ const content = text.slice(prefix.length);
26
+ return prefix + content.slice(0, width);
27
+ }
28
+ class ScreenManager {
29
+ constructor() {
30
+ this.cache = new Map();
31
+ this.regions = new Map();
32
+ this.isAltScreen = false;
33
+ }
34
+ /**
35
+ * Enter alternate screen buffer and hide cursor
36
+ */
37
+ enter() {
38
+ if (!this.isAltScreen) {
39
+ process.stdout.write(`${CSI}?1049h${CSI}?25l`);
40
+ this.isAltScreen = true;
41
+ }
42
+ }
43
+ /**
44
+ * Exit alternate screen buffer and show cursor
45
+ */
46
+ exit() {
47
+ if (this.isAltScreen) {
48
+ process.stdout.write(`${CSI}?25h${CSI}?1049l`);
49
+ this.isAltScreen = false;
50
+ }
51
+ }
52
+ /**
53
+ * Move cursor to absolute position (1-based)
54
+ */
55
+ moveTo(row, col) {
56
+ process.stdout.write(`${CSI}${row};${col}H`);
57
+ }
58
+ /**
59
+ * Register a fixed-height region
60
+ */
61
+ registerRegion(id, rect) {
62
+ this.regions.set(id, rect);
63
+ }
64
+ /**
65
+ * Render a region with diff-based updates
66
+ * Only updates lines that have changed
67
+ */
68
+ renderRegion(id, lines) {
69
+ const rect = this.regions.get(id);
70
+ if (!rect) {
71
+ throw new Error(`Region ${id} not registered`);
72
+ }
73
+ const prev = this.cache.get(id) ?? [];
74
+ const next = Array.from({ length: rect.height }, (_, i) => fitText(lines[i] ?? '', rect.width));
75
+ // Update only changed lines
76
+ for (let i = 0; i < rect.height; i++) {
77
+ if (next[i] === prev[i])
78
+ continue;
79
+ this.moveTo(rect.top + i, rect.left);
80
+ process.stdout.write(next[i]);
81
+ }
82
+ this.cache.set(id, next);
83
+ }
84
+ /**
85
+ * Clear a region (fill with spaces)
86
+ */
87
+ clearRegion(id) {
88
+ const rect = this.regions.get(id);
89
+ if (!rect) {
90
+ throw new Error(`Region ${id} not registered`);
91
+ }
92
+ const blank = ' '.repeat(rect.width);
93
+ for (let i = 0; i < rect.height; i++) {
94
+ this.moveTo(rect.top + i, rect.left);
95
+ process.stdout.write(blank);
96
+ }
97
+ this.cache.set(id, Array(rect.height).fill(blank));
98
+ }
99
+ /**
100
+ * Invalidate all cached content (forces full re-render on next update)
101
+ */
102
+ invalidateAll() {
103
+ this.cache.clear();
104
+ }
105
+ /**
106
+ * Reset manager state
107
+ */
108
+ reset() {
109
+ this.cache.clear();
110
+ this.regions.clear();
111
+ }
112
+ /**
113
+ * Get region rect
114
+ */
115
+ getRegion(id) {
116
+ return this.regions.get(id);
117
+ }
118
+ }
119
+ exports.ScreenManager = ScreenManager;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Shared State Manager
3
+ * Allows components to share state without direct coupling
4
+ */
5
+ type StateListener<T> = (value: T) => void;
6
+ export declare class StateManager {
7
+ private states;
8
+ private listeners;
9
+ /**
10
+ * Set a state value and notify listeners
11
+ */
12
+ setState<T>(key: string, value: T): void;
13
+ /**
14
+ * Get a state value
15
+ */
16
+ getState<T>(key: string): T | undefined;
17
+ /**
18
+ * Subscribe to state changes
19
+ */
20
+ subscribe<T>(key: string, listener: StateListener<T>): () => void;
21
+ /**
22
+ * Clear all state
23
+ */
24
+ clear(): void;
25
+ }
26
+ export declare const globalState: StateManager;
27
+ export {};
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ /**
3
+ * Shared State Manager
4
+ * Allows components to share state without direct coupling
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.globalState = exports.StateManager = void 0;
8
+ class StateManager {
9
+ constructor() {
10
+ this.states = new Map();
11
+ this.listeners = new Map();
12
+ }
13
+ /**
14
+ * Set a state value and notify listeners
15
+ */
16
+ setState(key, value) {
17
+ this.states.set(key, value);
18
+ // Notify all listeners
19
+ const listeners = this.listeners.get(key);
20
+ if (listeners) {
21
+ listeners.forEach(listener => listener(value));
22
+ }
23
+ }
24
+ /**
25
+ * Get a state value
26
+ */
27
+ getState(key) {
28
+ return this.states.get(key);
29
+ }
30
+ /**
31
+ * Subscribe to state changes
32
+ */
33
+ subscribe(key, listener) {
34
+ if (!this.listeners.has(key)) {
35
+ this.listeners.set(key, new Set());
36
+ }
37
+ this.listeners.get(key).add(listener);
38
+ // Return unsubscribe function
39
+ return () => {
40
+ const listeners = this.listeners.get(key);
41
+ if (listeners) {
42
+ listeners.delete(listener);
43
+ }
44
+ };
45
+ }
46
+ /**
47
+ * Clear all state
48
+ */
49
+ clear() {
50
+ this.states.clear();
51
+ this.listeners.clear();
52
+ }
53
+ }
54
+ exports.StateManager = StateManager;
55
+ // Global state manager instance
56
+ exports.globalState = new StateManager();
@@ -9,12 +9,14 @@ export interface TerminalState {
9
9
  stdin: NodeJS.ReadStream;
10
10
  renderedLines: number;
11
11
  isRawMode: boolean;
12
+ useAltScreen: boolean;
12
13
  }
13
14
  /**
14
15
  * Initialize terminal for interactive mode
16
+ * @param useAltScreen - Whether to use alternate screen buffer (prevents scroll issues)
15
17
  * @returns Terminal state object
16
18
  */
17
- export declare function initTerminal(): TerminalState;
19
+ export declare function initTerminal(useAltScreen?: boolean): TerminalState;
18
20
  /**
19
21
  * Restore terminal to normal mode
20
22
  * @param state - Terminal state
@@ -22,6 +24,7 @@ export declare function initTerminal(): TerminalState;
22
24
  export declare function restoreTerminal(state: TerminalState): void;
23
25
  /**
24
26
  * Clear the current menu display
27
+ * Only clears the lines that were rendered by this menu
25
28
  * @param state - Terminal state
26
29
  */
27
30
  export declare function clearMenu(state: TerminalState): void;
@@ -22,20 +22,36 @@ exports.clearScreen = clearScreen;
22
22
  exports.exitWithGoodbye = exitWithGoodbye;
23
23
  /**
24
24
  * Initialize terminal for interactive mode
25
+ * @param useAltScreen - Whether to use alternate screen buffer (prevents scroll issues)
25
26
  * @returns Terminal state object
26
27
  */
27
- function initTerminal() {
28
+ function initTerminal(useAltScreen = false) {
28
29
  const stdin = process.stdin;
30
+ // Disable all mouse tracking modes BEFORE enabling raw mode
31
+ process.stdout.write('\x1b[?1000l'); // Disable normal mouse tracking
32
+ process.stdout.write('\x1b[?1001l'); // Disable highlight mouse tracking
33
+ process.stdout.write('\x1b[?1002l'); // Disable button event tracking
34
+ process.stdout.write('\x1b[?1003l'); // Disable any event tracking
35
+ process.stdout.write('\x1b[?1004l'); // Disable focus events
36
+ process.stdout.write('\x1b[?1005l'); // Disable UTF-8 mouse mode
37
+ process.stdout.write('\x1b[?1006l'); // Disable SGR extended mouse mode
38
+ process.stdout.write('\x1b[?1015l'); // Disable urxvt mouse mode
29
39
  // Enable raw mode for character-by-character input
30
40
  stdin.setRawMode(true);
31
41
  stdin.resume();
32
42
  stdin.setEncoding('utf8');
43
+ // Use alternate screen buffer if requested
44
+ if (useAltScreen) {
45
+ process.stdout.write('\x1b[?1049h'); // Enable alternate screen
46
+ process.stdout.write('\x1b[H'); // Move cursor to home
47
+ }
33
48
  // Hide cursor
34
49
  process.stdout.write('\x1b[?25l');
35
50
  return {
36
51
  stdin,
37
52
  renderedLines: 0,
38
- isRawMode: true
53
+ isRawMode: true,
54
+ useAltScreen
39
55
  };
40
56
  }
41
57
  /**
@@ -47,20 +63,37 @@ function restoreTerminal(state) {
47
63
  state.stdin.setRawMode(false);
48
64
  state.isRawMode = false;
49
65
  }
66
+ // Restore alternate screen if it was used
67
+ if (state.useAltScreen) {
68
+ process.stdout.write('\x1b[?1049l'); // Disable alternate screen
69
+ }
50
70
  // Show cursor
51
71
  process.stdout.write('\x1b[?25h');
52
72
  state.stdin.pause();
53
73
  }
54
74
  /**
55
75
  * Clear the current menu display
76
+ * Only clears the lines that were rendered by this menu
56
77
  * @param state - Terminal state
57
78
  */
58
79
  function clearMenu(state) {
59
80
  if (state.renderedLines > 0) {
60
81
  // Move cursor up to the start of the menu
61
82
  process.stdout.write(`\x1b[${state.renderedLines}A`);
62
- // Clear from cursor to end of screen
63
- process.stdout.write('\x1b[J');
83
+ // Clear each line individually
84
+ for (let i = 0; i < state.renderedLines; i++) {
85
+ process.stdout.write('\x1b[2K'); // Clear entire line
86
+ if (i < state.renderedLines - 1) {
87
+ process.stdout.write('\x1b[1B'); // Move down one line
88
+ }
89
+ }
90
+ // Move cursor back to start position
91
+ // After loop, cursor is at the last rendered line
92
+ // To get back to line 1, move up (renderedLines - 1)
93
+ // Note: \x1b[0A defaults to 1 in ANSI spec, so skip when renderedLines === 1
94
+ if (state.renderedLines > 1) {
95
+ process.stdout.write(`\x1b[${state.renderedLines - 1}A`);
96
+ }
64
97
  state.renderedLines = 0;
65
98
  }
66
99
  }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Virtual scrolling utilities for rendering large lists efficiently
3
+ * by only displaying a visible window of items.
4
+ */
5
+ export interface VirtualScrollOptions<T> {
6
+ /** All items in the list */
7
+ items: T[];
8
+ /** Current cursor/focus position (index in items array) */
9
+ cursorIndex: number;
10
+ /** Target number of lines to display */
11
+ targetLines: number;
12
+ /** Function to calculate how many lines each item will occupy when rendered */
13
+ getItemLineCount: (item: T, index: number) => number;
14
+ }
15
+ export interface VirtualScrollResult {
16
+ /** Start index of visible range (inclusive) */
17
+ visibleStart: number;
18
+ /** End index of visible range (exclusive) */
19
+ visibleEnd: number;
20
+ /** Actual number of lines that will be rendered */
21
+ actualLines: number;
22
+ /** Whether virtual scrolling is active (content exceeds target) */
23
+ isScrolled: boolean;
24
+ /** Whether there are items before the visible range */
25
+ hasItemsBefore: boolean;
26
+ /** Whether there are items after the visible range */
27
+ hasItemsAfter: boolean;
28
+ }
29
+ /**
30
+ * Calculate visible range for virtual scrolling based on line count.
31
+ *
32
+ * This function maintains a stable viewport height by calculating which items
33
+ * to display based on their actual line count, not just item count. This prevents
34
+ * height jumping when items have varying heights (e.g., separators with descriptions).
35
+ *
36
+ * Algorithm:
37
+ * 1. Calculate total lines needed for all items
38
+ * 2. If total <= targetLines, show everything (no scrolling)
39
+ * 3. Otherwise, create a window centered on cursor:
40
+ * - Start from cursor position
41
+ * - Expand downward until reaching target or end
42
+ * - Expand upward to fill remaining space
43
+ * - Expand downward again if space remains
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const result = calculateVirtualScroll({
48
+ * items: menuOptions,
49
+ * cursorIndex: 10,
50
+ * targetLines: 30,
51
+ * getItemLineCount: (item, index) => {
52
+ * if (item.type === 'separator') {
53
+ * return 1 + (item.description ? 1 : 0) + (index > 0 ? 1 : 0);
54
+ * }
55
+ * return 1;
56
+ * }
57
+ * });
58
+ *
59
+ * // Render only visible items
60
+ * for (let i = result.visibleStart; i < result.visibleEnd; i++) {
61
+ * renderItem(items[i]);
62
+ * }
63
+ * ```
64
+ */
65
+ export declare function calculateVirtualScroll<T>(options: VirtualScrollOptions<T>): VirtualScrollResult;