@ankhorage/zora 25.4.0 → 25.6.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 (100) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +3 -1
  3. package/dist/features/grid-view/adapters/inbound/GridView.d.ts +2 -7
  4. package/dist/features/grid-view/adapters/inbound/GridView.d.ts.map +1 -1
  5. package/dist/features/grid-view/adapters/inbound/GridView.js +309 -51
  6. package/dist/features/grid-view/adapters/inbound/GridView.js.map +1 -1
  7. package/dist/features/grid-view/adapters/inbound/TileGrid.d.ts +1 -1
  8. package/dist/features/grid-view/adapters/inbound/TileGrid.d.ts.map +1 -1
  9. package/dist/features/grid-view/adapters/inbound/TileGrid.js +11 -4
  10. package/dist/features/grid-view/adapters/inbound/TileGrid.js.map +1 -1
  11. package/dist/features/registry/ZORA_COMPONENT_REGISTRY.d.ts.map +1 -1
  12. package/dist/features/registry/ZORA_COMPONENT_REGISTRY.js +2 -0
  13. package/dist/features/registry/ZORA_COMPONENT_REGISTRY.js.map +1 -1
  14. package/dist/features/registry/componentMeta.d.ts.map +1 -1
  15. package/dist/features/registry/componentMeta.js +2 -0
  16. package/dist/features/registry/componentMeta.js.map +1 -1
  17. package/dist/features/time-grid/adapters/inbound/TimeGrid.d.ts +5 -0
  18. package/dist/features/time-grid/adapters/inbound/TimeGrid.d.ts.map +1 -0
  19. package/dist/features/time-grid/adapters/inbound/TimeGrid.js +107 -0
  20. package/dist/features/time-grid/adapters/inbound/TimeGrid.js.map +1 -0
  21. package/dist/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.d.ts +9 -0
  22. package/dist/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.d.ts.map +1 -0
  23. package/dist/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.js +5 -0
  24. package/dist/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.js.map +1 -0
  25. package/dist/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.native.d.ts +9 -0
  26. package/dist/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.native.d.ts.map +1 -0
  27. package/dist/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.native.js +5 -0
  28. package/dist/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.native.js.map +1 -0
  29. package/dist/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.web.d.ts +9 -0
  30. package/dist/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.web.d.ts.map +1 -0
  31. package/dist/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.web.js +10 -0
  32. package/dist/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.web.js.map +1 -0
  33. package/dist/features/time-grid/application/resolveTimeGridNextFocusId.d.ts +4 -0
  34. package/dist/features/time-grid/application/resolveTimeGridNextFocusId.d.ts.map +1 -0
  35. package/dist/features/time-grid/application/resolveTimeGridNextFocusId.js +31 -0
  36. package/dist/features/time-grid/application/resolveTimeGridNextFocusId.js.map +1 -0
  37. package/dist/features/time-grid/application/resolveTimeGridVisibleIntervals.d.ts +5 -0
  38. package/dist/features/time-grid/application/resolveTimeGridVisibleIntervals.d.ts.map +1 -0
  39. package/dist/features/time-grid/application/resolveTimeGridVisibleIntervals.js +6 -0
  40. package/dist/features/time-grid/application/resolveTimeGridVisibleIntervals.js.map +1 -0
  41. package/dist/features/time-grid/meta.d.ts +11 -0
  42. package/dist/features/time-grid/meta.d.ts.map +1 -0
  43. package/dist/features/time-grid/meta.js +11 -0
  44. package/dist/features/time-grid/meta.js.map +1 -0
  45. package/dist/features/time-grid/public.d.ts +5 -0
  46. package/dist/features/time-grid/public.d.ts.map +1 -0
  47. package/dist/features/time-grid/public.js +4 -0
  48. package/dist/features/time-grid/public.js.map +1 -0
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +1 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/types/grid-view.d.ts +18 -1
  54. package/dist/types/grid-view.d.ts.map +1 -1
  55. package/dist/types/grid-view.js.map +1 -1
  56. package/dist/types/time-grid.d.ts +30 -0
  57. package/dist/types/time-grid.d.ts.map +1 -0
  58. package/dist/types/time-grid.js +2 -0
  59. package/dist/types/time-grid.js.map +1 -0
  60. package/package.json +1 -1
  61. package/src/features/grid-view/adapters/inbound/GridView.interaction.test.tsx +585 -0
  62. package/src/features/grid-view/adapters/inbound/GridView.test.tsx +74 -0
  63. package/src/features/grid-view/adapters/inbound/GridView.tsx +506 -77
  64. package/src/features/grid-view/adapters/inbound/TileGrid.tsx +26 -3
  65. package/src/features/registry/ZORA_COMPONENT_REGISTRY.ts +2 -0
  66. package/src/features/registry/componentMeta.ts +2 -0
  67. package/src/features/time-grid/adapters/inbound/TimeGrid.test.tsx +158 -0
  68. package/src/features/time-grid/adapters/inbound/TimeGrid.tsx +197 -0
  69. package/src/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.native.tsx +11 -0
  70. package/src/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.tsx +11 -0
  71. package/src/features/time-grid/adapters/inbound/TimeGridKeyboardProxy.web.tsx +20 -0
  72. package/src/features/time-grid/application/resolveTimeGridNextFocusId.test.ts +46 -0
  73. package/src/features/time-grid/application/resolveTimeGridNextFocusId.ts +41 -0
  74. package/src/features/time-grid/application/resolveTimeGridVisibleIntervals.test.ts +33 -0
  75. package/src/features/time-grid/application/resolveTimeGridVisibleIntervals.ts +13 -0
  76. package/src/features/time-grid/meta.ts +12 -0
  77. package/src/features/time-grid/public.ts +4 -0
  78. package/src/index.ts +2 -0
  79. package/src/types/grid-view.ts +27 -1
  80. package/src/types/time-grid.ts +33 -0
  81. package/web-dist/chunks/{Accordion-r2w86rwv.js → Accordion-ebghcyzj.js} +125 -1
  82. package/web-dist/chunks/{Accordion-s3d6g59s.js → Accordion-f43jba23.js} +1 -1
  83. package/web-dist/chunks/Accordion-q8exgc3h.js +515 -0
  84. package/web-dist/chunks/{Accordion-60wn9xn9.js → Accordion-szar8dt5.js} +1 -1
  85. package/web-dist/components/explorer/Explorer.js +2 -2
  86. package/web-dist/components/file-explorer/FileExplorer.js +2 -2
  87. package/web-dist/components/grid-line-overlay/GridLineOverlay.js +1 -1
  88. package/web-dist/components/grid-ruler/GridRuler.js +1 -1
  89. package/web-dist/components/grid-view/GridView.js +1 -1
  90. package/web-dist/components/media-explorer/MediaExplorer.js +2 -2
  91. package/web-dist/components/reader/Reader.js +1 -1
  92. package/web-dist/components/spatial-grid/SpatialGrid.js +1 -1
  93. package/web-dist/components/tile-grid/TileGrid.js +1 -1
  94. package/web-dist/components/time-grid/TimeGrid.d.ts +3 -0
  95. package/web-dist/components/time-grid/TimeGrid.js +208 -0
  96. package/web-dist/components/time-grid/index.d.ts +1 -0
  97. package/web-dist/components/time-grid/index.js +2 -0
  98. package/web-dist/components/uploader/Uploader.js +2 -2
  99. package/web-dist/manifest.json +38 -22
  100. package/web-dist/chunks/Accordion-nxsda1my.js +0 -194
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 25.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b3bd629: Add controlled and bounded two-dimensional GridView and TileGrid viewport interaction for React Native and React Native Web.
8
+
9
+ ## 25.5.0
10
+
11
+ ### Minor Changes
12
+
13
+ - e6b7784: Add virtualized TimeGrid interval lanes for generic timeline, DAW, and scheduler code APIs.
14
+
3
15
  ## 25.4.0
4
16
 
5
17
  ### Minor 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.4.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)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v25.6.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
 
@@ -65,12 +65,14 @@ This package contains 6 additional examples. See the generated documentation for
65
65
  - [createPokerTrainingTableState sequence](././paradox/diagrams/sequences/create-poker-training-table-state.mmd)
