@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
@@ -36,6 +36,42 @@ function getVisibleGridItems(items, viewport, overscanPixels = 0) {
36
36
  const bounds = getVisibleWorldBounds(viewport, overscanPixels);
37
37
  return items.filter((item) => item.x <= bounds.x + bounds.width && item.x + item.width >= bounds.x && item.y <= bounds.y + bounds.height && item.y + item.height >= bounds.y);
38
38
  }
39
+ // node_modules/@ankhorage/grid-view/dist/features/viewport/revealWorldRect.js
40
+ function revealWorldRect(viewport, rect, paddingPixels = 0) {
41
+ validateRevealInput(viewport, rect, paddingPixels);
42
+ return {
43
+ ...viewport,
44
+ offsetX: revealAxis(viewport.offsetX, viewport.width, viewport.pixelsPerUnitX, rect.x, rect.width, paddingPixels),
45
+ offsetY: revealAxis(viewport.offsetY, viewport.height, viewport.pixelsPerUnitY, rect.y, rect.height, paddingPixels)
46
+ };
47
+ }
48
+ function revealAxis(offset, viewportPixels, pixelsPerUnit, rectStart, rectSize, paddingPixels) {
49
+ const visibleStart = offset + paddingPixels / pixelsPerUnit;
50
+ const visibleEnd = offset + (viewportPixels - paddingPixels) / pixelsPerUnit;
51
+ const rectEnd = rectStart + rectSize;
52
+ if (rectSize > visibleEnd - visibleStart || rectStart < visibleStart) {
53
+ return rectStart - paddingPixels / pixelsPerUnit;
54
+ }
55
+ if (rectEnd > visibleEnd) {
56
+ return rectEnd - (viewportPixels - paddingPixels) / pixelsPerUnit;
57
+ }
58
+ return offset;
59
+ }
60
+ function validateRevealInput(viewport, rect, paddingPixels) {
61
+ if (![
62
+ viewport.width,
63
+ viewport.height,
64
+ viewport.offsetX,
65
+ viewport.offsetY,
66
+ viewport.pixelsPerUnitX,
67
+ viewport.pixelsPerUnitY
68
+ ].every(Number.isFinite) || viewport.width <= 0 || viewport.height <= 0 || viewport.pixelsPerUnitX <= 0 || viewport.pixelsPerUnitY <= 0 || !Number.isFinite(paddingPixels) || paddingPixels < 0 || paddingPixels * 2 >= viewport.width || paddingPixels * 2 >= viewport.height) {
69
+ throw new RangeError("Viewport geometry must be finite and padding must leave visible space.");
70
+ }
71
+ if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite) || rect.width < 0 || rect.height < 0) {
72
+ throw new RangeError("World rectangles must have finite nonnegative dimensions.");
73
+ }
74
+ }
39
75
  // node_modules/@ankhorage/grid-view/dist/features/viewport/worldToViewport.js
