@anymux/ui-kit 0.1.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 (244) hide show
  1. package/dist/ExplorerLayout-CSIJd7N4.js +105 -0
  2. package/dist/ExplorerLayout-CSIJd7N4.js.map +1 -0
  3. package/dist/FileBrowserContext-B6jixa2j.js +11 -0
  4. package/dist/FileBrowserContext-B6jixa2j.js.map +1 -0
  5. package/dist/calendar-DSlrbHoj.js +761 -0
  6. package/dist/calendar-DSlrbHoj.js.map +1 -0
  7. package/dist/calendar.d.ts +3 -0
  8. package/dist/calendar.js +3 -0
  9. package/dist/contacts-DQXTZzHc.js +539 -0
  10. package/dist/contacts-DQXTZzHc.js.map +1 -0
  11. package/dist/contacts.d.ts +3 -0
  12. package/dist/contacts.js +3 -0
  13. package/dist/file-browser-m5atC3kF.js +6755 -0
  14. package/dist/file-browser-m5atC3kF.js.map +1 -0
  15. package/dist/file-browser.d.ts +11 -0
  16. package/dist/file-browser.js +9 -0
  17. package/dist/git-B55e6LL-.js +561 -0
  18. package/dist/git-B55e6LL-.js.map +1 -0
  19. package/dist/git.d.ts +2 -0
  20. package/dist/git.js +3 -0
  21. package/dist/iconMap-V4B8P-Uh.js +206 -0
  22. package/dist/iconMap-V4B8P-Uh.js.map +1 -0
  23. package/dist/icons-CIsIOZXR.js +0 -0
  24. package/dist/icons.d.ts +2 -0
  25. package/dist/icons.js +4 -0
  26. package/dist/index-BNmNIWBL.d.ts +71 -0
  27. package/dist/index-BNmNIWBL.d.ts.map +1 -0
  28. package/dist/index-Bryv_GCG.d.ts +1481 -0
  29. package/dist/index-Bryv_GCG.d.ts.map +1 -0
  30. package/dist/index-CuQIjSXs.d.ts +134 -0
  31. package/dist/index-CuQIjSXs.d.ts.map +1 -0
  32. package/dist/index-DSu19mq0.d.ts +153 -0
  33. package/dist/index-DSu19mq0.d.ts.map +1 -0
  34. package/dist/index-DmsyeHFr.d.ts +149 -0
  35. package/dist/index-DmsyeHFr.d.ts.map +1 -0
  36. package/dist/index-DxnJ8FYM.d.ts +17 -0
  37. package/dist/index-DxnJ8FYM.d.ts.map +1 -0
  38. package/dist/index-DzfY1Tok.d.ts +32 -0
  39. package/dist/index-DzfY1Tok.d.ts.map +1 -0
  40. package/dist/index-Ml_SgiKa.d.ts +1847 -0
  41. package/dist/index-Ml_SgiKa.d.ts.map +1 -0
  42. package/dist/index-kHr9udZD.d.ts +1025 -0
  43. package/dist/index-kHr9udZD.d.ts.map +1 -0
  44. package/dist/index.d.ts +11 -0
  45. package/dist/index.js +15 -0
  46. package/dist/layout-Ca_4r8ka.js +89 -0
  47. package/dist/layout-Ca_4r8ka.js.map +1 -0
  48. package/dist/layout.d.ts +2 -0
  49. package/dist/layout.js +5 -0
  50. package/dist/list-CxfT6hix.js +6831 -0
  51. package/dist/list-CxfT6hix.js.map +1 -0
  52. package/dist/list.d.ts +2 -0
  53. package/dist/list.js +5 -0
  54. package/dist/media-DZ292aKK.js +557 -0
  55. package/dist/media-DZ292aKK.js.map +1 -0
  56. package/dist/media.d.ts +3 -0
  57. package/dist/media.js +3 -0
  58. package/dist/tree-Dd9Z0Aso.js +3351 -0
  59. package/dist/tree-Dd9Z0Aso.js.map +1 -0
  60. package/dist/tree.d.ts +2 -0
  61. package/dist/tree.js +6 -0
  62. package/dist/types-common-CB3kRek8.d.ts +26 -0
  63. package/dist/types-common-CB3kRek8.d.ts.map +1 -0
  64. package/dist/utils-B4fdKKsy.js +3 -0
  65. package/package.json +109 -0
  66. package/src/calendar/AgendaView.tsx +37 -0
  67. package/src/calendar/CalendarBrowser.tsx +90 -0
  68. package/src/calendar/CalendarModel.ts +142 -0
  69. package/src/calendar/CalendarSidebar.tsx +81 -0
  70. package/src/calendar/DayView.tsx +76 -0
  71. package/src/calendar/EventCard.tsx +51 -0
  72. package/src/calendar/MockCalendarProvider.ts +98 -0
  73. package/src/calendar/MonthView.tsx +77 -0
  74. package/src/calendar/WeekView.tsx +129 -0
  75. package/src/calendar/index.ts +18 -0
  76. package/src/calendar/types.ts +25 -0
  77. package/src/contacts/ContactAvatar.tsx +35 -0
  78. package/src/contacts/ContactBrowser.tsx +56 -0
  79. package/src/contacts/ContactCard.tsx +37 -0
  80. package/src/contacts/ContactDetail.tsx +63 -0
  81. package/src/contacts/ContactGroupSidebar.tsx +40 -0
  82. package/src/contacts/ContactList.tsx +32 -0
  83. package/src/contacts/ContactListModel.ts +120 -0
  84. package/src/contacts/MockContactProvider.ts +77 -0
  85. package/src/contacts/index.ts +17 -0
  86. package/src/contacts/types.ts +26 -0
  87. package/src/demos/CalendarBrowserDemo.tsx +15 -0
  88. package/src/demos/ContactBrowserDemo.tsx +15 -0
  89. package/src/demos/MediaBrowserDemo.tsx +15 -0
  90. package/src/file-browser/adapters/DocumentViewerAdapter.ts +371 -0
  91. package/src/file-browser/adapters/FileSystemBridge.ts +168 -0
  92. package/src/file-browser/adapters/GitBrowserAdapter.ts +546 -0
  93. package/src/file-browser/adapters/README.md +504 -0
  94. package/src/file-browser/adapters/index.ts +27 -0
  95. package/src/file-browser/adapters/types.ts +70 -0
  96. package/src/file-browser/architecture.md +645 -0
  97. package/src/file-browser/components/CreateItemDialog.tsx +71 -0
  98. package/src/file-browser/components/DeleteConfirmDialog.tsx +58 -0
  99. package/src/file-browser/components/FileBrowser.tsx +473 -0
  100. package/src/file-browser/components/FileBrowserContent.tsx +209 -0
  101. package/src/file-browser/components/FileBrowserHeader.tsx +151 -0
  102. package/src/file-browser/components/FileBrowserToolbar.tsx +145 -0
  103. package/src/file-browser/components/LeftPanel/LeftPanel.tsx +103 -0
  104. package/src/file-browser/components/LeftPanel/LeftPanelTabs.tsx +70 -0
  105. package/src/file-browser/components/LeftPanel/TreeNavigationView.tsx +256 -0
  106. package/src/file-browser/components/PreviewPane.tsx +146 -0
  107. package/src/file-browser/components/RightPanel/FilePreview.tsx +219 -0
  108. package/src/file-browser/components/RightPanel/RightPanel.tsx +186 -0
  109. package/src/file-browser/components/RightPanel/RightPanelToolbar.tsx +113 -0
  110. package/src/file-browser/components/UploadProgress.tsx +123 -0
  111. package/src/file-browser/components/ViewerHost.tsx +208 -0
  112. package/src/file-browser/components/mobile/MobileNavigation.tsx +227 -0
  113. package/src/file-browser/components/navigation/NavigationButtons.tsx +171 -0
  114. package/src/file-browser/components/shared/ErrorBoundary.tsx +116 -0
  115. package/src/file-browser/components/shared/FileBrowserItem.tsx +195 -0
  116. package/src/file-browser/components/shared/FileIcon.tsx +169 -0
  117. package/src/file-browser/components/toolbar/ViewModeToggle.tsx +200 -0
  118. package/src/file-browser/components/views/ListView/ListView.tsx +484 -0
  119. package/src/file-browser/components/views/ThumbnailView/ThumbnailView.tsx +323 -0
  120. package/src/file-browser/components/views/TreeView/TreeNode.tsx +186 -0
  121. package/src/file-browser/components/views/TreeView/TreeNodeList.tsx +191 -0
  122. package/src/file-browser/components/views/TreeView/TreeView.tsx +200 -0
  123. package/src/file-browser/components/views/TreemapView/TreemapView.tsx +339 -0
  124. package/src/file-browser/context/FileBrowserContext.tsx +13 -0
  125. package/src/file-browser/examples/BasicUsage.tsx +20 -0
  126. package/src/file-browser/index.ts +98 -0
  127. package/src/file-browser/models/FileBrowserModel.ts +623 -0
  128. package/src/file-browser/models/LeftPanelManagerModel.ts +105 -0
  129. package/src/file-browser/models/NavigationManagerModel.ts +312 -0
  130. package/src/file-browser/models/ResponsiveLayoutManagerModel.ts +437 -0
  131. package/src/file-browser/models/RightPanelManagerModel.ts +190 -0
  132. package/src/file-browser/models/SelectionManagerModel.ts +252 -0
  133. package/src/file-browser/models/ToolbarManagerModel.ts +144 -0
  134. package/src/file-browser/models/UploadModel.ts +147 -0
  135. package/src/file-browser/models/ViewModeManagerModel.ts +185 -0
  136. package/src/file-browser/models/ViewerHostModel.ts +44 -0
  137. package/src/file-browser/models/ui/ListViewUIModel.ts +265 -0
  138. package/src/file-browser/models/ui/PreviewUIModel.ts +297 -0
  139. package/src/file-browser/models/ui/ThumbnailViewUIModel.ts +254 -0
  140. package/src/file-browser/models/ui/TreeViewUIModel.ts +128 -0
  141. package/src/file-browser/models/ui/TreemapViewUIModel.ts +350 -0
  142. package/src/file-browser/providers/FileSystemListProvider.ts +552 -0
  143. package/src/file-browser/providers/FileSystemProvider.ts +401 -0
  144. package/src/file-browser/providers/FileSystemTreeProvider.ts +231 -0
  145. package/src/file-browser/providers/GitProvider.ts +337 -0
  146. package/src/file-browser/providers/GitRepositoryProvider.ts +376 -0
  147. package/src/file-browser/providers/IFileBrowserProvider.ts +56 -0
  148. package/src/file-browser/providers/MemoryProvider.ts +303 -0
  149. package/src/file-browser/providers/index.ts +4 -0
  150. package/src/file-browser/registry/ViewerRegistry.ts +551 -0
  151. package/src/file-browser/registry/types.ts +144 -0
  152. package/src/file-browser/scripts/performanceBenchmark.ts +553 -0
  153. package/src/file-browser/services/ThumbnailCacheService.ts +128 -0
  154. package/src/file-browser/tasks.md +537 -0
  155. package/src/file-browser/types/FileBrowserTypes.ts +126 -0
  156. package/src/file-browser/types/ProviderTypes.ts +155 -0
  157. package/src/file-browser/types/UITypes.ts +235 -0
  158. package/src/file-browser/types/ViewModeTypes.ts +150 -0
  159. package/src/file-browser/utils/gestures.ts +327 -0
  160. package/src/file-browser/utils/performance.ts +563 -0
  161. package/src/file-browser/viewers/ImageViewer.tsx +163 -0
  162. package/src/file-browser/viewers/ImageViewerModel.ts +79 -0
  163. package/src/file-browser/viewers/TextViewer.tsx +95 -0
  164. package/src/file-browser/viewers/UnsupportedFileViewer.tsx +57 -0
  165. package/src/file-browser/viewers/index.ts +61 -0
  166. package/src/git/BranchList.tsx +128 -0
  167. package/src/git/CommitGraph.tsx +239 -0
  168. package/src/git/CommitList.tsx +258 -0
  169. package/src/git/DiffViewer.tsx +219 -0
  170. package/src/git/index.ts +4 -0
  171. package/src/icons/iconMap.ts +146 -0
  172. package/src/icons/index.ts +9 -0
  173. package/src/index.ts +13 -0
  174. package/src/layout/README.md +307 -0
  175. package/src/layout/components/ExplorerLayout/ExplorerLayout.tsx +178 -0
  176. package/src/layout/examples/SimpleExample.tsx +60 -0
  177. package/src/layout/index.ts +6 -0
  178. package/src/lib/utils.ts +1 -0
  179. package/src/list/README.md +303 -0
  180. package/src/list/architecture.md +807 -0
  181. package/src/list/components/CalculatedGridView.tsx +252 -0
  182. package/src/list/components/DragPreview.tsx +102 -0
  183. package/src/list/components/ListContextMenu.tsx +274 -0
  184. package/src/list/components/ListItem.tsx +761 -0
  185. package/src/list/components/ListItems.tsx +919 -0
  186. package/src/list/components/MasonryView.tsx +241 -0
  187. package/src/list/components/SearchFilter.tsx +44 -0
  188. package/src/list/components/TreemapView.tsx +709 -0
  189. package/src/list/components/ViewSizeControls.tsx +205 -0
  190. package/src/list/components/ViewTypeSelector.tsx +312 -0
  191. package/src/list/components/VirtualizedDetailsView.tsx +231 -0
  192. package/src/list/components/VirtualizedGrid.tsx +164 -0
  193. package/src/list/components/VirtualizedList.tsx +154 -0
  194. package/src/list/components/VirtualizedMasonryView.tsx +344 -0
  195. package/src/list/components/shared/EmptyState.tsx +103 -0
  196. package/src/list/components/shared/ErrorBoundary.tsx +123 -0
  197. package/src/list/components/shared/ErrorDisplay.tsx +100 -0
  198. package/src/list/components/shared/ListLoader.tsx +146 -0
  199. package/src/list/components/shared/LoadingIndicator.tsx +80 -0
  200. package/src/list/index.ts +92 -0
  201. package/src/list/models/ListItemsModel.ts +1301 -0
  202. package/src/list/models/TreemapModel.ts +204 -0
  203. package/src/list/providers/ListItemsProvider.ts +313 -0
  204. package/src/list/providers/TestListProvider.ts +604 -0
  205. package/src/list/tasks.md +937 -0
  206. package/src/list/types/ListTypes.ts +178 -0
  207. package/src/list/utils/BenchmarkLogger.ts +243 -0
  208. package/src/list/utils/DragDropManager.ts +320 -0
  209. package/src/list/utils/GridLayoutCalculator.ts +290 -0
  210. package/src/list/utils/ListAccessibility.ts +367 -0
  211. package/src/list/utils/ListKeyboard.ts +414 -0
  212. package/src/list/utils/MasonryLayoutCalculator.ts +302 -0
  213. package/src/list/utils/MasonryLayoutEngine.ts +401 -0
  214. package/src/list/utils/__tests__/MasonryLayoutEngine.test.ts +157 -0
  215. package/src/list/utils/__tests__/VirtualizedMasonryView.test.tsx +251 -0
  216. package/src/media/AlbumSidebar.tsx +48 -0
  217. package/src/media/MediaBrowser.tsx +92 -0
  218. package/src/media/MediaBrowserModel.ts +138 -0
  219. package/src/media/MediaGrid.tsx +50 -0
  220. package/src/media/MediaList.tsx +49 -0
  221. package/src/media/MediaPreview.tsx +63 -0
  222. package/src/media/MediaTimeline.tsx +38 -0
  223. package/src/media/MockMediaProvider.ts +70 -0
  224. package/src/media/index.ts +18 -0
  225. package/src/media/types.ts +21 -0
  226. package/src/styles/variables.css +60 -0
  227. package/src/tree/DEVELOPMENT_SUMMARY.md +170 -0
  228. package/src/tree/__tests__/TreeModel.test.ts +16 -0
  229. package/src/tree/architecture.md +530 -0
  230. package/src/tree/components/Tree.tsx +283 -0
  231. package/src/tree/components/TreeCheckbox.tsx +147 -0
  232. package/src/tree/components/TreeContextMenu.tsx +139 -0
  233. package/src/tree/components/TreeNodeList.tsx +329 -0
  234. package/src/tree/components/TreeTable.tsx +382 -0
  235. package/src/tree/index.ts +58 -0
  236. package/src/tree/models/TreeModel.ts +839 -0
  237. package/src/tree/providers/SimpleTreeProvider.ts +463 -0
  238. package/src/tree/providers/TestTreeProvider.ts +946 -0
  239. package/src/tree/providers/TreeProvider.ts +308 -0
  240. package/src/tree/tasks.md +2046 -0
  241. package/src/tree/types/TreeTypes.ts +279 -0
  242. package/src/tree/utils/SelectionTheme.ts +150 -0
  243. package/src/tree/utils/logger.ts +203 -0
  244. package/src/types-common.ts +21 -0