66
66
  - [createZoraThemeConfig sequence](././paradox/diagrams/sequences/create-zora-theme-config.mmd)
67
67
  - [getLegalTargets sequence](././paradox/diagrams/sequences/get-legal-targets.mmd)
68
+ - [GridView sequence](././paradox/diagrams/sequences/grid-view.mmd)
68
69
  - [readChessPieces sequence](././paradox/diagrams/sequences/read-chess-pieces.mmd)
69
70
  - [resolveAvatarInitials sequence](././paradox/diagrams/sequences/resolve-avatar-initials.mmd)
70
71
  - [resolveExplorerSelection sequence](././paradox/diagrams/sequences/resolve-explorer-selection.mmd)
71
72
  - [resolveGridRulerMarks sequence](././paradox/diagrams/sequences/resolve-grid-ruler-marks.mmd)
72
73
  - [resolveOAuthProviderIcon sequence](././paradox/diagrams/sequences/resolve-oauth-provider-icon.mmd)
73
74
  - [resolveOAuthProviderLabel sequence](././paradox/diagrams/sequences/resolve-oauth-provider-label.mmd)
75
+ - [resolveTimeGridVisibleIntervals sequence](././paradox/diagrams/sequences/resolve-time-grid-visible-intervals.mmd)
74
76
  - [SelectionProvider sequence](././paradox/diagrams/sequences/selection-provider.mmd)
75
77
  - [tryMove sequence](././paradox/diagrams/sequences/try-move.mmd)
76
78
  - [useZoraTheme sequence](././paradox/diagrams/sequences/use-zora-theme.mmd)
@@ -1,10 +1,5 @@
1
1
  import React from 'react';
2
2
  import type { GridViewProps } from '../../../../types/grid-view';
3
- /***
4
- * Renders world-positioned items on native and web using the canonical viewport/culling engine.
5
- *
6
- * This first renderer uses nested native scroll regions. Only visible items are mounted;
7
- * logical cells and invisible elements are never rendered.
8
- */
9
- export declare function GridView({ items, contentWidth, contentHeight, width, height, zoom, overscanPixels, focusedItemId, revealPaddingPixels, interactionPolicy, onViewportChange, onVisibleItemIdsChange, renderItem, testID, }: GridViewProps): React.JSX.Element;
3
+ /*** Renders a controlled or uncontrolled, virtualized 2D world through the canonical grid viewport engine. */
4
+ export declare function GridView({ items, contentWidth, contentHeight, width, height, viewport: controlledViewport, defaultViewport, viewportConstraints, zoomLimits, zoom, overscanPixels, focusedItemId, revealPaddingPixels, interactionPolicy, onViewportChange, onVisibleItemIdsChange, renderItem, testID, }: GridViewProps): React.JSX.Element;
10
5
  //# sourceMappingURL=GridView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GridView.d.ts","sourceRoot":"","sources":["../../../../../src/features/grid-view/adapters/inbound/GridView.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,YAAY,EACZ,aAAa,EACb,KAAK,EACL,MAAM,EACN,IAAQ,EACR,cAAoB,EACpB,aAAa,EACb,mBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,UAAU,EACV,MAAM,GACP,EAAE,aAAa,qBA8Gf"}