40
76
  function worldToViewport(point, viewport) {
41
77
  return {
@@ -55,13 +91,18 @@ function GridView2({
55
91
  height,
56
92
  zoom = 1,
57
93
  overscanPixels = 160,
94
+ focusedItemId,
95
+ revealPaddingPixels = 8,
58
96
  interactionPolicy,
59
97
  onViewportChange,
98
+ onVisibleItemIdsChange,
60
99
  renderItem,
61
100
  testID
62
101
  }) {
63
102
  const [scrollX, setScrollX] = React.useState(0);
64
103
  const [scrollY, setScrollY] = React.useState(0);
104
+ const horizontalScrollRef = React.useRef(null);
105
+ const verticalScrollRef = React.useRef(null);
65
106
  const scale = Math.max(0.01, zoom);
66
107
  const viewport = React.useMemo(() => ({
67
108
  width,
@@ -72,10 +113,40 @@ function GridView2({
72
113
  pixelsPerUnitY: scale
73
114
  }), [height, scale, scrollX, scrollY, width]);
74
115
  const visibleItems = React.useMemo(() => getVisibleGridItems(items, viewport, overscanPixels), [items, overscanPixels, viewport]);
116
+ const viewportVisibleIds = React.useMemo(() => getVisibleGridItems(visibleItems, viewport).map((item) => item.id), [viewport, visibleItems]);
117
+ const previousVisibleIdsRef = React.useRef([]);
118
+ React.useEffect(() => {
119
+ if (!onVisibleItemIdsChange)
120
+ return;
121
+ const previous = previousVisibleIdsRef.current;
122
+ if (previous.length === viewportVisibleIds.length && previous.every((id, index) => id === viewportVisibleIds.at(index))) {
123
+ return;
124
+ }
125
+ previousVisibleIdsRef.current = viewportVisibleIds;
126
+ onVisibleItemIdsChange(viewportVisibleIds);
127
+ }, [onVisibleItemIdsChange, viewportVisibleIds]);
75
128
  React.useEffect(() => {
76
129
  onViewportChange?.(viewport);
77
130
  }, [onViewportChange, viewport]);
131
+ const viewportRef = React.useRef(viewport);
132
+ React.useEffect(() => {
133
+ viewportRef.current = viewport;
134
+ }, [viewport]);
135
+ React.useEffect(() => {
136
+ const currentViewport = viewportRef.current;
137
+ const focusedItem = items.find((item) => item.id === focusedItemId);
138
+ if (!focusedItem)
139
+ return;
140
+ const revealed = revealWorldRect(currentViewport, focusedItem, revealPaddingPixels);
141
+ if (revealed.offsetX !== currentViewport.offsetX) {
142
+ horizontalScrollRef.current?.scrollTo({ x: revealed.offsetX * scale, animated: true });
143
+ }
144
+ if (revealed.offsetY !== currentViewport.offsetY) {
145
+ verticalScrollRef.current?.scrollTo({ y: revealed.offsetY * scale, animated: true });
146
+ }
147
+ }, [focusedItemId, height, items, revealPaddingPixels, scale, width]);
78
148
  return /* @__PURE__ */ jsx(import_react_native.ScrollView, {
149
+ ref: horizontalScrollRef,
79
150
  horizontal: true,
80
151
  scrollEnabled: interactionPolicy !== "passive",
81
152
  scrollEventThrottle: 32,
@@ -84,6 +155,7 @@ function GridView2({
84
155
  testID,
85
156
  onScroll: (event) => setScrollX(event.nativeEvent.contentOffset.x),
86
157
  children: /* @__PURE__ */ jsx(import_react_native.ScrollView, {
158
+ ref: verticalScrollRef,
87
159
  scrollEnabled: interactionPolicy !== "passive",
88
160
  scrollEventThrottle: 32,
89
161
  showsVerticalScrollIndicator: true,
@@ -148,6 +220,10 @@ function TileGrid2({
148
220
  gap = 12,
149
221
  zoom = 1,
150
222
  overscanPixels,
223
+ focusedItemId,
224
+ revealPaddingPixels,
225
+ onColumnsChange,
226
+ onVisibleItemIdsChange,
151
227
  renderItem,
152
228
  interactionPolicy,
153
229
  testID
@@ -156,6 +232,10 @@ function TileGrid2({
156
232
  const resolvedWidth = width ?? measuredWidth;
157
233
  const layout = React2.useMemo(() => layoutTileGrid(items, resolvedWidth, tileSize, gap, zoom), [items, resolvedWidth, tileSize, gap, zoom]);
158
234
  const itemLookup = React2.useMemo(() => new Map(items.map((item) => [item.id, item])), [items]);
235
+ React2.useEffect(() => {
236
+ if (resolvedWidth > 0)
237
+ onColumnsChange?.(layout.columns);
238
+ }, [layout.columns, onColumnsChange, resolvedWidth]);
159
239
  return /* @__PURE__ */ jsx2(import_react_native2.View, {
160
240
  onLayout: (event) => setMeasuredWidth(event.nativeEvent.layout.width),
161
241
  style: { width, height, overflow: "hidden" },
@@ -164,13 +244,16 @@ function TileGrid2({
164
244
  contentHeight: layout.height,
165
245
  contentWidth: Math.max(resolvedWidth / zoom, layout.width),
166
246
  height,
247
+ focusedItemId,
167
248
  interactionPolicy,
168
249
  items: layout.items,
250
+ onVisibleItemIdsChange,
169
251
  overscanPixels,
170
252
  renderItem: (item) => {
171
253
  const source = itemLookup.get(item.id);
172
254
  return source ? renderItem(source) : null;
173
255
  },
256
+ revealPaddingPixels,
174
257
  width: resolvedWidth,
175
258
  zoom
176
259
  }) : null
@@ -0,0 +1,397 @@
1
+ 'use client';
2
+ import {
3
+ TileGrid2
4
+ } from "./Accordion-54k0ncs3.js";
5
+ import {
6
+ withZoraThemeScope
7
+ } from "./Accordion-42jfy1a1.js";
8
+ import {
9
+ Icon2
10
+ } from "./Accordion-bb8y34wh.js";
11
+ import {
12
+ Image2
13
+ } from "./Accordion-mej71qnw.js";
14
+ import {
15
+ Text3
16
+ } from "./Accordion-afmybp67.js";
17
+ import {
18
+ applySelectionIntent
19
+ } from "./Accordion-n4et7etk.js";
20
+ import {
21
+ useZoraTheme2
22
+ } from "./Accordion-srjya6dz.js";
23
+ import {
24
+ require_cjs
25
+ } from "./Accordion-xgpzh4wq.js";
26
+ import {
27
+ __toESM
28
+ } from "./Accordion-nh0cs9hq.js";
29
+
30
+ // src/features/explorer/adapters/inbound/Explorer.tsx
31
+ var import_react_native = __toESM(require_cjs(), 1);
32
+ import React2 from "react";
33
+
34
+ // src/features/explorer/application/resolveExplorerSelection.ts
35
+ function resolveExplorerSelection(orderedIds, currentIds, targetId, anchorId, intent, mode) {
36
+ const targetIndex = orderedIds.indexOf(targetId);
37
+ if (targetIndex < 0)
38
+ return currentIds;
39
+ if (mode === "single" || intent === "replace")
40
+ return applySelectionIntent(currentIds, targetId, "replace");
41
+ if (intent === "toggle") {
42
+ return applySelectionIntent(currentIds, targetId, "toggle");
43
+ }
44
+ const anchorIndex = anchorId === null ? -1 : orderedIds.indexOf(anchorId);
45
+ if (anchorIndex < 0)
46
+ return [targetId];
47
+ const start = Math.min(anchorIndex, targetIndex);
48
+ const end = Math.max(anchorIndex, targetIndex);
49
+ return orderedIds.slice(start, end + 1);
50
+ }
51
+
52
+ // src/features/explorer/application/shouldDispatchExplorerPageRequest.ts
53
+ function shouldDispatchExplorerPageRequest(previous, next) {
54
+ if (previous === null)
55
+ return true;
56
+ return previous.collectionId !== next.collectionId || previous.loadedItemCount !== next.loadedItemCount || previous.retryToken !== next.retryToken;
57
+ }
58
+
59
+ // src/features/explorer/application/shouldRequestExplorerPage.ts
60
+ function shouldRequestExplorerPage(itemIds, visibleIds, hasMore, loadingMore, loading, errorText, permissionStatus, threshold = 12) {
61
+ if (!hasMore || loadingMore || loading || errorText !== undefined || permissionStatus !== "granted" && permissionStatus !== "limited" || visibleIds.length === 0) {
62
+ return false;
63
+ }
64
+ const firstPagingIndex = Math.max(0, itemIds.length - threshold);
65
+ return visibleIds.some((id) => itemIds.indexOf(id) >= firstPagingIndex);
66
+ }
67
+
68
+ // src/features/explorer/adapters/inbound/ExplorerKeyboardProxy.web.tsx
69
+ import { jsx } from "react/jsx-runtime";
70
+ function ExplorerKeyboardProxy({ children, onKeyDown }) {
71
+ return /* @__PURE__ */ jsx("div", {
72
+ onKeyDown: (event) => {
73
+ if (onKeyDown(event.key, event.shiftKey))
74
+ event.preventDefault();
75
+ },
76
+ style: { display: "flex", flex: 1 },
77
+ children
78
+ });
79
+ }
80
+
81
+ // src/features/explorer/adapters/inbound/useExplorerKeyboardFocus.ts
82
+ import React from "react";
83
+
84
+ // src/features/explorer/application/getExplorerNextFocusId.ts
85
+ function getExplorerNextFocusId(ids, currentId, key, columns, disabledIds = new Set) {
86
+ const delta = resolveFocusDelta(key, columns);
87
+ if (delta === null || ids.length === 0)
88
+ return null;
89
+ const firstIndex = ids.findIndex((id) => !disabledIds.has(id));
90
+ if (firstIndex < 0)
91
+ return null;
92
+ const currentIndex = Math.max(firstIndex, ids.indexOf(currentId ?? ""));
93
+ const candidateIndex = delta === Number.NEGATIVE_INFINITY ? 0 : delta === Number.POSITIVE_INFINITY ? ids.length - 1 : Math.max(0, Math.min(ids.length - 1, currentIndex + delta));
94
+ const direction = delta === Number.NEGATIVE_INFINITY ? 1 : delta === Number.POSITIVE_INFINITY ? -1 : Math.sign(delta);
95
+ for (let index = candidateIndex;index >= 0 && index < ids.length; index += direction) {
96
+ const candidate = ids.at(index);
97
+ if (candidate !== undefined && !disabledIds.has(candidate))
98
+ return candidate;
99
+ }
100
+ const current = ids.at(currentIndex);
101
+ return current !== undefined && !disabledIds.has(current) ? current : ids.at(firstIndex) ?? null;
102
+ }
103
+ function resolveFocusDelta(key, columns) {
104
+ const rowSize = Math.max(1, Math.floor(columns));
105
+ switch (key) {
106
+ case "ArrowLeft":
107
+ return -1;
108
+ case "ArrowRight":
109
+ return 1;
110
+ case "ArrowUp":
111
+ return -rowSize;
112
+ case "ArrowDown":
113
+ return rowSize;
114
+ case "Home":
115
+ return Number.NEGATIVE_INFINITY;
116
+ case "End":
117
+ return Number.POSITIVE_INFINITY;
118
+ default:
119
+ return null;
120
+ }
121
+ }
122
+
123
+ // src/features/explorer/adapters/inbound/useExplorerKeyboardFocus.ts
124
+ function useExplorerKeyboardFocus(items, columns, disabled, visibleIds, onNavigate) {
125
+ const [focusedId, setFocusedId] = React.useState(null);
126
+ const focusedRef = React.useRef(null);
127
+ const pendingFocusRef = React.useRef(null);
128
+ const tileRefs = React.useRef(new Map);
129
+ const ids = React.useMemo(() => items.map(({ id }) => id), [items]);
130
+ const idSet = React.useMemo(() => new Set(ids), [ids]);
131
+ const disabledIds = React.useMemo(() => new Set(items.filter((item) => item.disabled).map(({ id }) => id)), [items]);
132
+ const visibleEligibleIds = visibleIds.filter((id) => idSet.has(id) && !disabledIds.has(id));
133
+ const tabStopId = focusedId !== null && visibleEligibleIds.includes(focusedId) ? focusedId : visibleEligibleIds[0] ?? ids.find((id) => !disabledIds.has(id));
134
+ const onFocus = React.useCallback((id) => {
135
+ pendingFocusRef.current = null;
136
+ focusedRef.current = id;
137
+ setFocusedId(id);
138
+ }, []);
139
+ const registerTile = React.useCallback((id, node) => {
140
+ if (node === null) {
141
+ tileRefs.current.delete(id);
142
+ return;
143
+ }
144
+ tileRefs.current.set(id, node);
145
+ if (pendingFocusRef.current === id) {
146
+ pendingFocusRef.current = null;
147
+ node.focus();
148
+ }
149
+ }, []);
150
+ const onKeyDown = React.useCallback((id, key, shiftKey) => {
151
+ if (disabled)
152
+ return false;
153
+ if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(key))
154
+ return false;
155
+ const originId = focusedRef.current ?? id;
156
+ const nextId = getExplorerNextFocusId(ids, originId, key, columns, disabledIds);
157
+ if (nextId === null || nextId === originId)
158
+ return true;
159
+ focusedRef.current = nextId;
160
+ pendingFocusRef.current = nextId;
161
+ setFocusedId(nextId);
162
+ onNavigate(nextId, originId, shiftKey);
163
+ const mounted = tileRefs.current.get(nextId);
164
+ if (mounted) {
165
+ pendingFocusRef.current = null;
166
+ mounted.focus();
167
+ }
168
+ return true;
169
+ }, [columns, disabled, disabledIds, ids, onNavigate]);
170
+ return { focusedId, tabStopId, onFocus, onKeyDown, registerTile };
171
+ }
172
+
173
+ // src/features/explorer/adapters/inbound/Explorer.tsx
174
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
175
+ var Explorer2 = withZoraThemeScope(ExplorerInner);
176
+ function ExplorerInner({
177
+ items,
178
+ selectedIds,
179
+ defaultSelectedIds = [],
180
+ selectionMode = "single",
181
+ onSelectionChange,
182
+ onActivate,
183
+ width,
184
+ height,
185
+ tileSize = 120,
186
+ zoom = 1,
187
+ loading = false,
188
+ hasMore = false,
189
+ loadingMore = false,
190
+ pagingCollectionId,
191
+ pagingRetryToken,
192
+ onLoadMore,
193
+ permissionStatus = "granted",
194
+ permissionText,
195
+ onRequestPermission,
196
+ errorText,
197
+ emptyText = "No items",
198
+ disabled = false,
199
+ readOnly = false,
200
+ interactionPolicy,
201
+ testID
202
+ }) {
203
+ const { theme } = useZoraTheme2();
204
+ const [internalSelectedIds, setInternalSelectedIds] = React2.useState(defaultSelectedIds);
205
+ const anchorId = React2.useRef(null);
206
+ const [columns, setColumns] = React2.useState(1);
207
+ const [visibleIds, setVisibleIds] = React2.useState([]);
208
+ const lastPageRequestRef = React2.useRef(null);
209
+ const effectiveSelectedIds = selectedIds ?? internalSelectedIds;
210
+ const itemLookup = React2.useMemo(() => new Map(items.map((item) => [item.id, item])), [items]);
211
+ const ids = React2.useMemo(() => items.filter((item) => !item.disabled).map((item) => item.id), [items]);
212
+ const selected = new Set(effectiveSelectedIds);
213
+ const passive = interactionPolicy === "passive" || disabled || readOnly;
214
+ React2.useEffect(() => {
215
+ const request = {
216
+ collectionId: pagingCollectionId,
217
+ loadedItemCount: items.length,
218
+ retryToken: pagingRetryToken
219
+ };
220
+ if (!onLoadMore || !shouldDispatchExplorerPageRequest(lastPageRequestRef.current, request) || !shouldRequestExplorerPage(ids, visibleIds, hasMore, loadingMore, loading, errorText, permissionStatus)) {
221
+ return;
222
+ }
223
+ lastPageRequestRef.current = request;
224
+ onLoadMore({ loadedItemCount: items.length });
225
+ }, [
226
+ errorText,
227
+ hasMore,
228
+ ids,
229
+ items.length,
230
+ loading,
231
+ loadingMore,
232
+ onLoadMore,
233
+ pagingCollectionId,
234
+ pagingRetryToken,
235
+ permissionStatus,
236
+ visibleIds
237
+ ]);
238
+ const select = (item, intent) => {
239
+ if (passive || item.disabled)
240
+ return;
241
+ const next = resolveExplorerSelection(ids, effectiveSelectedIds, item.id, anchorId.current, intent, selectionMode);
242
+ if (intent !== "range")
243
+ anchorId.current = item.id;
244
+ if (selectedIds === undefined)
245
+ setInternalSelectedIds(next);
246
+ onSelectionChange?.({ selectedIds: next });
247
+ };
248
+ const keyboard = useExplorerKeyboardFocus(items, columns, passive, visibleIds, (targetId, originId, shiftKey) => {
249
+ if (!shiftKey)
250
+ return;
251
+ anchorId.current ??= originId;
252
+ const nextItem = itemLookup.get(targetId);
253
+ if (nextItem)
254
+ select(nextItem, "range");
255
+ });
256
+ const renderTile = (tile) => {
257
+ const item = itemLookup.get(tile.id);
258
+ if (!item)
259
+ return null;
260
+ const isSelected = selected.has(item.id);
261
+ const canInteract = !passive && !item.disabled;
262
+ return /* @__PURE__ */ jsx2(ExplorerKeyboardProxy, {
263
+ onKeyDown: (key, shiftKey) => keyboard.onKeyDown(item.id, key, shiftKey),
264
+ children: /* @__PURE__ */ jsxs(import_react_native.Pressable, {
265
+ accessibilityLabel: item.name,
266
+ accessibilityRole: "button",
267
+ accessibilityState: { selected: isSelected, disabled: !canInteract },
268
+ disabled: !canInteract,
269
+ focusable: canInteract && (import_react_native.Platform.OS !== "web" || item.id === keyboard.tabStopId),
270
+ onFocus: () => keyboard.onFocus(item.id),
271
+ onLongPress: () => select(item, "toggle"),
272
+ onPress: (event) => {
273
+ const intent = resolveExplorerPressIntent(event.nativeEvent);
274
+ select(item, intent);
275
+ if (selectionMode === "single" && intent === "replace") {
276
+ onActivate?.({ id: item.id });
277
+ }
278
+ },
279
+ style: {
280
+ flex: 1,
281
+ overflow: "hidden",
282
+ borderRadius: 8,
283
+ borderWidth: isSelected ? 2 : 1,
284
+ borderColor: isSelected ? theme.colors.primary : theme.semantics.neutral.divider,
285
+ backgroundColor: theme.semantics.neutral.surface,
286
+ padding: 8,
287
+ gap: 4
288
+ },
289
+ ref: (node) => keyboard.registerTile(item.id, node),
290
+ testID: `explorer-item-${item.id}`,
291
+ children: [
292
+ /* @__PURE__ */ jsx2(import_react_native.View, {
293
+ style: { flex: 1, alignItems: "center", justifyContent: "center" },
294
+ children: item.thumbnailUri ? /* @__PURE__ */ jsx2(Image2, {
295
+ accessibilityLabel: item.name,
296
+ aspectRatio: 1,
297
+ source: item.thumbnailUri,
298
+ width: "100%"
299
+ }) : /* @__PURE__ */ jsx2(Icon2, {
300
+ name: item.kind === "folder" ? "folder-outline" : "document-outline",
301
+ size: 32
302
+ })
303
+ }),
304
+ /* @__PURE__ */ jsx2(Text3, {
305
+ numberOfLines: 1,
306
+ variant: "caption",
307
+ children: item.name
308
+ })
309
+ ]
310
+ })
311
+ });
312
+ };
313
+ if (permissionStatus === "denied" || permissionStatus === "unavailable") {
314
+ return /* @__PURE__ */ jsxs(import_react_native.View, {
315
+ testID,
316
+ children: [
317
+ /* @__PURE__ */ jsx2(Text3, {
318
+ children: permissionText ?? "This collection is unavailable without permission."
319
+ }),
320
+ permissionStatus === "denied" && onRequestPermission ? /* @__PURE__ */ jsx2(import_react_native.Pressable, {
321
+ accessibilityRole: "button",
322
+ onPress: onRequestPermission,
323
+ children: /* @__PURE__ */ jsx2(Text3, {
324
+ children: "Grant access"
325
+ })
326
+ }) : null
327
+ ]
328
+ });
329
+ }
330
+ if (errorText)
331
+ return /* @__PURE__ */ jsx2(Text3, {
332
+ testID,
333
+ children: errorText
334
+ });
335
+ if (loading)
336
+ return /* @__PURE__ */ jsx2(Text3, {
337
+ testID,
338
+ children: "Loading…"
339
+ });
340
+ if (items.length === 0)
341
+ return /* @__PURE__ */ jsx2(Text3, {
342
+ testID,
343
+ children: emptyText
344
+ });
345
+ return /* @__PURE__ */ jsxs(import_react_native.View, {
346
+ children: [
347
+ permissionStatus === "limited" ? /* @__PURE__ */ jsx2(Text3, {
348
+ children: permissionText ?? "Showing the media you have allowed."
349
+ }) : null,
350
+ /* @__PURE__ */ jsx2(TileGrid2, {
351
+ focusedItemId: keyboard.focusedId ?? undefined,
352
+ height,
353
+ interactionPolicy,
354
+ onColumnsChange: setColumns,
355
+ onVisibleItemIdsChange: setVisibleIds,
356
+ items,
357
+ renderItem: renderTile,
358
+ testID,
359
+ tileSize,
360
+ width,
361
+ zoom
362
+ }),
363
+ loadingMore ? /* @__PURE__ */ jsx2(Text3, {
364
+ children: "Loading more…"
365
+ }) : null
366
+ ]
367
+ });
368
+ }
369
+ function resolveExplorerPressIntent(event) {
370
+ if (typeof event !== "object" || event === null)
371
+ return "replace";
372
+ if ("shiftKey" in event && event.shiftKey === true)
373
+ return "range";
374
+ if ("ctrlKey" in event && event.ctrlKey === true)
375
+ return "toggle";
376
+ if ("metaKey" in event && event.metaKey === true)
377
+ return "toggle";
378
+ return "replace";
379
+ }
380
+
381
+ // src/features/explorer/adapters/inbound/FileExplorer.tsx
382
+ import { jsx as jsx3 } from "react/jsx-runtime";
383
+ function FileExplorer2(props) {
384
+ return /* @__PURE__ */ jsx3(Explorer2, {
385
+ ...props
386
+ });
387
+ }
388
+
389
+ // src/features/explorer/adapters/inbound/MediaExplorer.tsx
390
+ import { jsx as jsx4 } from "react/jsx-runtime";
391
+ function MediaExplorer2(props) {
392
+ return /* @__PURE__ */ jsx4(Explorer2, {
393
+ ...props,
394
+ items: props.items.filter((item) => item.kind === "image" || item.kind === "video" || item.kind === "audio")
395
+ });
396
+ }
397
+ export { Explorer2, FileExplorer2, MediaExplorer2 };
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import"../../chunks/Accordion-b20zfsfz.js";
2
+ import"../../chunks/Accordion-54k0ncs3.js";
3
3
  import"../../chunks/Accordion-bfzeab9x.js";
4
4
  import"../../chunks/Accordion-6wrr910w.js";
5
5
  import"../../chunks/Accordion-8f0nekwe.js";
@@ -14,7 +14,7 @@ import {
14
14
  Explorer2,
15
15
  FileExplorer2,
16
16
  MediaExplorer2
17
- } from "../../chunks/Accordion-0md8wbtx.js";
17
+ } from "../../chunks/Accordion-kkr7xfh2.js";
18
18
  import"../../chunks/Accordion-bff0p3x4.js";
19
19
  import"../../chunks/Accordion-srjya6dz.js";
20
20
  import"../../chunks/Accordion-184r2r82.js";
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import"../../chunks/Accordion-b20zfsfz.js";
2
+ import"../../chunks/Accordion-54k0ncs3.js";
3
3
  import"../../chunks/Accordion-bfzeab9x.js";
4
4
  import"../../chunks/Accordion-6wrr910w.js";
5
5
  import"../../chunks/Accordion-8f0nekwe.js";
@@ -14,7 +14,7 @@ import {
14
14
  Explorer2,
15
15
  FileExplorer2,
16
16
  MediaExplorer2
17
- } from "../../chunks/Accordion-0md8wbtx.js";
17
+ } from "../../chunks/Accordion-kkr7xfh2.js";
18
18
  import"../../chunks/Accordion-bff0p3x4.js";
19
19
  import"../../chunks/Accordion-srjya6dz.js";
20
20
  import"../../chunks/Accordion-184r2r82.js";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  GridView2,
4
4
  TileGrid2
5
- } from "../../chunks/Accordion-b20zfsfz.js";
5
+ } from "../../chunks/Accordion-54k0ncs3.js";
6
6
  import"../../chunks/Accordion-xgpzh4wq.js";
7
7
  export {
8
8
  GridView2 as GridView,
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import"../../chunks/Accordion-b20zfsfz.js";
2
+ import"../../chunks/Accordion-54k0ncs3.js";
3
3
  import"../../chunks/Accordion-bfzeab9x.js";
4
4
  import"../../chunks/Accordion-6wrr910w.js";
5
5
  import"../../chunks/Accordion-8f0nekwe.js";
@@ -14,7 +14,7 @@ import {
14
14
  Explorer2,
15
15
  FileExplorer2,
16
16
  MediaExplorer2
17
- } from "../../chunks/Accordion-0md8wbtx.js";
17
+ } from "../../chunks/Accordion-kkr7xfh2.js";
18
18
  import"../../chunks/Accordion-bff0p3x4.js";
19
19
  import"../../chunks/Accordion-srjya6dz.js";
20
20
  import"../../chunks/Accordion-184r2r82.js";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  GridView2,
4
4
  TileGrid2
5
- } from "../../chunks/Accordion-b20zfsfz.js";
5
+ } from "../../chunks/Accordion-54k0ncs3.js";
6
6
  import"../../chunks/Accordion-xgpzh4wq.js";
7
7
  export {
8
8
  GridView2 as GridView,
@@ -25,7 +25,7 @@ import {
25
25
  import {
26
26
  Text3
27
27
  } from "../../chunks/Accordion-afmybp67.js";
28
- import"../../chunks/Accordion-b20zfsfz.js";
28
+ import"../../chunks/Accordion-54k0ncs3.js";
29
29
  import {
30
30
  Icon2
31
31
  } from "../../chunks/Accordion-bb8y34wh.js";
@@ -34,7 +34,7 @@ import {
34
34
  } from "../../chunks/Accordion-mej71qnw.js";
35
35
  import {
36
36
  Explorer2
37
- } from "../../chunks/Accordion-0md8wbtx.js";
37
+ } from "../../chunks/Accordion-kkr7xfh2.js";
38
38
  import"../../chunks/Accordion-ndf3jqhb.js";
39
39
  import"../../chunks/Accordion-45nc4bte.js";
40
40
  import {