@@ -0,0 +1,367 @@
1
+ // AICODE-NOTE: Accessibility utilities for list components
2
+ export interface AccessibilityConfig {
3
+ role?: string;
4
+ ariaLabel?: string;
5
+ ariaLabelledBy?: string;
6
+ ariaDescribedBy?: string;
7
+ ariaMultiSelectable?: boolean;
8
+ ariaOrientation?: 'horizontal' | 'vertical';
9
+ tabIndex?: number;
10
+ }
11
+
12
+ export interface ItemAccessibilityConfig {
13
+ role?: string;
14
+ ariaLabel?: string;
15
+ ariaSelected?: boolean;
16
+ ariaExpanded?: boolean;
17
+ ariaLevel?: number;
18
+ ariaSetSize?: number;
19
+ ariaPosInSet?: number;
20
+ tabIndex?: number;
21
+ }
22
+
23
+ // AICODE-NOTE: Generate accessibility attributes for list container
24
+ export function getListAccessibilityProps(config: {
25
+ viewType: string;
26
+ isMultiSelect: boolean;
27
+ totalItems: number;
28
+ selectedCount: number;
29
+ label?: string;
30
+ labelledBy?: string;
31
+ describedBy?: string;
32
+ }): AccessibilityConfig {
33
+ const { viewType, isMultiSelect, totalItems, selectedCount, label, labelledBy, describedBy } = config;
34
+
35
+ // AICODE-NOTE: Base role depends on view type
36
+ let role = 'listbox';
37
+ let ariaOrientation: 'horizontal' | 'vertical' = 'vertical';
38
+
39
+ switch (viewType) {
40
+ case 'grid':
41
+ case 'masonry-horizontal':
42
+ case 'masonry-vertical':
43
+ role = 'grid';
44
+ ariaOrientation = viewType === 'masonry-horizontal' ? 'horizontal' : 'vertical';
45
+ break;
46
+ case 'details':
47
+ role = 'table';
48
+ break;
49
+ default:
50
+ role = 'listbox';
51
+ }
52
+
53
+ return {
54
+ role,
55
+ ariaLabel: label || `${viewType} view with ${totalItems} items, ${selectedCount} selected`,
56
+ ariaLabelledBy: labelledBy,
57
+ ariaDescribedBy: describedBy,
58
+ ariaMultiSelectable: isMultiSelect,
59
+ ariaOrientation,
60
+ tabIndex: 0
61
+ };
62
+ }
63
+
64
+ // AICODE-NOTE: Generate accessibility attributes for list items
65
+ export function getItemAccessibilityProps(config: {
66
+ item: any;
67
+ index: number;
68
+ totalItems: number;
69
+ isSelected: boolean;
70
+ isFocused: boolean;
71
+ viewType: string;
72
+ isExpandable?: boolean;
73
+ isExpanded?: boolean;
74
+ level?: number;
75
+ }): ItemAccessibilityConfig {
76
+ const {
77
+ item,
78
+ index,
79
+ totalItems,
80
+ isSelected,
81
+ isFocused,
82
+ viewType,
83
+ isExpandable = false,
84
+ isExpanded,
85
+ level = 1
86
+ } = config;
87
+
88
+ // AICODE-NOTE: Base role depends on view type
89
+ let role = 'option';
90
+
91
+ switch (viewType) {
92
+ case 'grid':
93
+ case 'masonry-horizontal':
94
+ case 'masonry-vertical':
95
+ role = 'gridcell';
96
+ break;
97
+ case 'details':
98
+ role = 'row';
99
+ break;
100
+ default:
101
+ role = 'option';
102
+ }
103
+
104
+ // AICODE-NOTE: Generate descriptive label
105
+ const itemType = item?.type || 'item';
106
+ const itemSize = item?.size ? `, ${formatFileSize(item.size)}` : '';
107
+ const itemDate = item?.modifiedDate ? `, modified ${formatDate(item.modifiedDate)}` : '';
108
+ const selectionState = isSelected ? ', selected' : '';
109
+ const expandState = isExpandable ? (isExpanded ? ', expanded' : ', collapsed') : '';
110
+
111
+ const ariaLabel = `${item?.name || 'Unknown item'}, ${itemType}${itemSize}${itemDate}${selectionState}${expandState}`;
112
+
113
+ return {
114
+ role,
115
+ ariaLabel,
116
+ ariaSelected: isSelected,
117
+ ariaExpanded: isExpandable ? isExpanded : undefined,
118
+ ariaLevel: level,
119
+ ariaSetSize: totalItems,
120
+ ariaPosInSet: index + 1,
121
+ tabIndex: isFocused ? 0 : -1
122
+ };
123
+ }
124
+
125
+ // AICODE-NOTE: Generate live region announcements for screen readers
126
+ export function createLiveRegionAnnouncement(config: {
127
+ action: 'select' | 'deselect' | 'focus' | 'activate' | 'load' | 'error';
128
+ itemName?: string;
129
+ itemCount?: number;
130
+ selectedCount?: number;
131
+ totalCount?: number;
132
+ errorMessage?: string;
133
+ }): string {
134
+ const { action, itemName, itemCount, selectedCount, totalCount, errorMessage } = config;
135
+
136
+ switch (action) {
137
+ case 'select':
138
+ if (itemName && selectedCount !== undefined) {
139
+ return `${itemName} selected. ${selectedCount} of ${totalCount} items selected.`;
140
+ }
141
+ return 'Item selected';
142
+
143
+ case 'deselect':
144
+ if (itemName && selectedCount !== undefined) {
145
+ return `${itemName} deselected. ${selectedCount} of ${totalCount} items selected.`;
146
+ }
147
+ return 'Item deselected';
148
+
149
+ case 'focus':
150
+ return itemName ? `${itemName} focused` : 'Item focused';
151
+
152
+ case 'activate':
153
+ return itemName ? `${itemName} activated` : 'Item activated';
154
+
155
+ case 'load':
156
+ if (itemCount !== undefined) {
157
+ return `${itemCount} items loaded`;
158
+ }
159
+ return 'Items loaded';
160
+
161
+ case 'error':
162
+ return errorMessage || 'An error occurred';
163
+
164
+ default:
165
+ return '';
166
+ }
167
+ }
168
+
169
+ // AICODE-NOTE: Keyboard navigation helpers
170
+ export interface KeyboardNavigationConfig {
171
+ currentIndex: number;
172
+ totalItems: number;
173
+ itemsPerRow?: number;
174
+ viewType: string;
175
+ }
176
+
177
+ export function getNextFocusIndex(
178
+ key: string,
179
+ config: KeyboardNavigationConfig
180
+ ): number | null {
181
+ const { currentIndex, totalItems, itemsPerRow = 1, viewType } = config;
182
+
183
+ switch (key) {
184
+ case 'ArrowDown':
185
+ if (viewType === 'grid' || viewType.includes('masonry')) {
186
+ // AICODE-NOTE: Grid navigation - move down by itemsPerRow
187
+ const nextIndex = currentIndex + itemsPerRow;
188
+ return nextIndex < totalItems ? nextIndex : null;
189
+ } else {
190
+ // AICODE-NOTE: List navigation - next item
191
+ return currentIndex < totalItems - 1 ? currentIndex + 1 : null;
192
+ }
193
+
194
+ case 'ArrowUp':
195
+ if (viewType === 'grid' || viewType.includes('masonry')) {
196
+ // AICODE-NOTE: Grid navigation - move up by itemsPerRow
197
+ const prevIndex = currentIndex - itemsPerRow;
198
+ return prevIndex >= 0 ? prevIndex : null;
199
+ } else {
200
+ // AICODE-NOTE: List navigation - previous item
201
+ return currentIndex > 0 ? currentIndex - 1 : null;
202
+ }
203
+
204
+ case 'ArrowRight':
205
+ if (viewType === 'grid' || viewType.includes('masonry')) {
206
+ // AICODE-NOTE: Grid navigation - next item in row
207
+ const nextIndex = currentIndex + 1;
208
+ const currentRow = Math.floor(currentIndex / itemsPerRow);
209
+ const nextRow = Math.floor(nextIndex / itemsPerRow);
210
+ return nextIndex < totalItems && currentRow === nextRow ? nextIndex : null;
211
+ }
212
+ return null;
213
+
214
+ case 'ArrowLeft':
215
+ if (viewType === 'grid' || viewType.includes('masonry')) {
216
+ // AICODE-NOTE: Grid navigation - previous item in row
217
+ const prevIndex = currentIndex - 1;
218
+ const currentRow = Math.floor(currentIndex / itemsPerRow);
219
+ const prevRow = Math.floor(prevIndex / itemsPerRow);
220
+ return prevIndex >= 0 && currentRow === prevRow ? prevIndex : null;
221
+ }
222
+ return null;
223
+
224
+ case 'Home':
225
+ return 0;
226
+
227
+ case 'End':
228
+ return totalItems - 1;
229
+
230
+ case 'PageUp':
231
+ // AICODE-NOTE: Move up by approximately 10 items or one screen
232
+ const pageUpIndex = Math.max(0, currentIndex - 10);
233
+ return pageUpIndex !== currentIndex ? pageUpIndex : null;
234
+
235
+ case 'PageDown':
236
+ // AICODE-NOTE: Move down by approximately 10 items or one screen
237
+ const pageDownIndex = Math.min(totalItems - 1, currentIndex + 10);
238
+ return pageDownIndex !== currentIndex ? pageDownIndex : null;
239
+
240
+ default:
241
+ return null;
242
+ }
243
+ }
244
+
245
+ // AICODE-NOTE: Format file size for screen readers
246
+ function formatFileSize(bytes: number): string {
247
+ if (bytes === 0) return '0 bytes';
248
+
249
+ const k = 1024;
250
+ const sizes = ['bytes', 'KB', 'MB', 'GB'];
251
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
252
+
253
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
254
+ }
255
+
256
+ // AICODE-NOTE: Format date for screen readers
257
+ function formatDate(date: Date | string): string {
258
+ const dateObj = typeof date === 'string' ? new Date(date) : date;
259
+ return dateObj.toLocaleDateString('en-US', {
260
+ year: 'numeric',
261
+ month: 'short',
262
+ day: 'numeric',
263
+ hour: '2-digit',
264
+ minute: '2-digit'
265
+ });
266
+ }
267
+
268
+ // AICODE-NOTE: Focus management utilities
269
+ export class FocusManager {
270
+ private focusedElement: HTMLElement | null = null;
271
+ private focusHistory: HTMLElement[] = [];
272
+
273
+ // AICODE-NOTE: Save current focus for restoration
274
+ saveFocus(): void {
275
+ const activeElement = document.activeElement as HTMLElement;
276
+ if (activeElement && activeElement !== document.body) {
277
+ this.focusedElement = activeElement;
278
+ this.focusHistory.push(activeElement);
279
+ }
280
+ }
281
+
282
+ // AICODE-NOTE: Restore previously saved focus
283
+ restoreFocus(): boolean {
284
+ if (this.focusedElement && document.contains(this.focusedElement)) {
285
+ this.focusedElement.focus();
286
+ return true;
287
+ }
288
+
289
+ // AICODE-NOTE: Try focus history
290
+ while (this.focusHistory.length > 0) {
291
+ const element = this.focusHistory.pop();
292
+ if (element && document.contains(element)) {
293
+ element.focus();
294
+ return true;
295
+ }
296
+ }
297
+
298
+ return false;
299
+ }
300
+
301
+ // AICODE-NOTE: Focus first focusable element in container
302
+ focusFirst(container: HTMLElement): boolean {
303
+ const focusable = this.getFocusableElements(container);
304
+ if (focusable.length > 0 && focusable[0]) {
305
+ focusable[0].focus();
306
+ return true;
307
+ }
308
+ return false;
309
+ }
310
+
311
+ // AICODE-NOTE: Focus last focusable element in container
312
+ focusLast(container: HTMLElement): boolean {
313
+ const focusable = this.getFocusableElements(container);
314
+ const lastElement = focusable[focusable.length - 1];
315
+ if (focusable.length > 0 && lastElement) {
316
+ lastElement.focus();
317
+ return true;
318
+ }
319
+ return false;
320
+ }
321
+
322
+ // AICODE-NOTE: Get all focusable elements in container
323
+ private getFocusableElements(container: HTMLElement): HTMLElement[] {
324
+ const focusableSelectors = [
325
+ 'button:not([disabled])',
326
+ 'input:not([disabled])',
327
+ 'select:not([disabled])',
328
+ 'textarea:not([disabled])',
329
+ 'a[href]',
330
+ '[tabindex]:not([tabindex="-1"])'
331
+ ].join(', ');
332
+
333
+ return Array.from(container.querySelectorAll(focusableSelectors)) as HTMLElement[];
334
+ }
335
+ }
336
+
337
+ // AICODE-NOTE: Create live region element for announcements
338
+ export function createLiveRegion(id: string = 'list-live-region'): HTMLElement {
339
+ let liveRegion = document.getElementById(id);
340
+
341
+ if (!liveRegion) {
342
+ liveRegion = document.createElement('div');
343
+ liveRegion.id = id;
344
+ liveRegion.setAttribute('aria-live', 'polite');
345
+ liveRegion.setAttribute('aria-atomic', 'true');
346
+ liveRegion.style.position = 'absolute';
347
+ liveRegion.style.left = '-10000px';
348
+ liveRegion.style.width = '1px';
349
+ liveRegion.style.height = '1px';
350
+ liveRegion.style.overflow = 'hidden';
351
+ document.body.appendChild(liveRegion);
352
+ }
353
+
354
+ return liveRegion;
355
+ }
356
+
357
+ // AICODE-NOTE: Announce message to screen readers
358
+ export function announceToScreenReader(message: string, priority: 'polite' | 'assertive' = 'polite'): void {
359
+ const liveRegion = createLiveRegion();
360
+ liveRegion.setAttribute('aria-live', priority);
361
+
362
+ // AICODE-NOTE: Clear and set new message
363
+ liveRegion.textContent = '';
364
+ setTimeout(() => {
365
+ liveRegion.textContent = message;
366
+ }, 100);
367
+ }