1
+ {"version":3,"file":"GridView.d.ts","sourceRoot":"","sources":["../../../../../src/features/grid-view/adapters/inbound/GridView.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAiBjE,8GAA8G;AAC9G,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,YAAY,EACZ,aAAa,EACb,KAAK,EACL,MAAM,EACN,QAAQ,EAAE,kBAAkB,EAC5B,eAAe,EACf,mBAAmB,EACnB,UAAU,EACV,IAAQ,EACR,cAAoB,EACpB,aAAa,EACb,mBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,UAAU,EACV,MAAM,GACP,EAAE,aAAa,qBAwWf"}
@@ -1,29 +1,78 @@
1
- import { getVisibleGridItems, revealWorldRect, worldToViewport, } from '@ankhorage/grid-view';
1
+ import { constrainViewport, getVisibleGridItems, panViewport, revealWorldRect, worldToViewport, zoomViewportAt, } from '@ankhorage/grid-view';
2
2
  import React from 'react';
3
- import { ScrollView as NativeScrollView, View as NativeView } from 'react-native';
4
- /***
5
- * Renders world-positioned items on native and web using the canonical viewport/culling engine.
6
- *
7
- * This first renderer uses nested native scroll regions. Only visible items are mounted;
8
- * logical cells and invisible elements are never rendered.
9
- */
10
- export function GridView({ items, contentWidth, contentHeight, width, height, zoom = 1, overscanPixels = 160, focusedItemId, revealPaddingPixels = 8, interactionPolicy, onViewportChange, onVisibleItemIdsChange, renderItem, testID, }) {
11
- const [scrollX, setScrollX] = React.useState(0);
12
- const [scrollY, setScrollY] = React.useState(0);
3
+ import { Pressable as NativePressable, ScrollView as NativeScrollView, Text as NativeText, View as NativeView, } from 'react-native';
4
+ /*** Renders a controlled or uncontrolled, virtualized 2D world through the canonical grid viewport engine. */
5
+ export function GridView({ items, contentWidth, contentHeight, width, height, viewport: controlledViewport, defaultViewport, viewportConstraints, zoomLimits, zoom = 1, overscanPixels = 160, focusedItemId, revealPaddingPixels = 8, interactionPolicy, onViewportChange, onVisibleItemIdsChange, renderItem, testID, }) {
6
+ const [uncontrolledViewport, setUncontrolledViewport] = React.useState(() => createInitialViewport(width, height, zoom, defaultViewport));
13
7
  const horizontalScrollRef = React.useRef(null);
14
8
  const verticalScrollRef = React.useRef(null);
15
- const scale = Math.max(0.01, zoom);
16
- const viewport = React.useMemo(() => ({
17
- width,
18
- height,
19
- offsetX: scrollX / scale,
20
- offsetY: scrollY / scale,
21
- pixelsPerUnitX: scale,
22
- pixelsPerUnitY: scale,
23
- }), [height, scale, scrollX, scrollY, width]);
9
+ const gridRef = React.useRef(null);
10
+ const scrollPositionRef = React.useRef({ x: 0, y: 0 });
11
+ const pinchGestureRef = React.useRef(undefined);
12
+ const pinchMeasurementRef = React.useRef(0);
13
+ const pinchSessionRef = React.useRef(0);
14
+ const viewportOriginRef = React.useRef({ x: 0, y: 0 });
15
+ const focusRevealProposalRef = React.useRef(undefined);
16
+ const focusRevealViewportRef = React.useRef(undefined);
17
+ const [isPinching, setIsPinching] = React.useState(false);
18
+ const constraints = React.useMemo(() => viewportConstraints ?? createContentConstraints(contentWidth, contentHeight), [contentHeight, contentWidth, viewportConstraints]);
19
+ const sourceViewport = controlledViewport ?? uncontrolledViewport;
20
+ const viewport = React.useMemo(() => constrainViewport({ ...sourceViewport, height, width }, constraints), [constraints, height, sourceViewport, width]);
21
+ const viewportRef = React.useRef(viewport);
24
22
  const visibleItems = React.useMemo(() => getVisibleGridItems(items, viewport, overscanPixels), [items, overscanPixels, viewport]);
25
23
  const viewportVisibleIds = React.useMemo(() => getVisibleGridItems(visibleItems, viewport).map((item) => item.id), [viewport, visibleItems]);
24
+ const focusedItem = items.find((item) => item.id === focusedItemId);
25
+ const focusedItemHeight = focusedItem?.height;
26
+ const focusedItemIdForReveal = focusedItem?.id;
27
+ const focusedItemWidth = focusedItem?.width;
28
+ const focusedItemX = focusedItem?.x;
29
+ const focusedItemY = focusedItem?.y;
26
30
  const previousVisibleIdsRef = React.useRef([]);
31
+ const uncontrolled = controlledViewport === undefined;
32
+ const interactive = interactionPolicy !== 'passive';
33
+ const publish = React.useCallback((nextViewport) => {
34
+ if (areViewportsEqual(viewportRef.current, nextViewport))
35
+ return;
36
+ viewportRef.current = nextViewport;
37
+ if (uncontrolled)
38
+ setUncontrolledViewport(nextViewport);
39
+ onViewportChange?.(nextViewport);
40
+ }, [onViewportChange, uncontrolled]);
41
+ const publishRef = React.useRef(publish);
42
+ const pan = (displacement) => publish(panViewport(viewportRef.current, displacement, constraints));
43
+ const zoomAt = (focalPoint, factor) => publish(zoomViewportAt(viewportRef.current, focalPoint, {
44
+ pixelsPerUnitX: viewportRef.current.pixelsPerUnitX * factor,
45
+ pixelsPerUnitY: viewportRef.current.pixelsPerUnitY * factor,
46
+ }, zoomLimits, constraints));
47
+ React.useEffect(() => {
48
+ viewportRef.current = viewport;
49
+ }, [viewport]);
50
+ React.useEffect(() => {
51
+ publishRef.current = publish;
52
+ }, [publish]);
53
+ const refreshViewportOrigin = React.useCallback(() => {
54
+ gridRef.current?.measureInWindow((x, y) => {
55
+ viewportOriginRef.current = { x, y };
56
+ });
57
+ }, []);
58
+ const measurePinchGesture = React.useCallback((touches, session, onMeasured) => {
59
+ const measurement = pinchMeasurementRef.current + 1;
60
+ pinchMeasurementRef.current = measurement;
61
+ gridRef.current?.measureInWindow((x, y) => {
62
+ if (measurement !== pinchMeasurementRef.current || session !== pinchSessionRef.current) {
63
+ return;
64
+ }
65
+ viewportOriginRef.current = { x, y };
66
+ onMeasured(getPinchGesture(touches, width, height, { x, y }));
67
+ });
68
+ }, [height, width]);
69
+ React.useLayoutEffect(() => {
70
+ refreshViewportOrigin();
71
+ }, [height, refreshViewportOrigin, width]);
72
+ React.useEffect(() => () => {
73
+ pinchMeasurementRef.current += 1;
74
+ pinchSessionRef.current += 1;
75
+ }, []);
27
76
  React.useEffect(() => {
28
77
  if (!onVisibleItemIdsChange)
29
78
  return;
@@ -36,46 +85,255 @@ export function GridView({ items, contentWidth, contentHeight, width, height, zo
36
85
  onVisibleItemIdsChange(viewportVisibleIds);
37
86
  }, [onVisibleItemIdsChange, viewportVisibleIds]);
38
87
  React.useEffect(() => {
39
- onViewportChange?.(viewport);
40
- }, [onViewportChange, viewport]);
41
- const viewportRef = React.useRef(viewport);
42
- React.useEffect(() => {
43
- viewportRef.current = viewport;
44
- }, [viewport]);
88
+ const position = createScrollMapping(viewport, constraints);
89
+ const nextPosition = {
90
+ x: position.horizontal.position,
91
+ y: position.vertical.position,
92
+ };
93
+ if (areScrollPositionsEqual(scrollPositionRef.current, nextPosition))
94
+ return;
95
+ scrollPositionRef.current = nextPosition;
96
+ const animated = focusRevealViewportRef.current !== undefined &&
97
+ areViewportsEqual(viewport, focusRevealViewportRef.current);
98
+ horizontalScrollRef.current?.scrollTo({ animated, x: nextPosition.x });
99
+ verticalScrollRef.current?.scrollTo({ animated, y: nextPosition.y });
100
+ if (animated)
101
+ focusRevealViewportRef.current = undefined;
102
+ }, [constraints, viewport]);
45
103
  React.useEffect(() => {
46
- const currentViewport = viewportRef.current;
47
- const focusedItem = items.find((item) => item.id === focusedItemId);
48
- if (!focusedItem)
104
+ if (focusedItemHeight === undefined ||
105
+ focusedItemIdForReveal === undefined ||
106
+ focusedItemWidth === undefined ||
107
+ focusedItemX === undefined ||
108
+ focusedItemY === undefined) {
109
+ focusRevealProposalRef.current = undefined;
49
110
  return;
50
- const revealed = revealWorldRect(currentViewport, focusedItem, revealPaddingPixels);
51
- if (revealed.offsetX !== currentViewport.offsetX) {
52
- horizontalScrollRef.current?.scrollTo({ x: revealed.offsetX * scale, animated: true });
53
111
  }
54
- if (revealed.offsetY !== currentViewport.offsetY) {
55
- verticalScrollRef.current?.scrollTo({ y: revealed.offsetY * scale, animated: true });
112
+ const sourceViewport = viewportRef.current;
113
+ const revealed = revealWorldRect(sourceViewport, {
114
+ height: focusedItemHeight,
115
+ width: focusedItemWidth,
116
+ x: focusedItemX,
117
+ y: focusedItemY,
118
+ }, revealPaddingPixels, constraints);
119
+ if (isRepeatedControlledFocusRevealProposal({
120
+ controlled: !uncontrolled,
121
+ focusedItemId: focusedItemIdForReveal,
122
+ previous: focusRevealProposalRef.current,
123
+ sourceViewport,
124
+ viewport: revealed,
125
+ })) {
126
+ return;
56
127
  }
57
- }, [focusedItemId, height, items, revealPaddingPixels, scale, width]);
58
- return (<NativeScrollView ref={horizontalScrollRef} horizontal scrollEnabled={interactionPolicy !== 'passive'} scrollEventThrottle={32} showsHorizontalScrollIndicator style={{ width, height }} testID={testID} onScroll={(event) => setScrollX(event.nativeEvent.contentOffset.x)}>
59
- <NativeScrollView ref={verticalScrollRef} scrollEnabled={interactionPolicy !== 'passive'} scrollEventThrottle={32} showsVerticalScrollIndicator style={{ width: contentWidth * scale, height }} onScroll={(event) => setScrollY(event.nativeEvent.contentOffset.y)}>
60
- <NativeView style={{ width: contentWidth * scale, height: contentHeight * scale }}>
61
- {visibleItems.map((item) => {
62
- const position = worldToViewport({ x: item.x, y: item.y }, {
63
- ...viewport,
64
- offsetX: 0,
65
- offsetY: 0,
128
+ focusRevealProposalRef.current = {
129
+ focusedItemId: focusedItemIdForReveal,
130
+ sourceViewport,
131
+ viewport: revealed,
132
+ };
133
+ focusRevealViewportRef.current = areViewportsEqual(sourceViewport, revealed)
134
+ ? undefined
135
+ : revealed;
136
+ publishRef.current(revealed);
137
+ }, [
138
+ constraints,
139
+ focusedItemHeight,
140
+ focusedItemIdForReveal,
141
+ focusedItemWidth,
142
+ focusedItemX,
143
+ focusedItemY,
144
+ revealPaddingPixels,
145
+ uncontrolled,
146
+ ]);
147
+ const scrollMapping = createScrollMapping(viewport, constraints);
148
+ const syncScrollPosition = () => {
149
+ const nextMapping = createScrollMapping(viewportRef.current, constraints);
150
+ const nextPosition = {
151
+ x: nextMapping.horizontal.position,
152
+ y: nextMapping.vertical.position,
153
+ };
154
+ scrollPositionRef.current = nextPosition;
155
+ horizontalScrollRef.current?.scrollTo({ animated: false, x: nextPosition.x });
156
+ verticalScrollRef.current?.scrollTo({ animated: false, y: nextPosition.y });
157
+ };
158
+ return (<NativeView ref={gridRef} accessibilityLabel="Interactive grid viewport" onLayout={refreshViewportOrigin} onTouchEnd={() => {
159
+ const wasPinching = pinchGestureRef.current !== undefined;
160
+ pinchMeasurementRef.current += 1;
161
+ pinchSessionRef.current += 1;
162
+ pinchGestureRef.current = undefined;
163
+ setIsPinching(false);
164
+ if (wasPinching)
165
+ syncScrollPosition();
166
+ }} onTouchCancel={() => {
167
+ const wasPinching = pinchGestureRef.current !== undefined;
168
+ pinchMeasurementRef.current += 1;
169
+ pinchSessionRef.current += 1;
170
+ pinchGestureRef.current = undefined;
171
+ setIsPinching(false);
172
+ if (wasPinching)
173
+ syncScrollPosition();
174
+ }} onTouchMove={(event) => {
175
+ if (!interactive)
176
+ return;
177
+ measurePinchGesture([...event.nativeEvent.touches], pinchSessionRef.current, (pinch) => {
178
+ const previous = pinchGestureRef.current;
179
+ pinchGestureRef.current = pinch;
180
+ setIsPinching(pinch !== undefined);
181
+ if (!pinch || !previous)
182
+ return;
183
+ zoomAt(pinch.focalPoint, pinch.distance / previous.distance);
184
+ });
185
+ }} onTouchStart={(event) => {
186
+ const session = pinchSessionRef.current + 1;
187
+ pinchSessionRef.current = session;
188
+ measurePinchGesture([...event.nativeEvent.touches], session, (pinch) => {
189
+ pinchGestureRef.current = pinch;
190
+ setIsPinching(pinch !== undefined);
66
191
  });
192
+ }} style={{ height, overflow: 'hidden', width }} testID={testID}>
193
+ <NativeScrollView ref={horizontalScrollRef} testID={testID ? `${testID}-horizontal-scroll` : undefined} horizontal scrollEnabled={interactive && !isPinching} scrollEventThrottle={16} showsHorizontalScrollIndicator style={{ height, width }} onScroll={(event) => {
194
+ if (pinchGestureRef.current)
195
+ return;
196
+ const { x } = event.nativeEvent.contentOffset;
197
+ const displacement = x - scrollPositionRef.current.x;
198
+ scrollPositionRef.current = { ...scrollPositionRef.current, x };
199
+ if (displacement !== 0)
200
+ pan({ x: -displacement, y: 0 });
201
+ }}>
202
+ <NativeScrollView ref={verticalScrollRef} testID={testID ? `${testID}-vertical-scroll` : undefined} scrollEnabled={interactive && !isPinching} scrollEventThrottle={16} showsVerticalScrollIndicator style={{ height, width: scrollMapping.horizontal.contentSize }} onScroll={(event) => {
203
+ if (pinchGestureRef.current)
204
+ return;
205
+ const { y } = event.nativeEvent.contentOffset;
206
+ const displacement = y - scrollPositionRef.current.y;
207
+ scrollPositionRef.current = { ...scrollPositionRef.current, y };
208
+ if (displacement !== 0)
209
+ pan({ x: 0, y: -displacement });
210
+ }}>
211
+ <NativeView style={{
212
+ height: scrollMapping.vertical.contentSize,
213
+ width: scrollMapping.horizontal.contentSize,
214
+ }}>
215
+ {visibleItems.map((item) => {
216
+ const position = worldToViewport({ x: item.x, y: item.y }, { ...viewport, offsetX: 0, offsetY: 0 });
67
217
  return (<NativeView key={item.id} style={{
218
+ height: item.height * viewport.pixelsPerUnitY,
219
+ left: position.x + scrollMapping.horizontal.origin,
68
220
  position: 'absolute',
69
- left: position.x,
70
- top: position.y,
71
- width: item.width * scale,
72
- height: item.height * scale,
221
+ top: position.y + scrollMapping.vertical.origin,
222
+ width: item.width * viewport.pixelsPerUnitX,
73
223
  }}>
74
- {renderItem(item)}
75
- </NativeView>);
224
+ {renderItem(item)}
225
+ </NativeView>);
76
226
  })}
77
- </NativeView>
227
+ </NativeView>
228
+ </NativeScrollView>
78
229
  </NativeScrollView>
79
- </NativeScrollView>);
230
+ {interactive ? (<NativeView accessibilityLabel="Viewport controls" style={controlsStyle}>
231
+ <NativePressable accessibilityLabel="Pan left" accessibilityRole="button" onPress={() => pan({ x: 48, y: 0 })}>
232
+ <NativeText>←</NativeText>
233
+ </NativePressable>
234
+ <NativePressable accessibilityLabel="Pan right" accessibilityRole="button" onPress={() => pan({ x: -48, y: 0 })}>
235
+ <NativeText>→</NativeText>
236
+ </NativePressable>
237
+ <NativePressable accessibilityLabel="Pan up" accessibilityRole="button" onPress={() => pan({ x: 0, y: 48 })}>
238
+ <NativeText>↑</NativeText>
239
+ </NativePressable>
240
+ <NativePressable accessibilityLabel="Pan down" accessibilityRole="button" onPress={() => pan({ x: 0, y: -48 })}>
241
+ <NativeText>↓</NativeText>
242
+ </NativePressable>
243
+ <NativePressable accessibilityLabel="Zoom in" accessibilityRole="button" onPress={() => zoomAt({ x: width / 2, y: height / 2 }, 1.2)}>
244
+ <NativeText>+</NativeText>
245
+ </NativePressable>
246
+ <NativePressable accessibilityLabel="Zoom out" accessibilityRole="button" onPress={() => zoomAt({ x: width / 2, y: height / 2 }, 1 / 1.2)}>
247
+ <NativeText>−</NativeText>
248
+ </NativePressable>
249
+ </NativeView>) : null}
250
+ </NativeView>);
251
+ }
252
+ const controlsStyle = { bottom: 8, gap: 4, position: 'absolute', right: 8 };
253
+ /*** Builds the initial world position and independent scales for uncontrolled rendering. */
254
+ function createInitialViewport(width, height, zoom, viewport) {
255
+ return {
256
+ height,
257
+ offsetX: viewport?.offsetX ?? 0,
258
+ offsetY: viewport?.offsetY ?? 0,
259
+ pixelsPerUnitX: viewport?.pixelsPerUnitX ?? zoom,
260
+ pixelsPerUnitY: viewport?.pixelsPerUnitY ?? zoom,
261
+ width,
262
+ };
263
+ }
264
+ /*** Uses the rendered content extent as the default finite world boundary. */
265
+ function createContentConstraints(contentWidth, contentHeight) {
266
+ return { world: { height: contentHeight, width: contentWidth, x: 0, y: 0 } };
267
+ }
268
+ /*** Maps engine-constrained world offsets to nonnegative native scroll coordinates. */
269
+ function createScrollMapping(viewport, constraints) {
270
+ const horizontalMinimum = constrainViewport({ ...viewport, offsetX: -Number.MAX_SAFE_INTEGER }, constraints).offsetX;
271
+ const horizontalMaximum = constrainViewport({ ...viewport, offsetX: Number.MAX_SAFE_INTEGER }, constraints).offsetX;
272
+ const verticalMinimum = constrainViewport({ ...viewport, offsetY: -Number.MAX_SAFE_INTEGER }, constraints).offsetY;
273
+ const verticalMaximum = constrainViewport({ ...viewport, offsetY: Number.MAX_SAFE_INTEGER }, constraints).offsetY;
274
+ return {
275
+ horizontal: createScrollAxisMapping({
276
+ maximum: horizontalMaximum,
277
+ minimum: horizontalMinimum,
278
+ offset: viewport.offsetX,
279
+ scale: viewport.pixelsPerUnitX,
280
+ viewportSize: viewport.width,
281
+ }),
282
+ vertical: createScrollAxisMapping({
283
+ maximum: verticalMaximum,
284
+ minimum: verticalMinimum,
285
+ offset: viewport.offsetY,
286
+ scale: viewport.pixelsPerUnitY,
287
+ viewportSize: viewport.height,
288
+ }),
289
+ };
290
+ }
291
+ /*** Derives a physical scroll axis from the published engine constraint result. */
292
+ function createScrollAxisMapping({ maximum, minimum, offset, scale, viewportSize, }) {
293
+ const range = (maximum - minimum) * scale;
294
+ return {
295
+ contentSize: viewportSize + range,
296
+ origin: -minimum * scale,
297
+ position: (offset - minimum) * scale,
298
+ };
299
+ }
300
+ /*** Compares canonical viewport fields without treating equal proposals as state changes. */
301
+ function areViewportsEqual(left, right) {
302
+ return (left.width === right.width &&
303
+ left.height === right.height &&
304
+ left.offsetX === right.offsetX &&
305
+ left.offsetY === right.offsetY &&
306
+ left.pixelsPerUnitX === right.pixelsPerUnitX &&
307
+ left.pixelsPerUnitY === right.pixelsPerUnitY);
308
+ }
309
+ /*** Avoids requesting native scrolling again when the physical position is already current. */
310
+ function areScrollPositionsEqual(left, right) {
311
+ return left.x === right.x && left.y === right.y;
312
+ }
313
+ /*** Skips a rejected controlled focus proposal until its focus or incoming viewport changes. */
314
+ function isRepeatedControlledFocusRevealProposal({ controlled, focusedItemId, previous, sourceViewport, viewport, }) {
315
+ return (controlled &&
316
+ focusedItemId !== undefined &&
317
+ previous?.focusedItemId === focusedItemId &&
318
+ areViewportsEqual(previous.sourceViewport, sourceViewport) &&
319
+ areViewportsEqual(previous.viewport, viewport));
320
+ }
321
+ /*** Extracts a stable two-finger focal point in the measured outer viewport coordinate space. */
322
+ function getPinchGesture(touches, width, height, viewportOrigin) {
323
+ const [first, second] = touches;
324
+ if (!first || !second)
325
+ return undefined;
326
+ const deltaX = second.pageX - first.pageX;
327
+ const deltaY = second.pageY - first.pageY;
328
+ const distance = Math.hypot(deltaX, deltaY);
329
+ if (!Number.isFinite(distance) || distance === 0)
330
+ return undefined;
331
+ return {
332
+ distance,
333
+ focalPoint: {
334
+ x: Math.min(width, Math.max(0, (first.pageX + second.pageX) / 2 - viewportOrigin.x)),
335
+ y: Math.min(height, Math.max(0, (first.pageY + second.pageY) / 2 - viewportOrigin.y)),
336
+ },
337
+ };
80
338
  }
81
339
  //# sourceMappingURL=GridView.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GridView.js","sourceRoot":"","sources":["../../../../../src/features/grid-view/adapters/inbound/GridView.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n getVisibleGridItems,\n type GridViewport,\n revealWorldRect,\n worldToViewport,\n} from '@ankhorage/grid-view';\nimport React from 'react';\nimport { ScrollView as NativeScrollView, View as NativeView } from 'react-native';\n\nimport type { GridViewProps } from '../../../../types/grid-view';\n\n/***\n * Renders world-positioned items on native and web using the canonical viewport/culling engine.\n *\n * This first renderer uses nested native scroll regions. Only visible items are mounted;\n * logical cells and invisible elements are never rendered.\n */\nexport function GridView({\n items,\n contentWidth,\n contentHeight,\n width,\n height,\n zoom = 1,\n overscanPixels = 160,\n focusedItemId,\n revealPaddingPixels = 8,\n interactionPolicy,\n onViewportChange,\n onVisibleItemIdsChange,\n renderItem,\n testID,\n}: GridViewProps) {\n const [scrollX, setScrollX] = React.useState(0);\n const [scrollY, setScrollY] = React.useState(0);\n const horizontalScrollRef = React.useRef<NativeScrollView>(null);\n const verticalScrollRef = React.useRef<NativeScrollView>(null);\n const scale = Math.max(0.01, zoom);\n const viewport = React.useMemo<GridViewport>(\n () => ({\n width,\n height,\n offsetX: scrollX / scale,\n offsetY: scrollY / scale,\n pixelsPerUnitX: scale,\n pixelsPerUnitY: scale,\n }),\n [height, scale, scrollX, scrollY, width],\n );\n const visibleItems = React.useMemo(\n () => getVisibleGridItems(items, viewport, overscanPixels),\n [items, overscanPixels, viewport],\n );\n const viewportVisibleIds = React.useMemo(\n () => getVisibleGridItems(visibleItems, viewport).map((item) => item.id),\n [viewport, visibleItems],\n );\n const previousVisibleIdsRef = React.useRef<readonly string[]>([]);\n\n React.useEffect(() => {\n if (!onVisibleItemIdsChange) return;\n const previous = previousVisibleIdsRef.current;\n if (\n previous.length === viewportVisibleIds.length &&\n previous.every((id, index) => id === viewportVisibleIds.at(index))\n ) {\n return;\n }\n previousVisibleIdsRef.current = viewportVisibleIds;\n onVisibleItemIdsChange(viewportVisibleIds);\n }, [onVisibleItemIdsChange, viewportVisibleIds]);\n\n React.useEffect(() => {\n onViewportChange?.(viewport);\n }, [onViewportChange, viewport]);\n\n const viewportRef = React.useRef(viewport);\n React.useEffect(() => {\n viewportRef.current = viewport;\n }, [viewport]);\n\n React.useEffect(() => {\n const currentViewport = viewportRef.current;\n const focusedItem = items.find((item) => item.id === focusedItemId);\n if (!focusedItem) return;\n const revealed = revealWorldRect(currentViewport, focusedItem, revealPaddingPixels);\n if (revealed.offsetX !== currentViewport.offsetX) {\n horizontalScrollRef.current?.scrollTo({ x: revealed.offsetX * scale, animated: true });\n }\n if (revealed.offsetY !== currentViewport.offsetY) {\n verticalScrollRef.current?.scrollTo({ y: revealed.offsetY * scale, animated: true });\n }\n }, [focusedItemId, height, items, revealPaddingPixels, scale, width]);\n\n return (\n <NativeScrollView\n ref={horizontalScrollRef}\n horizontal\n scrollEnabled={interactionPolicy !== 'passive'}\n scrollEventThrottle={32}\n showsHorizontalScrollIndicator\n style={{ width, height }}\n testID={testID}\n onScroll={(event) => setScrollX(event.nativeEvent.contentOffset.x)}\n >\n <NativeScrollView\n ref={verticalScrollRef}\n scrollEnabled={interactionPolicy !== 'passive'}\n scrollEventThrottle={32}\n showsVerticalScrollIndicator\n style={{ width: contentWidth * scale, height }}\n onScroll={(event) => setScrollY(event.nativeEvent.contentOffset.y)}\n >\n <NativeView style={{ width: contentWidth * scale, height: contentHeight * scale }}>\n {visibleItems.map((item) => {\n const position = worldToViewport(\n { x: item.x, y: item.y },\n {\n ...viewport,\n offsetX: 0,\n offsetY: 0,\n },\n );\n return (\n <NativeView\n key={item.id}\n style={{\n position: 'absolute',\n left: position.x,\n top: position.y,\n width: item.width * scale,\n height: item.height * scale,\n }}\n >\n {renderItem(item)}\n </NativeView>\n );\n })}\n </NativeView>\n </NativeScrollView>\n </NativeScrollView>\n );\n}\n"]}
1
+ {"version":3,"file":"GridView.js","sourceRoot":"","sources":["../../../../../src/features/grid-view/adapters/inbound/GridView.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n constrainViewport,\n getVisibleGridItems,\n type GridPoint,\n type GridViewport,\n type GridViewportConstraints,\n panViewport,\n revealWorldRect,\n worldToViewport,\n zoomViewportAt,\n} from '@ankhorage/grid-view';\nimport React from 'react';\nimport {\n Pressable as NativePressable,\n ScrollView as NativeScrollView,\n Text as NativeText,\n View as NativeView,\n} from 'react-native';\n\nimport type { GridViewProps } from '../../../../types/grid-view';\n\ntype ScrollPosition = Readonly<{ x: number; y: number }>;\ntype PinchGesture = Readonly<{ distance: number; focalPoint: GridPoint }>;\ntype ScrollAxisMapping = Readonly<{ contentSize: number; origin: number; position: number }>;\ntype ScrollMapping = Readonly<{\n horizontal: ScrollAxisMapping;\n vertical: ScrollAxisMapping;\n}>;\ntype ViewportOrigin = Readonly<{ x: number; y: number }>;\ntype FocusRevealProposal = Readonly<{\n focusedItemId: string;\n sourceViewport: GridViewport;\n viewport: GridViewport;\n}>;\ntype PinchTouch = Readonly<{ pageX: number; pageY: number }>;\n\n/*** Renders a controlled or uncontrolled, virtualized 2D world through the canonical grid viewport engine. */\nexport function GridView({\n items,\n contentWidth,\n contentHeight,\n width,\n height,\n viewport: controlledViewport,\n defaultViewport,\n viewportConstraints,\n zoomLimits,\n zoom = 1,\n overscanPixels = 160,\n focusedItemId,\n revealPaddingPixels = 8,\n interactionPolicy,\n onViewportChange,\n onVisibleItemIdsChange,\n renderItem,\n testID,\n}: GridViewProps) {\n const [uncontrolledViewport, setUncontrolledViewport] = React.useState(() =>\n createInitialViewport(width, height, zoom, defaultViewport),\n );\n const horizontalScrollRef = React.useRef<NativeScrollView>(null);\n const verticalScrollRef = React.useRef<NativeScrollView>(null);\n const gridRef = React.useRef<React.ComponentRef<typeof NativeView>>(null);\n const scrollPositionRef = React.useRef<ScrollPosition>({ x: 0, y: 0 });\n const pinchGestureRef = React.useRef<PinchGesture | undefined>(undefined);\n const pinchMeasurementRef = React.useRef(0);\n const pinchSessionRef = React.useRef(0);\n const viewportOriginRef = React.useRef<ViewportOrigin>({ x: 0, y: 0 });\n const focusRevealProposalRef = React.useRef<FocusRevealProposal | undefined>(undefined);\n const focusRevealViewportRef = React.useRef<GridViewport | undefined>(undefined);\n const [isPinching, setIsPinching] = React.useState(false);\n const constraints = React.useMemo(\n () => viewportConstraints ?? createContentConstraints(contentWidth, contentHeight),\n [contentHeight, contentWidth, viewportConstraints],\n );\n const sourceViewport = controlledViewport ?? uncontrolledViewport;\n const viewport = React.useMemo(\n () => constrainViewport({ ...sourceViewport, height, width }, constraints),\n [constraints, height, sourceViewport, width],\n );\n const viewportRef = React.useRef(viewport);\n const visibleItems = React.useMemo(\n () => getVisibleGridItems(items, viewport, overscanPixels),\n [items, overscanPixels, viewport],\n );\n const viewportVisibleIds = React.useMemo(\n () => getVisibleGridItems(visibleItems, viewport).map((item) => item.id),\n [viewport, visibleItems],\n );\n const focusedItem = items.find((item) => item.id === focusedItemId);\n const focusedItemHeight = focusedItem?.height;\n const focusedItemIdForReveal = focusedItem?.id;\n const focusedItemWidth = focusedItem?.width;\n const focusedItemX = focusedItem?.x;\n const focusedItemY = focusedItem?.y;\n const previousVisibleIdsRef = React.useRef<readonly string[]>([]);\n const uncontrolled = controlledViewport === undefined;\n const interactive = interactionPolicy !== 'passive';\n\n const publish = React.useCallback(\n (nextViewport: GridViewport) => {\n if (areViewportsEqual(viewportRef.current, nextViewport)) return;\n viewportRef.current = nextViewport;\n if (uncontrolled) setUncontrolledViewport(nextViewport);\n onViewportChange?.(nextViewport);\n },\n [onViewportChange, uncontrolled],\n );\n const publishRef = React.useRef(publish);\n const pan = (displacement: GridPoint) =>\n publish(panViewport(viewportRef.current, displacement, constraints));\n const zoomAt = (focalPoint: GridPoint, factor: number) =>\n publish(\n zoomViewportAt(\n viewportRef.current,\n focalPoint,\n {\n pixelsPerUnitX: viewportRef.current.pixelsPerUnitX * factor,\n pixelsPerUnitY: viewportRef.current.pixelsPerUnitY * factor,\n },\n zoomLimits,\n constraints,\n ),\n );\n\n React.useEffect(() => {\n viewportRef.current = viewport;\n }, [viewport]);\n\n React.useEffect(() => {\n publishRef.current = publish;\n }, [publish]);\n\n const refreshViewportOrigin = React.useCallback(() => {\n gridRef.current?.measureInWindow((x, y) => {\n viewportOriginRef.current = { x, y };\n });\n }, []);\n\n const measurePinchGesture = React.useCallback(\n (\n touches: readonly PinchTouch[],\n session: number,\n onMeasured: (pinch: PinchGesture | undefined) => void,\n ) => {\n const measurement = pinchMeasurementRef.current + 1;\n pinchMeasurementRef.current = measurement;\n gridRef.current?.measureInWindow((x, y) => {\n if (measurement !== pinchMeasurementRef.current || session !== pinchSessionRef.current) {\n return;\n }\n viewportOriginRef.current = { x, y };\n onMeasured(getPinchGesture(touches, width, height, { x, y }));\n });\n },\n [height, width],\n );\n\n React.useLayoutEffect(() => {\n refreshViewportOrigin();\n }, [height, refreshViewportOrigin, width]);\n\n React.useEffect(\n () => () => {\n pinchMeasurementRef.current += 1;\n pinchSessionRef.current += 1;\n },\n [],\n );\n\n React.useEffect(() => {\n if (!onVisibleItemIdsChange) return;\n const previous = previousVisibleIdsRef.current;\n if (\n previous.length === viewportVisibleIds.length &&\n previous.every((id, index) => id === viewportVisibleIds.at(index))\n ) {\n return;\n }\n previousVisibleIdsRef.current = viewportVisibleIds;\n onVisibleItemIdsChange(viewportVisibleIds);\n }, [onVisibleItemIdsChange, viewportVisibleIds]);\n\n React.useEffect(() => {\n const position = createScrollMapping(viewport, constraints);\n const nextPosition = {\n x: position.horizontal.position,\n y: position.vertical.position,\n };\n if (areScrollPositionsEqual(scrollPositionRef.current, nextPosition)) return;\n scrollPositionRef.current = nextPosition;\n const animated =\n focusRevealViewportRef.current !== undefined &&\n areViewportsEqual(viewport, focusRevealViewportRef.current);\n horizontalScrollRef.current?.scrollTo({ animated, x: nextPosition.x });\n verticalScrollRef.current?.scrollTo({ animated, y: nextPosition.y });\n if (animated) focusRevealViewportRef.current = undefined;\n }, [constraints, viewport]);\n\n React.useEffect(() => {\n if (\n focusedItemHeight === undefined ||\n focusedItemIdForReveal === undefined ||\n focusedItemWidth === undefined ||\n focusedItemX === undefined ||\n focusedItemY === undefined\n ) {\n focusRevealProposalRef.current = undefined;\n return;\n }\n const sourceViewport = viewportRef.current;\n const revealed = revealWorldRect(\n sourceViewport,\n {\n height: focusedItemHeight,\n width: focusedItemWidth,\n x: focusedItemX,\n y: focusedItemY,\n },\n revealPaddingPixels,\n constraints,\n );\n if (\n isRepeatedControlledFocusRevealProposal({\n controlled: !uncontrolled,\n focusedItemId: focusedItemIdForReveal,\n previous: focusRevealProposalRef.current,\n sourceViewport,\n viewport: revealed,\n })\n ) {\n return;\n }\n focusRevealProposalRef.current = {\n focusedItemId: focusedItemIdForReveal,\n sourceViewport,\n viewport: revealed,\n };\n focusRevealViewportRef.current = areViewportsEqual(sourceViewport, revealed)\n ? undefined\n : revealed;\n publishRef.current(revealed);\n }, [\n constraints,\n focusedItemHeight,\n focusedItemIdForReveal,\n focusedItemWidth,\n focusedItemX,\n focusedItemY,\n revealPaddingPixels,\n uncontrolled,\n ]);\n\n const scrollMapping = createScrollMapping(viewport, constraints);\n\n const syncScrollPosition = () => {\n const nextMapping = createScrollMapping(viewportRef.current, constraints);\n const nextPosition = {\n x: nextMapping.horizontal.position,\n y: nextMapping.vertical.position,\n };\n scrollPositionRef.current = nextPosition;\n horizontalScrollRef.current?.scrollTo({ animated: false, x: nextPosition.x });\n verticalScrollRef.current?.scrollTo({ animated: false, y: nextPosition.y });\n };\n\n return (\n <NativeView\n ref={gridRef}\n accessibilityLabel=\"Interactive grid viewport\"\n onLayout={refreshViewportOrigin}\n onTouchEnd={() => {\n const wasPinching = pinchGestureRef.current !== undefined;\n pinchMeasurementRef.current += 1;\n pinchSessionRef.current += 1;\n pinchGestureRef.current = undefined;\n setIsPinching(false);\n if (wasPinching) syncScrollPosition();\n }}\n onTouchCancel={() => {\n const wasPinching = pinchGestureRef.current !== undefined;\n pinchMeasurementRef.current += 1;\n pinchSessionRef.current += 1;\n pinchGestureRef.current = undefined;\n setIsPinching(false);\n if (wasPinching) syncScrollPosition();\n }}\n onTouchMove={(event) => {\n if (!interactive) return;\n measurePinchGesture([...event.nativeEvent.touches], pinchSessionRef.current, (pinch) => {\n const previous = pinchGestureRef.current;\n pinchGestureRef.current = pinch;\n setIsPinching(pinch !== undefined);\n if (!pinch || !previous) return;\n zoomAt(pinch.focalPoint, pinch.distance / previous.distance);\n });\n }}\n onTouchStart={(event) => {\n const session = pinchSessionRef.current + 1;\n pinchSessionRef.current = session;\n measurePinchGesture([...event.nativeEvent.touches], session, (pinch) => {\n pinchGestureRef.current = pinch;\n setIsPinching(pinch !== undefined);\n });\n }}\n style={{ height, overflow: 'hidden', width }}\n testID={testID}\n >\n <NativeScrollView\n ref={horizontalScrollRef}\n testID={testID ? `${testID}-horizontal-scroll` : undefined}\n horizontal\n scrollEnabled={interactive && !isPinching}\n scrollEventThrottle={16}\n showsHorizontalScrollIndicator\n style={{ height, width }}\n onScroll={(event) => {\n if (pinchGestureRef.current) return;\n const { x } = event.nativeEvent.contentOffset;\n const displacement = x - scrollPositionRef.current.x;\n scrollPositionRef.current = { ...scrollPositionRef.current, x };\n if (displacement !== 0) pan({ x: -displacement, y: 0 });\n }}\n >\n <NativeScrollView\n ref={verticalScrollRef}\n testID={testID ? `${testID}-vertical-scroll` : undefined}\n scrollEnabled={interactive && !isPinching}\n scrollEventThrottle={16}\n showsVerticalScrollIndicator\n style={{ height, width: scrollMapping.horizontal.contentSize }}\n onScroll={(event) => {\n if (pinchGestureRef.current) return;\n const { y } = event.nativeEvent.contentOffset;\n const displacement = y - scrollPositionRef.current.y;\n scrollPositionRef.current = { ...scrollPositionRef.current, y };\n if (displacement !== 0) pan({ x: 0, y: -displacement });\n }}\n >\n <NativeView\n style={{\n height: scrollMapping.vertical.contentSize,\n width: scrollMapping.horizontal.contentSize,\n }}\n >\n {visibleItems.map((item) => {\n const position = worldToViewport(\n { x: item.x, y: item.y },\n { ...viewport, offsetX: 0, offsetY: 0 },\n );\n return (\n <NativeView\n key={item.id}\n style={{\n height: item.height * viewport.pixelsPerUnitY,\n left: position.x + scrollMapping.horizontal.origin,\n position: 'absolute',\n top: position.y + scrollMapping.vertical.origin,\n width: item.width * viewport.pixelsPerUnitX,\n }}\n >\n {renderItem(item)}\n </NativeView>\n );\n })}\n </NativeView>\n </NativeScrollView>\n </NativeScrollView>\n {interactive ? (\n <NativeView accessibilityLabel=\"Viewport controls\" style={controlsStyle}>\n <NativePressable\n accessibilityLabel=\"Pan left\"\n accessibilityRole=\"button\"\n onPress={() => pan({ x: 48, y: 0 })}\n >\n <NativeText>←</NativeText>\n </NativePressable>\n <NativePressable\n accessibilityLabel=\"Pan right\"\n accessibilityRole=\"button\"\n onPress={() => pan({ x: -48, y: 0 })}\n >\n <NativeText>→</NativeText>\n </NativePressable>\n <NativePressable\n accessibilityLabel=\"Pan up\"\n accessibilityRole=\"button\"\n onPress={() => pan({ x: 0, y: 48 })}\n >\n <NativeText>↑</NativeText>\n </NativePressable>\n <NativePressable\n accessibilityLabel=\"Pan down\"\n accessibilityRole=\"button\"\n onPress={() => pan({ x: 0, y: -48 })}\n >\n <NativeText>↓</NativeText>\n </NativePressable>\n <NativePressable\n accessibilityLabel=\"Zoom in\"\n accessibilityRole=\"button\"\n onPress={() => zoomAt({ x: width / 2, y: height / 2 }, 1.2)}\n >\n <NativeText>+</NativeText>\n </NativePressable>\n <NativePressable\n accessibilityLabel=\"Zoom out\"\n accessibilityRole=\"button\"\n onPress={() => zoomAt({ x: width / 2, y: height / 2 }, 1 / 1.2)}\n >\n <NativeText>−</NativeText>\n </NativePressable>\n </NativeView>\n ) : null}\n </NativeView>\n );\n}\n\nconst controlsStyle = { bottom: 8, gap: 4, position: 'absolute' as const, right: 8 };\n\n/*** Builds the initial world position and independent scales for uncontrolled rendering. */\nfunction createInitialViewport(\n width: number,\n height: number,\n zoom: number,\n viewport: GridViewProps['defaultViewport'],\n): GridViewport {\n return {\n height,\n offsetX: viewport?.offsetX ?? 0,\n offsetY: viewport?.offsetY ?? 0,\n pixelsPerUnitX: viewport?.pixelsPerUnitX ?? zoom,\n pixelsPerUnitY: viewport?.pixelsPerUnitY ?? zoom,\n width,\n };\n}\n\n/*** Uses the rendered content extent as the default finite world boundary. */\nfunction createContentConstraints(\n contentWidth: number,\n contentHeight: number,\n): GridViewportConstraints {\n return { world: { height: contentHeight, width: contentWidth, x: 0, y: 0 } };\n}\n\n/*** Maps engine-constrained world offsets to nonnegative native scroll coordinates. */\nfunction createScrollMapping(\n viewport: GridViewport,\n constraints: GridViewportConstraints,\n): ScrollMapping {\n const horizontalMinimum = constrainViewport(\n { ...viewport, offsetX: -Number.MAX_SAFE_INTEGER },\n constraints,\n ).offsetX;\n const horizontalMaximum = constrainViewport(\n { ...viewport, offsetX: Number.MAX_SAFE_INTEGER },\n constraints,\n ).offsetX;\n const verticalMinimum = constrainViewport(\n { ...viewport, offsetY: -Number.MAX_SAFE_INTEGER },\n constraints,\n ).offsetY;\n const verticalMaximum = constrainViewport(\n { ...viewport, offsetY: Number.MAX_SAFE_INTEGER },\n constraints,\n ).offsetY;\n return {\n horizontal: createScrollAxisMapping({\n maximum: horizontalMaximum,\n minimum: horizontalMinimum,\n offset: viewport.offsetX,\n scale: viewport.pixelsPerUnitX,\n viewportSize: viewport.width,\n }),\n vertical: createScrollAxisMapping({\n maximum: verticalMaximum,\n minimum: verticalMinimum,\n offset: viewport.offsetY,\n scale: viewport.pixelsPerUnitY,\n viewportSize: viewport.height,\n }),\n };\n}\n\ntype ScrollAxisMappingInput = Readonly<{\n maximum: number;\n minimum: number;\n offset: number;\n scale: number;\n viewportSize: number;\n}>;\n\n/*** Derives a physical scroll axis from the published engine constraint result. */\nfunction createScrollAxisMapping({\n maximum,\n minimum,\n offset,\n scale,\n viewportSize,\n}: ScrollAxisMappingInput): ScrollAxisMapping {\n const range = (maximum - minimum) * scale;\n return {\n contentSize: viewportSize + range,\n origin: -minimum * scale,\n position: (offset - minimum) * scale,\n };\n}\n\n/*** Compares canonical viewport fields without treating equal proposals as state changes. */\nfunction areViewportsEqual(left: GridViewport, right: GridViewport): boolean {\n return (\n left.width === right.width &&\n left.height === right.height &&\n left.offsetX === right.offsetX &&\n left.offsetY === right.offsetY &&\n left.pixelsPerUnitX === right.pixelsPerUnitX &&\n left.pixelsPerUnitY === right.pixelsPerUnitY\n );\n}\n\n/*** Avoids requesting native scrolling again when the physical position is already current. */\nfunction areScrollPositionsEqual(left: ScrollPosition, right: ScrollPosition): boolean {\n return left.x === right.x && left.y === right.y;\n}\n\n/*** Skips a rejected controlled focus proposal until its focus or incoming viewport changes. */\nfunction isRepeatedControlledFocusRevealProposal({\n controlled,\n focusedItemId,\n previous,\n sourceViewport,\n viewport,\n}: Readonly<{\n controlled: boolean;\n focusedItemId: string | undefined;\n previous: FocusRevealProposal | undefined;\n sourceViewport: GridViewport;\n viewport: GridViewport;\n}>): boolean {\n return (\n controlled &&\n focusedItemId !== undefined &&\n previous?.focusedItemId === focusedItemId &&\n areViewportsEqual(previous.sourceViewport, sourceViewport) &&\n areViewportsEqual(previous.viewport, viewport)\n );\n}\n\n/*** Extracts a stable two-finger focal point in the measured outer viewport coordinate space. */\nfunction getPinchGesture(\n touches: readonly Readonly<{\n pageX: number;\n pageY: number;\n }>[],\n width: number,\n height: number,\n viewportOrigin: ViewportOrigin,\n): PinchGesture | undefined {\n const [first, second] = touches;\n if (!first || !second) return undefined;\n const deltaX = second.pageX - first.pageX;\n const deltaY = second.pageY - first.pageY;\n const distance = Math.hypot(deltaX, deltaY);\n if (!Number.isFinite(distance) || distance === 0) return undefined;\n return {\n distance,\n focalPoint: {\n x: Math.min(width, Math.max(0, (first.pageX + second.pageX) / 2 - viewportOrigin.x)),\n y: Math.min(height, Math.max(0, (first.pageY + second.pageY) / 2 - viewportOrigin.y)),\n },\n };\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
2
  import type { TileGridProps } from '../../../../types/grid-view';
3
3
  /*** Composes a responsive, virtualized tile presentation over the generic GridView renderer. */
4
- export declare function TileGrid({ items, width, height, tileSize, gap, zoom, overscanPixels, focusedItemId, revealPaddingPixels, onColumnsChange, onVisibleItemIdsChange, renderItem, interactionPolicy, testID, }: TileGridProps): React.JSX.Element;
4
+ export declare function TileGrid({ items, width, height, tileSize, gap, zoom, viewport, defaultViewport, viewportConstraints, zoomLimits, overscanPixels, focusedItemId, revealPaddingPixels, onColumnsChange, onViewportChange, onVisibleItemIdsChange, renderItem, interactionPolicy, testID, }: TileGridProps): React.JSX.Element;
5
5
  //# sourceMappingURL=TileGrid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TileGrid.d.ts","sourceRoot":"","sources":["../../../../../src/features/grid-view/adapters/inbound/TileGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAIjE,gGAAgG;AAChG,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,KAAK,EACL,MAAY,EACZ,QAAc,EACd,GAAQ,EACR,IAAQ,EACR,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,eAAe,EACf,sBAAsB,EACtB,UAAU,EACV,iBAAiB,EACjB,MAAM,GACP,EAAE,aAAa,qBAwCf"}
1
+ {"version":3,"file":"TileGrid.d.ts","sourceRoot":"","sources":["../../../../../src/features/grid-view/adapters/inbound/TileGrid.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAIjE,gGAAgG;AAChG,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,KAAK,EACL,MAAY,EACZ,QAAc,EACd,GAAQ,EACR,IAAQ,EACR,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,UAAU,EACV,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,UAAU,EACV,iBAAiB,EACjB,MAAM,GACP,EAAE,aAAa,qBAyDf"}
@@ -3,20 +3,27 @@ import { View as NativeView } from 'react-native';
3
3
  import { layoutTileGrid } from '../../application/layoutTileGrid';
4
4
  import { GridView } from './GridView';
5
5
  /*** Composes a responsive, virtualized tile presentation over the generic GridView renderer. */
6
- export function TileGrid({ items, width, height = 440, tileSize = 120, gap = 12, zoom = 1, overscanPixels, focusedItemId, revealPaddingPixels, onColumnsChange, onVisibleItemIdsChange, renderItem, interactionPolicy, testID, }) {
6
+ export function TileGrid({ items, width, height = 440, tileSize = 120, gap = 12, zoom = 1, viewport, defaultViewport, viewportConstraints, zoomLimits, overscanPixels, focusedItemId, revealPaddingPixels, onColumnsChange, onViewportChange, onVisibleItemIdsChange, renderItem, interactionPolicy, testID, }) {
7
7
  const [measuredWidth, setMeasuredWidth] = React.useState(0);
8
+ const [uncontrolledPixelsPerUnitX, setUncontrolledPixelsPerUnitX] = React.useState(() => defaultViewport?.pixelsPerUnitX ?? zoom);
8
9
  const resolvedWidth = width ?? measuredWidth;
9
- const layout = React.useMemo(() => layoutTileGrid(items, resolvedWidth, tileSize, gap, zoom), [items, resolvedWidth, tileSize, gap, zoom]);
10
+ const pixelsPerUnitX = viewport?.pixelsPerUnitX ?? uncontrolledPixelsPerUnitX;
11
+ const layout = React.useMemo(() => layoutTileGrid(items, resolvedWidth, tileSize, gap, pixelsPerUnitX), [gap, items, pixelsPerUnitX, resolvedWidth, tileSize]);
10
12
  const itemLookup = React.useMemo(() => new Map(items.map((item) => [item.id, item])), [items]);
11
13
  React.useEffect(() => {
12
14
  if (resolvedWidth > 0)
13
15
  onColumnsChange?.(layout.columns);
14
16
  }, [layout.columns, onColumnsChange, resolvedWidth]);
17
+ const handleViewportChange = React.useCallback((nextViewport) => {
18
+ if (viewport === undefined)
19
+ setUncontrolledPixelsPerUnitX(nextViewport.pixelsPerUnitX);
20
+ onViewportChange?.(nextViewport);
21
+ }, [onViewportChange, viewport]);
15
22
  return (<NativeView onLayout={(event) => setMeasuredWidth(event.nativeEvent.layout.width)} style={{ width, height, overflow: 'hidden' }} testID={testID}>
16
- {resolvedWidth > 0 ? (<GridView contentHeight={layout.height} contentWidth={Math.max(resolvedWidth / zoom, layout.width)} height={height} focusedItemId={focusedItemId} interactionPolicy={interactionPolicy} items={layout.items} onVisibleItemIdsChange={onVisibleItemIdsChange} overscanPixels={overscanPixels} renderItem={(item) => {
23
+ {resolvedWidth > 0 ? (<GridView contentHeight={layout.height} contentWidth={Math.max(resolvedWidth / pixelsPerUnitX, layout.width)} defaultViewport={defaultViewport} height={height} focusedItemId={focusedItemId} interactionPolicy={interactionPolicy} items={layout.items} onVisibleItemIdsChange={onVisibleItemIdsChange} onViewportChange={handleViewportChange} overscanPixels={overscanPixels} renderItem={(item) => {
17
24
  const source = itemLookup.get(item.id);
18
25
  return source ? renderItem(source) : null;
19
- }} revealPaddingPixels={revealPaddingPixels} width={resolvedWidth} zoom={zoom}/>) : null}
26
+ }} revealPaddingPixels={revealPaddingPixels} width={resolvedWidth} viewport={viewport} viewportConstraints={viewportConstraints} zoomLimits={zoomLimits} zoom={zoom}/>) : null}
20
27
  </NativeView>);
21
28
  }
22
29
  //# sourceMappingURL=TileGrid.js.map