@ankhorage/zora 25.0.1 → 25.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 (86) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +2 -2
  3. package/dist/features/explorer/adapters/inbound/Explorer.d.ts.map +1 -1
  4. package/dist/features/explorer/adapters/inbound/Explorer.js +72 -14
  5. package/dist/features/explorer/adapters/inbound/Explorer.js.map +1 -1
  6. package/dist/features/explorer/adapters/inbound/ExplorerKeyboardProxy.d.ts +9 -0
  7. package/dist/features/explorer/adapters/inbound/ExplorerKeyboardProxy.d.ts.map +1 -0
  8. package/dist/features/explorer/adapters/inbound/ExplorerKeyboardProxy.js +5 -0
  9. package/dist/features/explorer/adapters/inbound/ExplorerKeyboardProxy.js.map +1 -0
  10. package/dist/features/explorer/adapters/inbound/ExplorerKeyboardProxy.native.d.ts +9 -0
  11. package/dist/features/explorer/adapters/inbound/ExplorerKeyboardProxy.native.d.ts.map +1 -0
  12. package/dist/features/explorer/adapters/inbound/ExplorerKeyboardProxy.native.js +5 -0
  13. package/dist/features/explorer/adapters/inbound/ExplorerKeyboardProxy.native.js.map +1 -0
  14. package/dist/features/explorer/adapters/inbound/ExplorerKeyboardProxy.web.d.ts +9 -0
  15. package/dist/features/explorer/adapters/inbound/ExplorerKeyboardProxy.web.d.ts.map +1 -0
  16. package/dist/features/explorer/adapters/inbound/ExplorerKeyboardProxy.web.js +10 -0
  17. package/dist/features/explorer/adapters/inbound/ExplorerKeyboardProxy.web.js.map +1 -0
  18. package/dist/features/explorer/adapters/inbound/useExplorerKeyboardFocus.d.ts +14 -0
  19. package/dist/features/explorer/adapters/inbound/useExplorerKeyboardFocus.d.ts.map +1 -0
  20. package/dist/features/explorer/adapters/inbound/useExplorerKeyboardFocus.js +54 -0
  21. package/dist/features/explorer/adapters/inbound/useExplorerKeyboardFocus.js.map +1 -0
  22. package/dist/features/explorer/application/getExplorerNextFocusId.d.ts +6 -0
  23. package/dist/features/explorer/application/getExplorerNextFocusId.d.ts.map +1 -0
  24. package/dist/features/explorer/application/getExplorerNextFocusId.js +53 -0
  25. package/dist/features/explorer/application/getExplorerNextFocusId.js.map +1 -0
  26. package/dist/features/explorer/application/shouldDispatchExplorerPageRequest.d.ts +8 -0
  27. package/dist/features/explorer/application/shouldDispatchExplorerPageRequest.d.ts.map +1 -0
  28. package/dist/features/explorer/application/shouldDispatchExplorerPageRequest.js +9 -0
  29. package/dist/features/explorer/application/shouldDispatchExplorerPageRequest.js.map +1 -0
  30. package/dist/features/explorer/application/shouldRequestExplorerPage.d.ts +4 -0
  31. package/dist/features/explorer/application/shouldRequestExplorerPage.d.ts.map +1 -0
  32. package/dist/features/explorer/application/shouldRequestExplorerPage.js +14 -0
  33. package/dist/features/explorer/application/shouldRequestExplorerPage.js.map +1 -0
  34. package/dist/features/explorer/meta.d.ts +110 -0
  35. package/dist/features/explorer/meta.d.ts.map +1 -1
  36. package/dist/features/explorer/meta.js +25 -0
  37. package/dist/features/explorer/meta.js.map +1 -1
  38. package/dist/features/explorer/public.d.ts +2 -1
  39. package/dist/features/explorer/public.d.ts.map +1 -1
  40. package/dist/features/explorer/public.js +1 -0
  41. package/dist/features/explorer/public.js.map +1 -1
  42. package/dist/features/grid-view/adapters/inbound/GridView.d.ts +1 -1
  43. package/dist/features/grid-view/adapters/inbound/GridView.d.ts.map +1 -1
  44. package/dist/features/grid-view/adapters/inbound/GridView.js +36 -4
  45. package/dist/features/grid-view/adapters/inbound/GridView.js.map +1 -1
  46. package/dist/features/grid-view/adapters/inbound/TileGrid.d.ts +1 -1
  47. package/dist/features/grid-view/adapters/inbound/TileGrid.d.ts.map +1 -1
  48. package/dist/features/grid-view/adapters/inbound/TileGrid.js +7 -3
  49. package/dist/features/grid-view/adapters/inbound/TileGrid.js.map +1 -1
  50. package/dist/types/explorer.d.ts +28 -0
  51. package/dist/types/explorer.d.ts.map +1 -1
  52. package/dist/types/explorer.js.map +1 -1
  53. package/dist/types/grid-view.d.ts +10 -0
  54. package/dist/types/grid-view.d.ts.map +1 -1
  55. package/dist/types/grid-view.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/capabilities/index.test.ts +10 -6
  58. package/src/features/explorer/adapters/inbound/Explorer.tsx +158 -53
  59. package/src/features/explorer/adapters/inbound/ExplorerKeyboardProxy.native.tsx +11 -0
  60. package/src/features/explorer/adapters/inbound/ExplorerKeyboardProxy.tsx +11 -0
  61. package/src/features/explorer/adapters/inbound/ExplorerKeyboardProxy.web.tsx +20 -0
  62. package/src/features/explorer/adapters/inbound/useExplorerKeyboardFocus.test.tsx +232 -0
  63. package/src/features/explorer/adapters/inbound/useExplorerKeyboardFocus.ts +77 -0
  64. package/src/features/explorer/application/getExplorerNextFocusId.test.ts +37 -0
  65. package/src/features/explorer/application/getExplorerNextFocusId.ts +61 -0
  66. package/src/features/explorer/application/shouldDispatchExplorerPageRequest.test.ts +43 -0
  67. package/src/features/explorer/application/shouldDispatchExplorerPageRequest.ts +18 -0
  68. package/src/features/explorer/application/shouldRequestExplorerPage.test.ts +93 -0
  69. package/src/features/explorer/application/shouldRequestExplorerPage.ts +26 -0
  70. package/src/features/explorer/meta.test.ts +55 -0
  71. package/src/features/explorer/meta.ts +25 -0
  72. package/src/features/explorer/public.ts +3 -0
  73. package/src/features/grid-view/adapters/inbound/GridView.tsx +49 -1
  74. package/src/features/grid-view/adapters/inbound/TileGrid.tsx +11 -0
  75. package/src/types/explorer.ts +30 -0
  76. package/src/types/grid-view.ts +10 -0
  77. package/web-dist/chunks/{Accordion-b20zfsfz.js → Accordion-54k0ncs3.js} +83 -0
  78. package/web-dist/chunks/Accordion-kkr7xfh2.js +397 -0
  79. package/web-dist/components/explorer/Explorer.js +2 -2
  80. package/web-dist/components/file-explorer/FileExplorer.js +2 -2
  81. package/web-dist/components/grid-view/GridView.js +1 -1
  82. package/web-dist/components/media-explorer/MediaExplorer.js +2 -2
  83. package/web-dist/components/tile-grid/TileGrid.js +1 -1
  84. package/web-dist/components/uploader/Uploader.js +2 -2
  85. package/web-dist/manifest.json +10 -10
  86. package/web-dist/chunks/Accordion-0md8wbtx.js +0 -197
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 25.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 92ed809: Finish the Explorer migration with provider-neutral paging and permission presentation, and extend
8
+ the runnable GridView workspace example with paged, limited-access, and Uploader interactions.
9
+
10
+ ## 25.1.0
11
+
12
+ ### Minor Changes
13
+
14
+ - 52f40c6: Add virtualized Explorer keyboard focus navigation with published GridView reveal geometry and a runnable native/web workspace example.
15
+
3
16
  ## 25.0.1
