@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.
- package/dist/ExplorerLayout-CSIJd7N4.js +105 -0
- package/dist/ExplorerLayout-CSIJd7N4.js.map +1 -0
- package/dist/FileBrowserContext-B6jixa2j.js +11 -0
- package/dist/FileBrowserContext-B6jixa2j.js.map +1 -0
- package/dist/calendar-DSlrbHoj.js +761 -0
- package/dist/calendar-DSlrbHoj.js.map +1 -0
- package/dist/calendar.d.ts +3 -0
- package/dist/calendar.js +3 -0
- package/dist/contacts-DQXTZzHc.js +539 -0
- package/dist/contacts-DQXTZzHc.js.map +1 -0
- package/dist/contacts.d.ts +3 -0
- package/dist/contacts.js +3 -0
- package/dist/file-browser-m5atC3kF.js +6755 -0
- package/dist/file-browser-m5atC3kF.js.map +1 -0
- package/dist/file-browser.d.ts +11 -0
- package/dist/file-browser.js +9 -0
- package/dist/git-B55e6LL-.js +561 -0
- package/dist/git-B55e6LL-.js.map +1 -0
- package/dist/git.d.ts +2 -0
- package/dist/git.js +3 -0
- package/dist/iconMap-V4B8P-Uh.js +206 -0
- package/dist/iconMap-V4B8P-Uh.js.map +1 -0
- package/dist/icons-CIsIOZXR.js +0 -0
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +4 -0
- package/dist/index-BNmNIWBL.d.ts +71 -0
- package/dist/index-BNmNIWBL.d.ts.map +1 -0
- package/dist/index-Bryv_GCG.d.ts +1481 -0
- package/dist/index-Bryv_GCG.d.ts.map +1 -0
- package/dist/index-CuQIjSXs.d.ts +134 -0
- package/dist/index-CuQIjSXs.d.ts.map +1 -0
- package/dist/index-DSu19mq0.d.ts +153 -0
- package/dist/index-DSu19mq0.d.ts.map +1 -0
- package/dist/index-DmsyeHFr.d.ts +149 -0
- package/dist/index-DmsyeHFr.d.ts.map +1 -0
- package/dist/index-DxnJ8FYM.d.ts +17 -0
- package/dist/index-DxnJ8FYM.d.ts.map +1 -0
- package/dist/index-DzfY1Tok.d.ts +32 -0
- package/dist/index-DzfY1Tok.d.ts.map +1 -0
- package/dist/index-Ml_SgiKa.d.ts +1847 -0
- package/dist/index-Ml_SgiKa.d.ts.map +1 -0
- package/dist/index-kHr9udZD.d.ts +1025 -0
- package/dist/index-kHr9udZD.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +15 -0
- package/dist/layout-Ca_4r8ka.js +89 -0
- package/dist/layout-Ca_4r8ka.js.map +1 -0
- package/dist/layout.d.ts +2 -0
- package/dist/layout.js +5 -0
- package/dist/list-CxfT6hix.js +6831 -0
- package/dist/list-CxfT6hix.js.map +1 -0
- package/dist/list.d.ts +2 -0
- package/dist/list.js +5 -0
- package/dist/media-DZ292aKK.js +557 -0
- package/dist/media-DZ292aKK.js.map +1 -0
- package/dist/media.d.ts +3 -0
- package/dist/media.js +3 -0
- package/dist/tree-Dd9Z0Aso.js +3351 -0
- package/dist/tree-Dd9Z0Aso.js.map +1 -0
- package/dist/tree.d.ts +2 -0
- package/dist/tree.js +6 -0
- package/dist/types-common-CB3kRek8.d.ts +26 -0
- package/dist/types-common-CB3kRek8.d.ts.map +1 -0
- package/dist/utils-B4fdKKsy.js +3 -0
- package/package.json +109 -0
- package/src/calendar/AgendaView.tsx +37 -0
- package/src/calendar/CalendarBrowser.tsx +90 -0
- package/src/calendar/CalendarModel.ts +142 -0
- package/src/calendar/CalendarSidebar.tsx +81 -0
- package/src/calendar/DayView.tsx +76 -0
- package/src/calendar/EventCard.tsx +51 -0
- package/src/calendar/MockCalendarProvider.ts +98 -0
- package/src/calendar/MonthView.tsx +77 -0
- package/src/calendar/WeekView.tsx +129 -0
- package/src/calendar/index.ts +18 -0
- package/src/calendar/types.ts +25 -0
- package/src/contacts/ContactAvatar.tsx +35 -0
- package/src/contacts/ContactBrowser.tsx +56 -0
- package/src/contacts/ContactCard.tsx +37 -0
- package/src/contacts/ContactDetail.tsx +63 -0
- package/src/contacts/ContactGroupSidebar.tsx +40 -0
- package/src/contacts/ContactList.tsx +32 -0
- package/src/contacts/ContactListModel.ts +120 -0
- package/src/contacts/MockContactProvider.ts +77 -0
- package/src/contacts/index.ts +17 -0
- package/src/contacts/types.ts +26 -0
- package/src/demos/CalendarBrowserDemo.tsx +15 -0
- package/src/demos/ContactBrowserDemo.tsx +15 -0
- package/src/demos/MediaBrowserDemo.tsx +15 -0
- package/src/file-browser/adapters/DocumentViewerAdapter.ts +371 -0
- package/src/file-browser/adapters/FileSystemBridge.ts +168 -0
- package/src/file-browser/adapters/GitBrowserAdapter.ts +546 -0
- package/src/file-browser/adapters/README.md +504 -0
- package/src/file-browser/adapters/index.ts +27 -0
- package/src/file-browser/adapters/types.ts +70 -0
- package/src/file-browser/architecture.md +645 -0
- package/src/file-browser/components/CreateItemDialog.tsx +71 -0
- package/src/file-browser/components/DeleteConfirmDialog.tsx +58 -0
- package/src/file-browser/components/FileBrowser.tsx +473 -0
- package/src/file-browser/components/FileBrowserContent.tsx +209 -0
- package/src/file-browser/components/FileBrowserHeader.tsx +151 -0
- package/src/file-browser/components/FileBrowserToolbar.tsx +145 -0
- package/src/file-browser/components/LeftPanel/LeftPanel.tsx +103 -0
- package/src/file-browser/components/LeftPanel/LeftPanelTabs.tsx +70 -0
- package/src/file-browser/components/LeftPanel/TreeNavigationView.tsx +256 -0
- package/src/file-browser/components/PreviewPane.tsx +146 -0
- package/src/file-browser/components/RightPanel/FilePreview.tsx +219 -0
- package/src/file-browser/components/RightPanel/RightPanel.tsx +186 -0
- package/src/file-browser/components/RightPanel/RightPanelToolbar.tsx +113 -0
- package/src/file-browser/components/UploadProgress.tsx +123 -0
- package/src/file-browser/components/ViewerHost.tsx +208 -0
- package/src/file-browser/components/mobile/MobileNavigation.tsx +227 -0
- package/src/file-browser/components/navigation/NavigationButtons.tsx +171 -0
- package/src/file-browser/components/shared/ErrorBoundary.tsx +116 -0
- package/src/file-browser/components/shared/FileBrowserItem.tsx +195 -0
- package/src/file-browser/components/shared/FileIcon.tsx +169 -0
- package/src/file-browser/components/toolbar/ViewModeToggle.tsx +200 -0
- package/src/file-browser/components/views/ListView/ListView.tsx +484 -0
- package/src/file-browser/components/views/ThumbnailView/ThumbnailView.tsx +323 -0
- package/src/file-browser/components/views/TreeView/TreeNode.tsx +186 -0
- package/src/file-browser/components/views/TreeView/TreeNodeList.tsx +191 -0
- package/src/file-browser/components/views/TreeView/TreeView.tsx +200 -0
- package/src/file-browser/components/views/TreemapView/TreemapView.tsx +339 -0
- package/src/file-browser/context/FileBrowserContext.tsx +13 -0
- package/src/file-browser/examples/BasicUsage.tsx +20 -0
- package/src/file-browser/index.ts +98 -0
- package/src/file-browser/models/FileBrowserModel.ts +623 -0
- package/src/file-browser/models/LeftPanelManagerModel.ts +105 -0
- package/src/file-browser/models/NavigationManagerModel.ts +312 -0
- package/src/file-browser/models/ResponsiveLayoutManagerModel.ts +437 -0
- package/src/file-browser/models/RightPanelManagerModel.ts +190 -0
- package/src/file-browser/models/SelectionManagerModel.ts +252 -0
- package/src/file-browser/models/ToolbarManagerModel.ts +144 -0
- package/src/file-browser/models/UploadModel.ts +147 -0
- package/src/file-browser/models/ViewModeManagerModel.ts +185 -0
- package/src/file-browser/models/ViewerHostModel.ts +44 -0
- package/src/file-browser/models/ui/ListViewUIModel.ts +265 -0
- package/src/file-browser/models/ui/PreviewUIModel.ts +297 -0
- package/src/file-browser/models/ui/ThumbnailViewUIModel.ts +254 -0
- package/src/file-browser/models/ui/TreeViewUIModel.ts +128 -0
- package/src/file-browser/models/ui/TreemapViewUIModel.ts +350 -0
- package/src/file-browser/providers/FileSystemListProvider.ts +552 -0
- package/src/file-browser/providers/FileSystemProvider.ts +401 -0
- package/src/file-browser/providers/FileSystemTreeProvider.ts +231 -0
- package/src/file-browser/providers/GitProvider.ts +337 -0
- package/src/file-browser/providers/GitRepositoryProvider.ts +376 -0
- package/src/file-browser/providers/IFileBrowserProvider.ts +56 -0
- package/src/file-browser/providers/MemoryProvider.ts +303 -0
- package/src/file-browser/providers/index.ts +4 -0
- package/src/file-browser/registry/ViewerRegistry.ts +551 -0
- package/src/file-browser/registry/types.ts +144 -0
- package/src/file-browser/scripts/performanceBenchmark.ts +553 -0
- package/src/file-browser/services/ThumbnailCacheService.ts +128 -0
- package/src/file-browser/tasks.md +537 -0
- package/src/file-browser/types/FileBrowserTypes.ts +126 -0
- package/src/file-browser/types/ProviderTypes.ts +155 -0
- package/src/file-browser/types/UITypes.ts +235 -0
- package/src/file-browser/types/ViewModeTypes.ts +150 -0
- package/src/file-browser/utils/gestures.ts +327 -0
- package/src/file-browser/utils/performance.ts +563 -0
- package/src/file-browser/viewers/ImageViewer.tsx +163 -0
- package/src/file-browser/viewers/ImageViewerModel.ts +79 -0
- package/src/file-browser/viewers/TextViewer.tsx +95 -0
- package/src/file-browser/viewers/UnsupportedFileViewer.tsx +57 -0
- package/src/file-browser/viewers/index.ts +61 -0
- package/src/git/BranchList.tsx +128 -0
- package/src/git/CommitGraph.tsx +239 -0
- package/src/git/CommitList.tsx +258 -0
- package/src/git/DiffViewer.tsx +219 -0
- package/src/git/index.ts +4 -0
- package/src/icons/iconMap.ts +146 -0
- package/src/icons/index.ts +9 -0
- package/src/index.ts +13 -0
- package/src/layout/README.md +307 -0
- package/src/layout/components/ExplorerLayout/ExplorerLayout.tsx +178 -0
- package/src/layout/examples/SimpleExample.tsx +60 -0
- package/src/layout/index.ts +6 -0
- package/src/lib/utils.ts +1 -0
- package/src/list/README.md +303 -0
- package/src/list/architecture.md +807 -0
- package/src/list/components/CalculatedGridView.tsx +252 -0
- package/src/list/components/DragPreview.tsx +102 -0
- package/src/list/components/ListContextMenu.tsx +274 -0
- package/src/list/components/ListItem.tsx +761 -0
- package/src/list/components/ListItems.tsx +919 -0
- package/src/list/components/MasonryView.tsx +241 -0
- package/src/list/components/SearchFilter.tsx +44 -0
- package/src/list/components/TreemapView.tsx +709 -0
- package/src/list/components/ViewSizeControls.tsx +205 -0
- package/src/list/components/ViewTypeSelector.tsx +312 -0
- package/src/list/components/VirtualizedDetailsView.tsx +231 -0
- package/src/list/components/VirtualizedGrid.tsx +164 -0
- package/src/list/components/VirtualizedList.tsx +154 -0
- package/src/list/components/VirtualizedMasonryView.tsx +344 -0
- package/src/list/components/shared/EmptyState.tsx +103 -0
- package/src/list/components/shared/ErrorBoundary.tsx +123 -0
- package/src/list/components/shared/ErrorDisplay.tsx +100 -0
- package/src/list/components/shared/ListLoader.tsx +146 -0
- package/src/list/components/shared/LoadingIndicator.tsx +80 -0
- package/src/list/index.ts +92 -0
- package/src/list/models/ListItemsModel.ts +1301 -0
- package/src/list/models/TreemapModel.ts +204 -0
- package/src/list/providers/ListItemsProvider.ts +313 -0
- package/src/list/providers/TestListProvider.ts +604 -0
- package/src/list/tasks.md +937 -0
- package/src/list/types/ListTypes.ts +178 -0
- package/src/list/utils/BenchmarkLogger.ts +243 -0
- package/src/list/utils/DragDropManager.ts +320 -0
- package/src/list/utils/GridLayoutCalculator.ts +290 -0
- package/src/list/utils/ListAccessibility.ts +367 -0
- package/src/list/utils/ListKeyboard.ts +414 -0
- package/src/list/utils/MasonryLayoutCalculator.ts +302 -0
- package/src/list/utils/MasonryLayoutEngine.ts +401 -0
- package/src/list/utils/__tests__/MasonryLayoutEngine.test.ts +157 -0
- package/src/list/utils/__tests__/VirtualizedMasonryView.test.tsx +251 -0
- package/src/media/AlbumSidebar.tsx +48 -0
- package/src/media/MediaBrowser.tsx +92 -0
- package/src/media/MediaBrowserModel.ts +138 -0
- package/src/media/MediaGrid.tsx +50 -0
- package/src/media/MediaList.tsx +49 -0
- package/src/media/MediaPreview.tsx +63 -0
- package/src/media/MediaTimeline.tsx +38 -0
- package/src/media/MockMediaProvider.ts +70 -0
- package/src/media/index.ts +18 -0
- package/src/media/types.ts +21 -0
- package/src/styles/variables.css +60 -0
- package/src/tree/DEVELOPMENT_SUMMARY.md +170 -0
- package/src/tree/__tests__/TreeModel.test.ts +16 -0
- package/src/tree/architecture.md +530 -0
- package/src/tree/components/Tree.tsx +283 -0
- package/src/tree/components/TreeCheckbox.tsx +147 -0
- package/src/tree/components/TreeContextMenu.tsx +139 -0
- package/src/tree/components/TreeNodeList.tsx +329 -0
- package/src/tree/components/TreeTable.tsx +382 -0
- package/src/tree/index.ts +58 -0
- package/src/tree/models/TreeModel.ts +839 -0
- package/src/tree/providers/SimpleTreeProvider.ts +463 -0
- package/src/tree/providers/TestTreeProvider.ts +946 -0
- package/src/tree/providers/TreeProvider.ts +308 -0
- package/src/tree/tasks.md +2046 -0
- package/src/tree/types/TreeTypes.ts +279 -0
- package/src/tree/utils/SelectionTheme.ts +150 -0
- package/src/tree/utils/logger.ts +203 -0
- 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
|
+
}
|