4
17
 
5
18
  ### Patch Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # @ankhorage/zora
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v25.0.1](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![paradox: warnings](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v25.2.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![paradox: warnings](././paradox/badges/docs.svg)
7
7
 
8
8
  Opinionated React Native and React Native Web UI kit built on @ankhorage/surface.
9
9
 
@@ -52,7 +52,7 @@ export default function BasicApp() {
52
52
  }
53
53
  ```
54
54
 
55
- This package contains 5 additional examples. See the generated documentation for the complete set.
55
+ This package contains 6 additional examples. See the generated documentation for the complete set.
56
56
 
57
57
  ## Generated documentation
58
58
 
@@ -1 +1 @@
1
- {"version":3,"file":"Explorer.d.ts","sourceRoot":"","sources":["../../../../../src/features/explorer/adapters/inbound/Explorer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAS9E,2FAA2F;AAC3F,eAAO,MAAM,QAAQ,qDAAoC,CAAC"}
1
+ {"version":3,"file":"Explorer.d.ts","sourceRoot":"","sources":["../../../../../src/features/explorer/adapters/inbound/Explorer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAgB9E,2FAA2F;AAC3F,eAAO,MAAM,QAAQ,qDAAoC,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Pressable as NativePressable, View as NativeView } from 'react-native';
2
+ import { Platform, Pressable as NativePressable, View as NativeView } from 'react-native';
3
3
  import { TileGrid } from '../../../grid-view/public';
4
4
  import { Icon } from '../../../icon/public';
5
5
  import { Image } from '../../../image/public';
@@ -7,18 +7,51 @@ import { withZoraThemeScope } from '../../../theme/adapters/inbound/withZoraThem
7
7
  import { useZoraTheme } from '../../../theme/composition/useZoraTheme';
8
8
  import { Text } from '../../../typography/public';
9
9
  import { resolveExplorerSelection } from '../../application/resolveExplorerSelection';
10
+ import { shouldDispatchExplorerPageRequest, } from '../../application/shouldDispatchExplorerPageRequest';
11
+ import { shouldRequestExplorerPage } from '../../application/shouldRequestExplorerPage';
12
+ import { ExplorerKeyboardProxy } from './ExplorerKeyboardProxy';
13
+ import { useExplorerKeyboardFocus } from './useExplorerKeyboardFocus';
10
14
  /*** Single canonical Explorer implementation for media and file catalogue presentation. */
11
15
  export const Explorer = withZoraThemeScope(ExplorerInner);
12
16
  /*** Presents provider-neutral asset tiles with controlled or uncontrolled selection. */
13
- function ExplorerInner({ items, selectedIds, defaultSelectedIds = [], selectionMode = 'single', onSelectionChange, onActivate, width, height, tileSize = 120, zoom = 1, loading = false, errorText, emptyText = 'No items', disabled = false, readOnly = false, interactionPolicy, testID, }) {
17
+ function ExplorerInner({ items, selectedIds, defaultSelectedIds = [], selectionMode = 'single', onSelectionChange, onActivate, width, height, tileSize = 120, zoom = 1, loading = false, hasMore = false, loadingMore = false, pagingCollectionId, pagingRetryToken, onLoadMore, permissionStatus = 'granted', permissionText, onRequestPermission, errorText, emptyText = 'No items', disabled = false, readOnly = false, interactionPolicy, testID, }) {
14
18
  const { theme } = useZoraTheme();
15
19
  const [internalSelectedIds, setInternalSelectedIds] = React.useState(defaultSelectedIds);
16
20
  const anchorId = React.useRef(null);
21
+ const [columns, setColumns] = React.useState(1);
22
+ const [visibleIds, setVisibleIds] = React.useState([]);
23
+ const lastPageRequestRef = React.useRef(null);
17
24
  const effectiveSelectedIds = selectedIds ?? internalSelectedIds;
18
25
  const itemLookup = React.useMemo(() => new Map(items.map((item) => [item.id, item])), [items]);
19
- const ids = items.filter((item) => !item.disabled).map((item) => item.id);
26
+ const ids = React.useMemo(() => items.filter((item) => !item.disabled).map((item) => item.id), [items]);
20
27
  const selected = new Set(effectiveSelectedIds);
21
28
  const passive = interactionPolicy === 'passive' || disabled || readOnly;
29
+ React.useEffect(() => {
30
+ const request = {
31
+ collectionId: pagingCollectionId,
32
+ loadedItemCount: items.length,
33
+ retryToken: pagingRetryToken,
34
+ };
35
+ if (!onLoadMore ||
36
+ !shouldDispatchExplorerPageRequest(lastPageRequestRef.current, request) ||
37
+ !shouldRequestExplorerPage(ids, visibleIds, hasMore, loadingMore, loading, errorText, permissionStatus)) {
38
+ return;
39
+ }
40
+ lastPageRequestRef.current = request;
41
+ onLoadMore({ loadedItemCount: items.length });
42
+ }, [
43
+ errorText,
44
+ hasMore,
45
+ ids,
46
+ items.length,
47
+ loading,
48
+ loadingMore,
49
+ onLoadMore,
50
+ pagingCollectionId,
51
+ pagingRetryToken,
52
+ permissionStatus,
53
+ visibleIds,
54
+ ]);
22
55
  const select = (item, intent) => {
23
56
  if (passive || item.disabled)
24
57
  return;
@@ -29,13 +62,22 @@ function ExplorerInner({ items, selectedIds, defaultSelectedIds = [], selectionM
29
62
  setInternalSelectedIds(next);
30
63
  onSelectionChange?.({ selectedIds: next });
31
64
  };
65
+ const keyboard = useExplorerKeyboardFocus(items, columns, passive, visibleIds, (targetId, originId, shiftKey) => {
66
+ if (!shiftKey)
67
+ return;
68
+ anchorId.current ??= originId;
69
+ const nextItem = itemLookup.get(targetId);
70
+ if (nextItem)
71
+ select(nextItem, 'range');
72
+ });
32
73
  const renderTile = (tile) => {
33
74
  const item = itemLookup.get(tile.id);
34
75
  if (!item)
35
76
  return null;
36
77
  const isSelected = selected.has(item.id);
37
78
  const canInteract = !passive && !item.disabled;
38
- return (<NativePressable accessibilityLabel={item.name} accessibilityRole="button" accessibilityState={{ selected: isSelected, disabled: !canInteract }} disabled={!canInteract} onLongPress={() => select(item, 'toggle')} onPress={(event) => {
79
+ return (<ExplorerKeyboardProxy onKeyDown={(key, shiftKey) => keyboard.onKeyDown(item.id, key, shiftKey)}>
80
+ <NativePressable accessibilityLabel={item.name} accessibilityRole="button" accessibilityState={{ selected: isSelected, disabled: !canInteract }} disabled={!canInteract} focusable={canInteract && (Platform.OS !== 'web' || item.id === keyboard.tabStopId)} onFocus={() => keyboard.onFocus(item.id)} onLongPress={() => select(item, 'toggle')} onPress={(event) => {
39
81
  const intent = resolveExplorerPressIntent(event.nativeEvent);
40
82
  select(item, intent);
41
83
  if (selectionMode === 'single' && intent === 'replace') {
@@ -50,24 +92,40 @@ function ExplorerInner({ items, selectedIds, defaultSelectedIds = [], selectionM
50
92
  backgroundColor: theme.semantics.neutral.surface,
51
93
  padding: 8,
52
94
  gap: 4,
53
- }} testID={`explorer-item-${item.id}`}>
54
- <NativeView style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
55
- {item.thumbnailUri ? (<Image accessibilityLabel={item.name} aspectRatio={1} source={item.thumbnailUri} width="100%"/>) : (<Icon name={item.kind === 'folder' ? 'folder-outline' : 'document-outline'} size={32}/>)}
56
- </NativeView>
57
- <Text numberOfLines={1} variant="caption">
58
- {item.name}
59
- </Text>
60
- </NativePressable>);
95
+ }} ref={(node) => keyboard.registerTile(item.id, node)} testID={`explorer-item-${item.id}`}>
96
+ <NativeView style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
97
+ {item.thumbnailUri ? (<Image accessibilityLabel={item.name} aspectRatio={1} source={item.thumbnailUri} width="100%"/>) : (<Icon name={item.kind === 'folder' ? 'folder-outline' : 'document-outline'} size={32}/>)}
98
+ </NativeView>
99
+ <Text numberOfLines={1} variant="caption">
100
+ {item.name}
101
+ </Text>
102
+ </NativePressable>
103
+ </ExplorerKeyboardProxy>);
61
104
  };
105
+ if (permissionStatus === 'denied' || permissionStatus === 'unavailable') {
106
+ return (<NativeView testID={testID}>
107
+ <Text>{permissionText ?? 'This collection is unavailable without permission.'}</Text>
108
+ {permissionStatus === 'denied' && onRequestPermission ? (<NativePressable accessibilityRole="button" onPress={onRequestPermission}>
109
+ <Text>Grant access</Text>
110
+ </NativePressable>) : null}
111
+ </NativeView>);
112
+ }
62
113
  if (errorText)
63
114
  return <Text testID={testID}>{errorText}</Text>;
64
115
  if (loading)
65
116
  return <Text testID={testID}>Loading…</Text>;
66
117
  if (items.length === 0)
67
118
  return <Text testID={testID}>{emptyText}</Text>;
68
- return (<TileGrid height={height} interactionPolicy={interactionPolicy} items={items} renderItem={renderTile} testID={testID} tileSize={tileSize} width={width} zoom={zoom}/>);
119
+ return (<NativeView>
120
+ {permissionStatus === 'limited' ? (<Text>{permissionText ?? 'Showing the media you have allowed.'}</Text>) : null}
121
+ <TileGrid focusedItemId={keyboard.focusedId ?? undefined} height={height} interactionPolicy={interactionPolicy} onColumnsChange={setColumns} onVisibleItemIdsChange={setVisibleIds} items={items} renderItem={renderTile} testID={testID} tileSize={tileSize} width={width} zoom={zoom}/>
122
+ {loadingMore ? <Text>Loading more…</Text> : null}
123
+ </NativeView>);
69
124
  }
70
- /*** Normalize platform press modifiers for multi-selection without passing event objects into contracts. */
125
+ /***
126
+ * Normalize platform press modifiers into selection intents
127
+ * without passing event objects into the public contracts.
128
+ */
71
129
  function resolveExplorerPressIntent(event) {
72
130
  if (typeof event !== 'object' || event === null)
73
131
  return 'replace';
@@ -1 +1 @@
1
- {"version":3,"file":"Explorer.js","sourceRoot":"","sources":["../../../../../src/features/explorer/adapters/inbound/Explorer.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport { Pressable as NativePressable, View as NativeView } from 'react-native';\n\nimport type { ExplorerItem, ExplorerProps } from '../../../../types/explorer';\nimport { TileGrid } from '../../../grid-view/public';\nimport { Icon } from '../../../icon/public';\nimport { Image } from '../../../image/public';\nimport { withZoraThemeScope } from '../../../theme/adapters/inbound/withZoraThemeScope';\nimport { useZoraTheme } from '../../../theme/composition/useZoraTheme';\nimport { Text } from '../../../typography/public';\nimport { resolveExplorerSelection } from '../../application/resolveExplorerSelection';\n\n/*** Single canonical Explorer implementation for media and file catalogue presentation. */\nexport const Explorer = withZoraThemeScope(ExplorerInner);\n\n/*** Presents provider-neutral asset tiles with controlled or uncontrolled selection. */\nfunction ExplorerInner({\n items,\n selectedIds,\n defaultSelectedIds = [],\n selectionMode = 'single',\n onSelectionChange,\n onActivate,\n width,\n height,\n tileSize = 120,\n zoom = 1,\n loading = false,\n errorText,\n emptyText = 'No items',\n disabled = false,\n readOnly = false,\n interactionPolicy,\n testID,\n}: ExplorerProps) {\n const { theme } = useZoraTheme();\n const [internalSelectedIds, setInternalSelectedIds] =\n React.useState<readonly string[]>(defaultSelectedIds);\n const anchorId = React.useRef<string | null>(null);\n const effectiveSelectedIds = selectedIds ?? internalSelectedIds;\n const itemLookup = React.useMemo(() => new Map(items.map((item) => [item.id, item])), [items]);\n const ids = items.filter((item) => !item.disabled).map((item) => item.id);\n const selected = new Set(effectiveSelectedIds);\n const passive = interactionPolicy === 'passive' || disabled || readOnly;\n\n const select = (item: ExplorerItem, intent: 'replace' | 'toggle' | 'range') => {\n if (passive || item.disabled) return;\n const next = resolveExplorerSelection(\n ids,\n effectiveSelectedIds,\n item.id,\n anchorId.current,\n intent,\n selectionMode,\n );\n if (intent !== 'range') anchorId.current = item.id;\n if (selectedIds === undefined) setInternalSelectedIds(next);\n onSelectionChange?.({ selectedIds: next });\n };\n\n const renderTile = (tile: { readonly id: string }) => {\n const item = itemLookup.get(tile.id);\n if (!item) return null;\n const isSelected = selected.has(item.id);\n const canInteract = !passive && !item.disabled;\n return (\n <NativePressable\n accessibilityLabel={item.name}\n accessibilityRole=\"button\"\n accessibilityState={{ selected: isSelected, disabled: !canInteract }}\n disabled={!canInteract}\n onLongPress={() => select(item, 'toggle')}\n onPress={(event) => {\n const intent = resolveExplorerPressIntent(event.nativeEvent);\n select(item, intent);\n if (selectionMode === 'single' && intent === 'replace') {\n onActivate?.({ id: item.id });\n }\n }}\n style={{\n flex: 1,\n overflow: 'hidden',\n borderRadius: 8,\n borderWidth: isSelected ? 2 : 1,\n borderColor: isSelected ? theme.colors.primary : theme.semantics.neutral.divider,\n backgroundColor: theme.semantics.neutral.surface,\n padding: 8,\n gap: 4,\n }}\n testID={`explorer-item-${item.id}`}\n >\n <NativeView style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>\n {item.thumbnailUri ? (\n <Image\n accessibilityLabel={item.name}\n aspectRatio={1}\n source={item.thumbnailUri}\n width=\"100%\"\n />\n ) : (\n <Icon name={item.kind === 'folder' ? 'folder-outline' : 'document-outline'} size={32} />\n )}\n </NativeView>\n <Text numberOfLines={1} variant=\"caption\">\n {item.name}\n </Text>\n </NativePressable>\n );\n };\n\n if (errorText) return <Text testID={testID}>{errorText}</Text>;\n if (loading) return <Text testID={testID}>Loading…</Text>;\n if (items.length === 0) return <Text testID={testID}>{emptyText}</Text>;\n\n return (\n <TileGrid\n height={height}\n interactionPolicy={interactionPolicy}\n items={items}\n renderItem={renderTile}\n testID={testID}\n tileSize={tileSize}\n width={width}\n zoom={zoom}\n />\n );\n}\n\n/*** Normalize platform press modifiers for multi-selection without passing event objects into contracts. */\nfunction resolveExplorerPressIntent(event: unknown): 'replace' | 'toggle' | 'range' {\n if (typeof event !== 'object' || event === null) return 'replace';\n if ('shiftKey' in event && event.shiftKey === true) return 'range';\n if ('ctrlKey' in event && event.ctrlKey === true) return 'toggle';\n if ('metaKey' in event && event.metaKey === true) return 'toggle';\n return 'replace';\n}\n"]}
1
+ {"version":3,"file":"Explorer.js","sourceRoot":"","sources":["../../../../../src/features/explorer/adapters/inbound/Explorer.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport { Platform, Pressable as NativePressable, View as NativeView } from 'react-native';\n\nimport type { ExplorerItem, ExplorerProps } from '../../../../types/explorer';\nimport { TileGrid } from '../../../grid-view/public';\nimport { Icon } from '../../../icon/public';\nimport { Image } from '../../../image/public';\nimport { withZoraThemeScope } from '../../../theme/adapters/inbound/withZoraThemeScope';\nimport { useZoraTheme } from '../../../theme/composition/useZoraTheme';\nimport { Text } from '../../../typography/public';\nimport { resolveExplorerSelection } from '../../application/resolveExplorerSelection';\nimport {\n type ExplorerPageRequestSignature,\n shouldDispatchExplorerPageRequest,\n} from '../../application/shouldDispatchExplorerPageRequest';\nimport { shouldRequestExplorerPage } from '../../application/shouldRequestExplorerPage';\nimport { ExplorerKeyboardProxy } from './ExplorerKeyboardProxy';\nimport { useExplorerKeyboardFocus } from './useExplorerKeyboardFocus';\n\n/*** Single canonical Explorer implementation for media and file catalogue presentation. */\nexport const Explorer = withZoraThemeScope(ExplorerInner);\n\n/*** Presents provider-neutral asset tiles with controlled or uncontrolled selection. */\nfunction ExplorerInner({\n items,\n selectedIds,\n defaultSelectedIds = [],\n selectionMode = 'single',\n onSelectionChange,\n onActivate,\n width,\n height,\n tileSize = 120,\n zoom = 1,\n loading = false,\n hasMore = false,\n loadingMore = false,\n pagingCollectionId,\n pagingRetryToken,\n onLoadMore,\n permissionStatus = 'granted',\n permissionText,\n onRequestPermission,\n errorText,\n emptyText = 'No items',\n disabled = false,\n readOnly = false,\n interactionPolicy,\n testID,\n}: ExplorerProps) {\n const { theme } = useZoraTheme();\n const [internalSelectedIds, setInternalSelectedIds] =\n React.useState<readonly string[]>(defaultSelectedIds);\n const anchorId = React.useRef<string | null>(null);\n const [columns, setColumns] = React.useState(1);\n const [visibleIds, setVisibleIds] = React.useState<readonly string[]>([]);\n const lastPageRequestRef = React.useRef<ExplorerPageRequestSignature | null>(null);\n const effectiveSelectedIds = selectedIds ?? internalSelectedIds;\n const itemLookup = React.useMemo(() => new Map(items.map((item) => [item.id, item])), [items]);\n const ids = React.useMemo(\n () => items.filter((item) => !item.disabled).map((item) => item.id),\n [items],\n );\n const selected = new Set(effectiveSelectedIds);\n const passive = interactionPolicy === 'passive' || disabled || readOnly;\n\n React.useEffect(() => {\n const request = {\n collectionId: pagingCollectionId,\n loadedItemCount: items.length,\n retryToken: pagingRetryToken,\n };\n if (\n !onLoadMore ||\n !shouldDispatchExplorerPageRequest(lastPageRequestRef.current, request) ||\n !shouldRequestExplorerPage(\n ids,\n visibleIds,\n hasMore,\n loadingMore,\n loading,\n errorText,\n permissionStatus,\n )\n ) {\n return;\n }\n lastPageRequestRef.current = request;\n onLoadMore({ loadedItemCount: items.length });\n }, [\n errorText,\n hasMore,\n ids,\n items.length,\n loading,\n loadingMore,\n onLoadMore,\n pagingCollectionId,\n pagingRetryToken,\n permissionStatus,\n visibleIds,\n ]);\n\n const select = (item: ExplorerItem, intent: 'replace' | 'toggle' | 'range') => {\n if (passive || item.disabled) return;\n const next = resolveExplorerSelection(\n ids,\n effectiveSelectedIds,\n item.id,\n anchorId.current,\n intent,\n selectionMode,\n );\n if (intent !== 'range') anchorId.current = item.id;\n if (selectedIds === undefined) setInternalSelectedIds(next);\n onSelectionChange?.({ selectedIds: next });\n };\n\n const keyboard = useExplorerKeyboardFocus(\n items,\n columns,\n passive,\n visibleIds,\n (targetId, originId, shiftKey) => {\n if (!shiftKey) return;\n anchorId.current ??= originId;\n const nextItem = itemLookup.get(targetId);\n if (nextItem) select(nextItem, 'range');\n },\n );\n\n const renderTile = (tile: { readonly id: string }) => {\n const item = itemLookup.get(tile.id);\n if (!item) return null;\n const isSelected = selected.has(item.id);\n const canInteract = !passive && !item.disabled;\n return (\n <ExplorerKeyboardProxy\n onKeyDown={(key, shiftKey) => keyboard.onKeyDown(item.id, key, shiftKey)}\n >\n <NativePressable\n accessibilityLabel={item.name}\n accessibilityRole=\"button\"\n accessibilityState={{ selected: isSelected, disabled: !canInteract }}\n disabled={!canInteract}\n focusable={canInteract && (Platform.OS !== 'web' || item.id === keyboard.tabStopId)}\n onFocus={() => keyboard.onFocus(item.id)}\n onLongPress={() => select(item, 'toggle')}\n onPress={(event) => {\n const intent = resolveExplorerPressIntent(event.nativeEvent);\n select(item, intent);\n if (selectionMode === 'single' && intent === 'replace') {\n onActivate?.({ id: item.id });\n }\n }}\n style={{\n flex: 1,\n overflow: 'hidden',\n borderRadius: 8,\n borderWidth: isSelected ? 2 : 1,\n borderColor: isSelected ? theme.colors.primary : theme.semantics.neutral.divider,\n backgroundColor: theme.semantics.neutral.surface,\n padding: 8,\n gap: 4,\n }}\n ref={(node) => keyboard.registerTile(item.id, node)}\n testID={`explorer-item-${item.id}`}\n >\n <NativeView style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>\n {item.thumbnailUri ? (\n <Image\n accessibilityLabel={item.name}\n aspectRatio={1}\n source={item.thumbnailUri}\n width=\"100%\"\n />\n ) : (\n <Icon\n name={item.kind === 'folder' ? 'folder-outline' : 'document-outline'}\n size={32}\n />\n )}\n </NativeView>\n <Text numberOfLines={1} variant=\"caption\">\n {item.name}\n </Text>\n </NativePressable>\n </ExplorerKeyboardProxy>\n );\n };\n\n if (permissionStatus === 'denied' || permissionStatus === 'unavailable') {\n return (\n <NativeView testID={testID}>\n <Text>{permissionText ?? 'This collection is unavailable without permission.'}</Text>\n {permissionStatus === 'denied' && onRequestPermission ? (\n <NativePressable accessibilityRole=\"button\" onPress={onRequestPermission}>\n <Text>Grant access</Text>\n </NativePressable>\n ) : null}\n </NativeView>\n );\n }\n if (errorText) return <Text testID={testID}>{errorText}</Text>;\n if (loading) return <Text testID={testID}>Loading…</Text>;\n if (items.length === 0) return <Text testID={testID}>{emptyText}</Text>;\n\n return (\n <NativeView>\n {permissionStatus === 'limited' ? (\n <Text>{permissionText ?? 'Showing the media you have allowed.'}</Text>\n ) : null}\n <TileGrid\n focusedItemId={keyboard.focusedId ?? undefined}\n height={height}\n interactionPolicy={interactionPolicy}\n onColumnsChange={setColumns}\n onVisibleItemIdsChange={setVisibleIds}\n items={items}\n renderItem={renderTile}\n testID={testID}\n tileSize={tileSize}\n width={width}\n zoom={zoom}\n />\n {loadingMore ? <Text>Loading more…</Text> : null}\n </NativeView>\n );\n}\n\n/***\n * Normalize platform press modifiers into selection intents\n * without passing event objects into the public contracts.\n */\nfunction resolveExplorerPressIntent(event: unknown): 'replace' | 'toggle' | 'range' {\n if (typeof event !== 'object' || event === null) return 'replace';\n if ('shiftKey' in event && event.shiftKey === true) return 'range';\n if ('ctrlKey' in event && event.ctrlKey === true) return 'toggle';\n if ('metaKey' in event && event.metaKey === true) return 'toggle';\n return 'replace';\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type React from 'react';
2
+ /*** Provides the native fallback type surface; RNW resolves the web adapter at runtime. */
3
+ export declare function ExplorerKeyboardProxy({ children }: ExplorerKeyboardProxyProps): React.JSX.Element;
4
+ interface ExplorerKeyboardProxyProps {
5
+ readonly children: React.ReactNode;
6
+ readonly onKeyDown: (key: string, shiftKey: boolean) => boolean;
7
+ }
8
+ export {};
9
+ //# sourceMappingURL=ExplorerKeyboardProxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExplorerKeyboardProxy.d.ts","sourceRoot":"","sources":["../../../../../src/features/explorer/adapters/inbound/ExplorerKeyboardProxy.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,2FAA2F;AAC3F,wBAAgB,qBAAqB,CAAC,EAAE,QAAQ,EAAE,EAAE,0BAA0B,qBAE7E;AAED,UAAU,0BAA0B;IAClC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC;CACjE"}
@@ -0,0 +1,5 @@
1
+ /*** Provides the native fallback type surface; RNW resolves the web adapter at runtime. */
2
+ export function ExplorerKeyboardProxy({ children }) {
3
+ return <>{children}</>;
4
+ }
5
+ //# sourceMappingURL=ExplorerKeyboardProxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExplorerKeyboardProxy.js","sourceRoot":"","sources":["../../../../../src/features/explorer/adapters/inbound/ExplorerKeyboardProxy.tsx"],"names":[],"mappings":"AAEA,2FAA2F;AAC3F,MAAM,UAAU,qBAAqB,CAAC,EAAE,QAAQ,EAA8B;IAC5E,OAAO,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC;AACzB,CAAC","sourcesContent":["import type React from 'react';\n\n/*** Provides the native fallback type surface; RNW resolves the web adapter at runtime. */\nexport function ExplorerKeyboardProxy({ children }: ExplorerKeyboardProxyProps) {\n return <>{children}</>;\n}\n\ninterface ExplorerKeyboardProxyProps {\n readonly children: React.ReactNode;\n readonly onKeyDown: (key: string, shiftKey: boolean) => boolean;\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type React from 'react';
2
+ /*** Keeps Explorer navigation portable when a native host has no DOM keyboard event surface. */
3
+ export declare function ExplorerKeyboardProxy({ children }: ExplorerKeyboardProxyProps): React.JSX.Element;
4
+ interface ExplorerKeyboardProxyProps {
5
+ readonly children: React.ReactNode;
6
+ readonly onKeyDown: (key: string, shiftKey: boolean) => boolean;
7
+ }
8
+ export {};
9
+ //# sourceMappingURL=ExplorerKeyboardProxy.native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExplorerKeyboardProxy.native.d.ts","sourceRoot":"","sources":["../../../../../src/features/explorer/adapters/inbound/ExplorerKeyboardProxy.native.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,gGAAgG;AAChG,wBAAgB,qBAAqB,CAAC,EAAE,QAAQ,EAAE,EAAE,0BAA0B,qBAE7E;AAED,UAAU,0BAA0B;IAClC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC;CACjE"}
@@ -0,0 +1,5 @@
1
+ /*** Keeps Explorer navigation portable when a native host has no DOM keyboard event surface. */
2
+ export function ExplorerKeyboardProxy({ children }) {
3
+ return <>{children}</>;
4
+ }
5
+ //# sourceMappingURL=ExplorerKeyboardProxy.native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExplorerKeyboardProxy.native.js","sourceRoot":"","sources":["../../../../../src/features/explorer/adapters/inbound/ExplorerKeyboardProxy.native.tsx"],"names":[],"mappings":"AAEA,gGAAgG;AAChG,MAAM,UAAU,qBAAqB,CAAC,EAAE,QAAQ,EAA8B;IAC5E,OAAO,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC;AACzB,CAAC","sourcesContent":["import type React from 'react';\n\n/*** Keeps Explorer navigation portable when a native host has no DOM keyboard event surface. */\nexport function ExplorerKeyboardProxy({ children }: ExplorerKeyboardProxyProps) {\n return <>{children}</>;\n}\n\ninterface ExplorerKeyboardProxyProps {\n readonly children: React.ReactNode;\n readonly onKeyDown: (key: string, shiftKey: boolean) => boolean;\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type React from 'react';
2
+ /*** Bridges RNW's rendered DOM focus events into Explorer's platform-neutral navigation policy. */
3
+ export declare function ExplorerKeyboardProxy({ children, onKeyDown }: ExplorerKeyboardProxyProps): React.JSX.Element;
4
+ interface ExplorerKeyboardProxyProps {
5
+ readonly children: React.ReactNode;
6
+ readonly onKeyDown: (key: string, shiftKey: boolean) => boolean;
7
+ }
8
+ export {};
9
+ //# sourceMappingURL=ExplorerKeyboardProxy.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExplorerKeyboardProxy.web.d.ts","sourceRoot":"","sources":["../../../../../src/features/explorer/adapters/inbound/ExplorerKeyboardProxy.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,mGAAmG;AACnG,wBAAgB,qBAAqB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,0BAA0B,qBAWxF;AAED,UAAU,0BAA0B;IAClC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC;CACjE"}
@@ -0,0 +1,10 @@
1
+ /*** Bridges RNW's rendered DOM focus events into Explorer's platform-neutral navigation policy. */
2
+ export function ExplorerKeyboardProxy({ children, onKeyDown }) {
3
+ return (<div onKeyDown={(event) => {
4
+ if (onKeyDown(event.key, event.shiftKey))
5
+ event.preventDefault();
6
+ }} style={{ display: 'flex', flex: 1 }}>
7
+ {children}
8
+ </div>);
9
+ }
10
+ //# sourceMappingURL=ExplorerKeyboardProxy.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExplorerKeyboardProxy.web.js","sourceRoot":"","sources":["../../../../../src/features/explorer/adapters/inbound/ExplorerKeyboardProxy.web.tsx"],"names":[],"mappings":"AAEA,mGAAmG;AACnG,MAAM,UAAU,qBAAqB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAA8B;IACvF,OAAO,CACL,CAAC,GAAG,CACF,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE;YACnB,IAAI,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC;gBAAE,KAAK,CAAC,cAAc,EAAE,CAAC;QACnE,CAAC,CAAC,CACF,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAEpC;MAAA,CAAC,QAAQ,CACX;IAAA,EAAE,GAAG,CAAC,CACP,CAAC;AACJ,CAAC","sourcesContent":["import type React from 'react';\n\n/*** Bridges RNW's rendered DOM focus events into Explorer's platform-neutral navigation policy. */\nexport function ExplorerKeyboardProxy({ children, onKeyDown }: ExplorerKeyboardProxyProps) {\n return (\n <div\n onKeyDown={(event) => {\n if (onKeyDown(event.key, event.shiftKey)) event.preventDefault();\n }}\n style={{ display: 'flex', flex: 1 }}\n >\n {children}\n </div>\n );\n}\n\ninterface ExplorerKeyboardProxyProps {\n readonly children: React.ReactNode;\n readonly onKeyDown: (key: string, shiftKey: boolean) => boolean;\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { ExplorerItem } from '../../../../types/explorer';
2
+ /*** Tracks the one keyboard tab stop and restores focus only after a virtualized tile mounts. */
3
+ export declare function useExplorerKeyboardFocus(items: readonly ExplorerItem[], columns: number, disabled: boolean, visibleIds: readonly string[], onNavigate: (targetId: string, originId: string, shiftKey: boolean) => void): {
4
+ focusedId: string | null;
5
+ tabStopId: string | undefined;
6
+ onFocus: (id: string) => void;
7
+ onKeyDown: (id: string, key: string, shiftKey: boolean) => boolean;
8
+ registerTile: (id: string, node: FocusableTile | null) => void;
9
+ };
10
+ interface FocusableTile {
11
+ focus: () => void;
12
+ }
13
+ export {};
14
+ //# sourceMappingURL=useExplorerKeyboardFocus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useExplorerKeyboardFocus.d.ts","sourceRoot":"","sources":["../../../../../src/features/explorer/adapters/inbound/useExplorerKeyboardFocus.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG/D,iGAAiG;AACjG,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,OAAO,EACjB,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI;;;kBAkBpC,MAAM;oBAmBtC,MAAM,OAAO,MAAM,YAAY,OAAO,KAAG,OAAO;uBAbX,MAAM,QAAQ,aAAa,GAAG,IAAI;EAqC/E;AAED,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB"}
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+ import { getExplorerNextFocusId } from '../../application/getExplorerNextFocusId';
3
+ /*** Tracks the one keyboard tab stop and restores focus only after a virtualized tile mounts. */
4
+ export function useExplorerKeyboardFocus(items, columns, disabled, visibleIds, onNavigate) {
5
+ const [focusedId, setFocusedId] = React.useState(null);
6
+ const focusedRef = React.useRef(null);
7
+ const pendingFocusRef = React.useRef(null);
8
+ const tileRefs = React.useRef(new Map());
9
+ const ids = React.useMemo(() => items.map(({ id }) => id), [items]);
10
+ const idSet = React.useMemo(() => new Set(ids), [ids]);
11
+ const disabledIds = React.useMemo(() => new Set(items.filter((item) => item.disabled).map(({ id }) => id)), [items]);
12
+ const visibleEligibleIds = visibleIds.filter((id) => idSet.has(id) && !disabledIds.has(id));
13
+ const tabStopId = focusedId !== null && visibleEligibleIds.includes(focusedId)
14
+ ? focusedId
15
+ : (visibleEligibleIds[0] ?? ids.find((id) => !disabledIds.has(id)));
16
+ const onFocus = React.useCallback((id) => {
17
+ pendingFocusRef.current = null;
18
+ focusedRef.current = id;
19
+ setFocusedId(id);
20
+ }, []);
21
+ const registerTile = React.useCallback((id, node) => {
22
+ if (node === null) {
23
+ tileRefs.current.delete(id);
24
+ return;
25
+ }
26
+ tileRefs.current.set(id, node);
27
+ if (pendingFocusRef.current === id) {
28
+ pendingFocusRef.current = null;
29
+ node.focus();
30
+ }
31
+ }, []);
32
+ const onKeyDown = React.useCallback((id, key, shiftKey) => {
33
+ if (disabled)
34
+ return false;
35
+ if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(key))
36
+ return false;
37
+ const originId = focusedRef.current ?? id;
38
+ const nextId = getExplorerNextFocusId(ids, originId, key, columns, disabledIds);
39
+ if (nextId === null || nextId === originId)
40
+ return true;
41
+ focusedRef.current = nextId;
42
+ pendingFocusRef.current = nextId;
43
+ setFocusedId(nextId);
44
+ onNavigate(nextId, originId, shiftKey);
45
+ const mounted = tileRefs.current.get(nextId);
46
+ if (mounted) {
47
+ pendingFocusRef.current = null;
48
+ mounted.focus();
49
+ }
50
+ return true;
51
+ }, [columns, disabled, disabledIds, ids, onNavigate]);
52
+ return { focusedId, tabStopId, onFocus, onKeyDown, registerTile };
53
+ }
54
+ //# sourceMappingURL=useExplorerKeyboardFocus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useExplorerKeyboardFocus.js","sourceRoot":"","sources":["../../../../../src/features/explorer/adapters/inbound/useExplorerKeyboardFocus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAC;AAElF,iGAAiG;AACjG,MAAM,UAAU,wBAAwB,CACtC,KAA8B,EAC9B,OAAe,EACf,QAAiB,EACjB,UAA6B,EAC7B,UAA2E;IAE3E,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAgB,IAAI,CAAC,CAAC;IACtE,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAgB,IAAI,CAAC,CAAC;IACrD,MAAM,eAAe,GAAG,KAAK,CAAC,MAAM,CAAgB,IAAI,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,GAAG,EAAyB,CAAC,CAAC;IAChE,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACpE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvD,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAC/B,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EACxE,CAAC,KAAK,CAAC,CACR,CAAC;IACF,MAAM,kBAAkB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5F,MAAM,SAAS,GACb,SAAS,KAAK,IAAI,IAAI,kBAAkB,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC1D,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAExE,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,EAAU,EAAE,EAAE;QAC/C,eAAe,CAAC,OAAO,GAAG,IAAI,CAAC;QAC/B,UAAU,CAAC,OAAO,GAAG,EAAE,CAAC;QACxB,YAAY,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,YAAY,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,EAAU,EAAE,IAA0B,EAAE,EAAE;QAChF,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QACD,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC/B,IAAI,eAAe,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;YACnC,eAAe,CAAC,OAAO,GAAG,IAAI,CAAC;YAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,CACjC,CAAC,EAAU,EAAE,GAAW,EAAE,QAAiB,EAAW,EAAE;QACtD,IAAI,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC3B,IAAI,CAAC,CAAC,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;YACnF,OAAO,KAAK,CAAC;QACf,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,IAAI,EAAE,CAAC;QAC1C,MAAM,MAAM,GAAG,sBAAsB,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;QAChF,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAExD,UAAU,CAAC,OAAO,GAAG,MAAM,CAAC;QAC5B,eAAe,CAAC,OAAO,GAAG,MAAM,CAAC;QACjC,YAAY,CAAC,MAAM,CAAC,CAAC;QACrB,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAEvC,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,OAAO,EAAE,CAAC;YACZ,eAAe,CAAC,OAAO,GAAG,IAAI,CAAC;YAC/B,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,EACD,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,CAAC,CAClD,CAAC;IAEF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;AACpE,CAAC","sourcesContent":["import React from 'react';\n\nimport type { ExplorerItem } from '../../../../types/explorer';\nimport { getExplorerNextFocusId } from '../../application/getExplorerNextFocusId';\n\n/*** Tracks the one keyboard tab stop and restores focus only after a virtualized tile mounts. */\nexport function useExplorerKeyboardFocus(\n items: readonly ExplorerItem[],\n columns: number,\n disabled: boolean,\n visibleIds: readonly string[],\n onNavigate: (targetId: string, originId: string, shiftKey: boolean) => void,\n) {\n const [focusedId, setFocusedId] = React.useState<string | null>(null);\n const focusedRef = React.useRef<string | null>(null);\n const pendingFocusRef = React.useRef<string | null>(null);\n const tileRefs = React.useRef(new Map<string, FocusableTile>());\n const ids = React.useMemo(() => items.map(({ id }) => id), [items]);\n const idSet = React.useMemo(() => new Set(ids), [ids]);\n const disabledIds = React.useMemo(\n () => new Set(items.filter((item) => item.disabled).map(({ id }) => id)),\n [items],\n );\n const visibleEligibleIds = visibleIds.filter((id) => idSet.has(id) && !disabledIds.has(id));\n const tabStopId =\n focusedId !== null && visibleEligibleIds.includes(focusedId)\n ? focusedId\n : (visibleEligibleIds[0] ?? ids.find((id) => !disabledIds.has(id)));\n\n const onFocus = React.useCallback((id: string) => {\n pendingFocusRef.current = null;\n focusedRef.current = id;\n setFocusedId(id);\n }, []);\n\n const registerTile = React.useCallback((id: string, node: FocusableTile | null) => {\n if (node === null) {\n tileRefs.current.delete(id);\n return;\n }\n tileRefs.current.set(id, node);\n if (pendingFocusRef.current === id) {\n pendingFocusRef.current = null;\n node.focus();\n }\n }, []);\n\n const onKeyDown = React.useCallback(\n (id: string, key: string, shiftKey: boolean): boolean => {\n if (disabled) return false;\n if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(key))\n return false;\n const originId = focusedRef.current ?? id;\n const nextId = getExplorerNextFocusId(ids, originId, key, columns, disabledIds);\n if (nextId === null || nextId === originId) return true;\n\n focusedRef.current = nextId;\n pendingFocusRef.current = nextId;\n setFocusedId(nextId);\n onNavigate(nextId, originId, shiftKey);\n\n const mounted = tileRefs.current.get(nextId);\n if (mounted) {\n pendingFocusRef.current = null;\n mounted.focus();\n }\n return true;\n },\n [columns, disabled, disabledIds, ids, onNavigate],\n );\n\n return { focusedId, tabStopId, onFocus, onKeyDown, registerTile };\n}\n\ninterface FocusableTile {\n focus: () => void;\n}\n"]}
@@ -0,0 +1,6 @@
1
+ /***
2
+ * Resolve focus along actual tile positions while skipping unavailable items
3
+ * without collapsing the visual row geometry.
4
+ */
5
+ export declare function getExplorerNextFocusId(ids: readonly string[], currentId: string | null, key: string, columns: number, disabledIds?: ReadonlySet<string>): string | null;
6
+ //# sourceMappingURL=getExplorerNextFocusId.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getExplorerNextFocusId.d.ts","sourceRoot":"","sources":["../../../../src/features/explorer/application/getExplorerNextFocusId.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,SAAS,MAAM,EAAE,EACtB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,WAAW,GAAE,WAAW,CAAC,MAAM,CAAa,GAC3C,MAAM,GAAG,IAAI,CA6Bf"}
@@ -0,0 +1,53 @@
1
+ /***
2
+ * Resolve focus along actual tile positions while skipping unavailable items
3
+ * without collapsing the visual row geometry.
4
+ */
5
+ export function getExplorerNextFocusId(ids, currentId, key, columns, disabledIds = new Set()) {
6
+ const delta = resolveFocusDelta(key, columns);
7
+ if (delta === null || ids.length === 0)
8
+ return null;
9
+ const firstIndex = ids.findIndex((id) => !disabledIds.has(id));
10
+ if (firstIndex < 0)
11
+ return null;
12
+ const currentIndex = Math.max(firstIndex, ids.indexOf(currentId ?? ''));
13
+ const candidateIndex = delta === Number.NEGATIVE_INFINITY
14
+ ? 0
15
+ : delta === Number.POSITIVE_INFINITY
16
+ ? ids.length - 1
17
+ : Math.max(0, Math.min(ids.length - 1, currentIndex + delta));
18
+ const direction = delta === Number.NEGATIVE_INFINITY
19
+ ? 1
20
+ : delta === Number.POSITIVE_INFINITY
21
+ ? -1
22
+ : Math.sign(delta);
23
+ for (let index = candidateIndex; index >= 0 && index < ids.length; index += direction) {
24
+ const candidate = ids.at(index);
25
+ if (candidate !== undefined && !disabledIds.has(candidate))
26
+ return candidate;
27
+ }
28
+ const current = ids.at(currentIndex);
29
+ return current !== undefined && !disabledIds.has(current)
30
+ ? current
31
+ : (ids.at(firstIndex) ?? null);
32
+ }
33
+ /*** Translate navigation keys into collection-relative movements. */
34
+ function resolveFocusDelta(key, columns) {
35
+ const rowSize = Math.max(1, Math.floor(columns));
36
+ switch (key) {
37
+ case 'ArrowLeft':
38
+ return -1;
39
+ case 'ArrowRight':
40
+ return 1;
41
+ case 'ArrowUp':
42
+ return -rowSize;
43
+ case 'ArrowDown':
44
+ return rowSize;
45
+ case 'Home':
46
+ return Number.NEGATIVE_INFINITY;
47
+ case 'End':
48
+ return Number.POSITIVE_INFINITY;
49
+ default:
50
+ return null;
51
+ }
52
+ }
53
+ //# sourceMappingURL=getExplorerNextFocusId.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getExplorerNextFocusId.js","sourceRoot":"","sources":["../../../../src/features/explorer/application/getExplorerNextFocusId.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,GAAsB,EACtB,SAAwB,EACxB,GAAW,EACX,OAAe,EACf,cAAmC,IAAI,GAAG,EAAE;IAE5C,MAAM,KAAK,GAAG,iBAAiB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC9C,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpD,MAAM,UAAU,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/D,IAAI,UAAU,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAEhC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;IACxE,MAAM,cAAc,GAClB,KAAK,KAAK,MAAM,CAAC,iBAAiB;QAChC,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,iBAAiB;YAClC,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC;YAChB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC;IACpE,MAAM,SAAS,GACb,KAAK,KAAK,MAAM,CAAC,iBAAiB;QAChC,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,iBAAiB;YAClC,CAAC,CAAC,CAAC,CAAC;YACJ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAEzB,KAAK,IAAI,KAAK,GAAG,cAAc,EAAE,KAAK,IAAI,CAAC,IAAI,KAAK,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,IAAI,SAAS,EAAE,CAAC;QACtF,MAAM,SAAS,GAAG,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC/E,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC;IACrC,OAAO,OAAO,KAAK,SAAS,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC;QACvD,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,qEAAqE;AACrE,SAAS,iBAAiB,CAAC,GAAW,EAAE,OAAe;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,WAAW;YACd,OAAO,CAAC,CAAC,CAAC;QACZ,KAAK,YAAY;YACf,OAAO,CAAC,CAAC;QACX,KAAK,SAAS;YACZ,OAAO,CAAC,OAAO,CAAC;QAClB,KAAK,WAAW;YACd,OAAO,OAAO,CAAC;QACjB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC,iBAAiB,CAAC;QAClC,KAAK,KAAK;YACR,OAAO,MAAM,CAAC,iBAAiB,CAAC;QAClC;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC","sourcesContent":["/***\n * Resolve focus along actual tile positions while skipping unavailable items\n * without collapsing the visual row geometry.\n */\nexport function getExplorerNextFocusId(\n ids: readonly string[],\n currentId: string | null,\n key: string,\n columns: number,\n disabledIds: ReadonlySet<string> = new Set(),\n): string | null {\n const delta = resolveFocusDelta(key, columns);\n if (delta === null || ids.length === 0) return null;\n\n const firstIndex = ids.findIndex((id) => !disabledIds.has(id));\n if (firstIndex < 0) return null;\n\n const currentIndex = Math.max(firstIndex, ids.indexOf(currentId ?? ''));\n const candidateIndex =\n delta === Number.NEGATIVE_INFINITY\n ? 0\n : delta === Number.POSITIVE_INFINITY\n ? ids.length - 1\n : Math.max(0, Math.min(ids.length - 1, currentIndex + delta));\n const direction =\n delta === Number.NEGATIVE_INFINITY\n ? 1\n : delta === Number.POSITIVE_INFINITY\n ? -1\n : Math.sign(delta);\n\n for (let index = candidateIndex; index >= 0 && index < ids.length; index += direction) {\n const candidate = ids.at(index);\n if (candidate !== undefined && !disabledIds.has(candidate)) return candidate;\n }\n const current = ids.at(currentIndex);\n return current !== undefined && !disabledIds.has(current)\n ? current\n : (ids.at(firstIndex) ?? null);\n}\n\n/*** Translate navigation keys into collection-relative movements. */\nfunction resolveFocusDelta(key: string, columns: number): number | null {\n const rowSize = Math.max(1, Math.floor(columns));\n switch (key) {\n case 'ArrowLeft':\n return -1;\n case 'ArrowRight':\n return 1;\n case 'ArrowUp':\n return -rowSize;\n case 'ArrowDown':\n return rowSize;\n case 'Home':\n return Number.NEGATIVE_INFINITY;\n case 'End':\n return Number.POSITIVE_INFINITY;\n default:\n return null;\n }\n}\n"]}
@@ -0,0 +1,8 @@
1
+ export interface ExplorerPageRequestSignature {
2
+ readonly collectionId: string | undefined;
3
+ readonly loadedItemCount: number;
4
+ readonly retryToken: string | undefined;
5
+ }
6
+ /*** Preserves one request per provider page boundary until its collection, row count, or retry token changes. */
7
+ export declare function shouldDispatchExplorerPageRequest(previous: ExplorerPageRequestSignature | null, next: ExplorerPageRequestSignature): boolean;
8
+ //# sourceMappingURL=shouldDispatchExplorerPageRequest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shouldDispatchExplorerPageRequest.d.ts","sourceRoot":"","sources":["../../../../src/features/explorer/application/shouldDispatchExplorerPageRequest.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAED,iHAAiH;AACjH,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,4BAA4B,GAAG,IAAI,EAC7C,IAAI,EAAE,4BAA4B,GACjC,OAAO,CAOT"}
@@ -0,0 +1,9 @@
1
+ /*** Preserves one request per provider page boundary until its collection, row count, or retry token changes. */
2
+ export function shouldDispatchExplorerPageRequest(previous, next) {
3
+ if (previous === null)
4
+ return true;
5
+ return (previous.collectionId !== next.collectionId ||
6
+ previous.loadedItemCount !== next.loadedItemCount ||
7
+ previous.retryToken !== next.retryToken);
8
+ }
9
+ //# sourceMappingURL=shouldDispatchExplorerPageRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shouldDispatchExplorerPageRequest.js","sourceRoot":"","sources":["../../../../src/features/explorer/application/shouldDispatchExplorerPageRequest.ts"],"names":[],"mappings":"AAMA,iHAAiH;AACjH,MAAM,UAAU,iCAAiC,CAC/C,QAA6C,EAC7C,IAAkC;IAElC,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,CACL,QAAQ,CAAC,YAAY,KAAK,IAAI,CAAC,YAAY;QAC3C,QAAQ,CAAC,eAAe,KAAK,IAAI,CAAC,eAAe;QACjD,QAAQ,CAAC,UAAU,KAAK,IAAI,CAAC,UAAU,CACxC,CAAC;AACJ,CAAC","sourcesContent":["export interface ExplorerPageRequestSignature {\n readonly collectionId: string | undefined;\n readonly loadedItemCount: number;\n readonly retryToken: string | undefined;\n}\n\n/*** Preserves one request per provider page boundary until its collection, row count, or retry token changes. */\nexport function shouldDispatchExplorerPageRequest(\n previous: ExplorerPageRequestSignature | null,\n next: ExplorerPageRequestSignature,\n): boolean {\n if (previous === null) return true;\n return (\n previous.collectionId !== next.collectionId ||\n previous.loadedItemCount !== next.loadedItemCount ||\n previous.retryToken !== next.retryToken\n );\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { ExplorerPermissionStatus } from '../../../types/explorer';
2
+ /*** Determines whether the mounted, accessible viewport has reached the provider-owned page boundary. */
3
+ export declare function shouldRequestExplorerPage(itemIds: readonly string[], visibleIds: readonly string[], hasMore: boolean, loadingMore: boolean, loading: boolean, errorText: string | undefined, permissionStatus: ExplorerPermissionStatus, threshold?: number): boolean;
4
+ //# sourceMappingURL=shouldRequestExplorerPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shouldRequestExplorerPage.d.ts","sourceRoot":"","sources":["../../../../src/features/explorer/application/shouldRequestExplorerPage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAExE,yGAAyG;AACzG,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,OAAO,EACpB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,gBAAgB,EAAE,wBAAwB,EAC1C,SAAS,SAAK,GACb,OAAO,CAaT"}
@@ -0,0 +1,14 @@
1
+ /*** Determines whether the mounted, accessible viewport has reached the provider-owned page boundary. */
2
+ export function shouldRequestExplorerPage(itemIds, visibleIds, hasMore, loadingMore, loading, errorText, permissionStatus, threshold = 12) {
3
+ if (!hasMore ||
4
+ loadingMore ||
5
+ loading ||
6
+ errorText !== undefined ||
7
+ (permissionStatus !== 'granted' && permissionStatus !== 'limited') ||
8
+ visibleIds.length === 0) {
9
+ return false;
10
+ }
11
+ const firstPagingIndex = Math.max(0, itemIds.length - threshold);
12
+ return visibleIds.some((id) => itemIds.indexOf(id) >= firstPagingIndex);
13
+ }
14
+ //# sourceMappingURL=shouldRequestExplorerPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shouldRequestExplorerPage.js","sourceRoot":"","sources":["../../../../src/features/explorer/application/shouldRequestExplorerPage.ts"],"names":[],"mappings":"AAEA,yGAAyG;AACzG,MAAM,UAAU,yBAAyB,CACvC,OAA0B,EAC1B,UAA6B,EAC7B,OAAgB,EAChB,WAAoB,EACpB,OAAgB,EAChB,SAA6B,EAC7B,gBAA0C,EAC1C,SAAS,GAAG,EAAE;IAEd,IACE,CAAC,OAAO;QACR,WAAW;QACX,OAAO;QACP,SAAS,KAAK,SAAS;QACvB,CAAC,gBAAgB,KAAK,SAAS,IAAI,gBAAgB,KAAK,SAAS,CAAC;QAClE,UAAU,CAAC,MAAM,KAAK,CAAC,EACvB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACjE,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,gBAAgB,CAAC,CAAC;AAC1E,CAAC","sourcesContent":["import type { ExplorerPermissionStatus } from '../../../types/explorer';\n\n/*** Determines whether the mounted, accessible viewport has reached the provider-owned page boundary. */\nexport function shouldRequestExplorerPage(\n itemIds: readonly string[],\n visibleIds: readonly string[],\n hasMore: boolean,\n loadingMore: boolean,\n loading: boolean,\n errorText: string | undefined,\n permissionStatus: ExplorerPermissionStatus,\n threshold = 12,\n): boolean {\n if (\n !hasMore ||\n loadingMore ||\n loading ||\n errorText !== undefined ||\n (permissionStatus !== 'granted' && permissionStatus !== 'limited') ||\n visibleIds.length === 0\n ) {\n return false;\n }\n const firstPagingIndex = Math.max(0, itemIds.length - threshold);\n return visibleIds.some((id) => itemIds.indexOf(id) >= firstPagingIndex);\n}\n"]}