@ankhorage/zora 25.5.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 (36) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +2 -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/types/grid-view.d.ts +18 -1
  12. package/dist/types/grid-view.d.ts.map +1 -1
  13. package/dist/types/grid-view.js.map +1 -1
  14. package/package.json +1 -1
  15. package/src/features/grid-view/adapters/inbound/GridView.interaction.test.tsx +585 -0
  16. package/src/features/grid-view/adapters/inbound/GridView.test.tsx +74 -0
  17. package/src/features/grid-view/adapters/inbound/GridView.tsx +506 -77
  18. package/src/features/grid-view/adapters/inbound/TileGrid.tsx +26 -3
  19. package/src/features/time-grid/adapters/inbound/TimeGrid.test.tsx +35 -8
  20. package/src/types/grid-view.ts +27 -1
  21. package/web-dist/chunks/{Accordion-0my2z6qe.js → Accordion-ebghcyzj.js} +41 -1
  22. package/web-dist/chunks/{Accordion-d6azgz09.js → Accordion-f43jba23.js} +1 -1
  23. package/web-dist/chunks/Accordion-q8exgc3h.js +515 -0
  24. package/web-dist/chunks/{Accordion-7qfd4cb4.js → Accordion-szar8dt5.js} +1 -1
  25. package/web-dist/components/explorer/Explorer.js +2 -2
  26. package/web-dist/components/file-explorer/FileExplorer.js +2 -2
  27. package/web-dist/components/grid-line-overlay/GridLineOverlay.js +1 -1
  28. package/web-dist/components/grid-ruler/GridRuler.js +1 -1
  29. package/web-dist/components/grid-view/GridView.js +1 -1
  30. package/web-dist/components/media-explorer/MediaExplorer.js +2 -2
  31. package/web-dist/components/spatial-grid/SpatialGrid.js +1 -1
  32. package/web-dist/components/tile-grid/TileGrid.js +1 -1
  33. package/web-dist/components/time-grid/TimeGrid.js +2 -2
  34. package/web-dist/components/uploader/Uploader.js +2 -2
  35. package/web-dist/manifest.json +24 -24
  36. package/web-dist/chunks/Accordion-gxj5qbpr.js +0 -194
@@ -1,3 +1,4 @@
1
+ import type { GridViewport } from '@ankhorage/grid-view';
1
2
  import React from 'react';
2
3
  import { View as NativeView } from 'react-native';
3
4
 
@@ -13,20 +14,29 @@ export function TileGrid({
13
14
  tileSize = 120,
14
15
  gap = 12,
15
16
  zoom = 1,
17
+ viewport,
18
+ defaultViewport,
19
+ viewportConstraints,
20
+ zoomLimits,
16
21
  overscanPixels,
17
22
  focusedItemId,
18
23
  revealPaddingPixels,
19
24
  onColumnsChange,
25
+ onViewportChange,
20
26
  onVisibleItemIdsChange,
21
27
  renderItem,
22
28
  interactionPolicy,
23
29
  testID,
24
30
  }: TileGridProps) {
25
31
  const [measuredWidth, setMeasuredWidth] = React.useState(0);
32
+ const [uncontrolledPixelsPerUnitX, setUncontrolledPixelsPerUnitX] = React.useState(
33
+ () => defaultViewport?.pixelsPerUnitX ?? zoom,
34
+ );
26
35
  const resolvedWidth = width ?? measuredWidth;
36
+ const pixelsPerUnitX = viewport?.pixelsPerUnitX ?? uncontrolledPixelsPerUnitX;
27
37
  const layout = React.useMemo(
28
- () => layoutTileGrid(items, resolvedWidth, tileSize, gap, zoom),
29
- [items, resolvedWidth, tileSize, gap, zoom],
38
+ () => layoutTileGrid(items, resolvedWidth, tileSize, gap, pixelsPerUnitX),
39
+ [gap, items, pixelsPerUnitX, resolvedWidth, tileSize],
30
40
  );
31
41
  const itemLookup = React.useMemo(() => new Map(items.map((item) => [item.id, item])), [items]);
32
42
 
@@ -34,6 +44,14 @@ export function TileGrid({
34
44
  if (resolvedWidth > 0) onColumnsChange?.(layout.columns);
35
45
  }, [layout.columns, onColumnsChange, resolvedWidth]);
36
46
 
47
+ const handleViewportChange = React.useCallback(
48
+ (nextViewport: GridViewport) => {
49
+ if (viewport === undefined) setUncontrolledPixelsPerUnitX(nextViewport.pixelsPerUnitX);
50
+ onViewportChange?.(nextViewport);
51
+ },
52
+ [onViewportChange, viewport],
53
+ );
54
+
37
55
  return (
38
56
  <NativeView
39
57
  onLayout={(event) => setMeasuredWidth(event.nativeEvent.layout.width)}
@@ -43,12 +61,14 @@ export function TileGrid({
43
61
  {resolvedWidth > 0 ? (
44
62
  <GridView
45
63
  contentHeight={layout.height}
46
- contentWidth={Math.max(resolvedWidth / zoom, layout.width)}
64
+ contentWidth={Math.max(resolvedWidth / pixelsPerUnitX, layout.width)}
65
+ defaultViewport={defaultViewport}
47
66
  height={height}
48
67
  focusedItemId={focusedItemId}
49
68
  interactionPolicy={interactionPolicy}
50
69
  items={layout.items}
51
70
  onVisibleItemIdsChange={onVisibleItemIdsChange}
71
+ onViewportChange={handleViewportChange}
52
72
  overscanPixels={overscanPixels}
53
73
  renderItem={(item) => {
54
74
  const source = itemLookup.get(item.id);
@@ -56,6 +76,9 @@ export function TileGrid({
56
76
  }}
57
77
  revealPaddingPixels={revealPaddingPixels}
58
78
  width={resolvedWidth}
79
+ viewport={viewport}
80
+ viewportConstraints={viewportConstraints}
81
+ zoomLimits={zoomLimits}
59
82
  zoom={zoom}
60
83
  />
61
84
  ) : null}
@@ -105,22 +105,49 @@ test('keeps mounted interval and virtualized variable-height lane labels synchro
105
105
  expect(arrowDown.defaultPrevented).toBe(true);
106
106
  expect(focusedIds).toEqual(['strings']);
107
107
 
108
- const verticalScroller = Array.from(host.querySelectorAll<HTMLElement>('div')).find(
109
- (element) =>
110
- element.getAttribute('style') === 'width: 200px; height: 30px;' &&
111
- Array.from(element.querySelectorAll('div')).some(
112
- (child) => child.getAttribute('style') === 'width: 200px; height: 180px;',
113
- ),
108
+ const verticalScroller = host.querySelector<HTMLElement>(
109
+ '[data-testid="time-grid-viewport-vertical-scroll"]',
114
110
  );
115
111
  if (!verticalScroller) throw new Error('Missing GridView vertical scroller');
116
- Object.defineProperty(verticalScroller, 'scrollTop', { configurable: true, value: 60 });
112
+ verticalScroller.scrollTop = 60;
117
113
  void act(() => verticalScroller.dispatchEvent(new browser.Event('scroll', { bubbles: true })));
118
114
 
119
- expect(viewportOffsets).toContain(30);
115
+ expect(viewportOffsets).toEqual([30]);
120
116
  expect(host.querySelector('[data-testid="time-grid-lane-label-drums"]')).toBeNull();
121
117
  expect(
122
118
  host.querySelector('[data-testid="time-grid-lane-label-bass"]')?.getAttribute('style'),
123
119
  ).toContain('top: -20px');
120
+
121
+ act(() => {
122
+ root.render(
123
+ <TimeGrid
124
+ contentWidth={100}
125
+ focusedIntervalId="strings"
126
+ height={30}
127
+ intervals={[
128
+ { extent: 10, id: 'drums', laneId: 'drums', start: 0 },
129
+ { extent: 10, id: 'strings', laneId: 'strings', start: 0 },
130
+ ]}
131
+ lanes={[
132
+ { height: 20, id: 'drums' },
133
+ { height: 40, id: 'bass' },
134
+ { height: 30, id: 'strings' },
135
+ ]}
136
+ onFocusedIntervalIdChange={(id) => focusedIds.push(id)}
137
+ onViewportChange={(viewport) => viewportOffsets.push(viewport.offsetY)}
138
+ overscanPixels={0}
139
+ renderInterval={(interval, selected) => (
140
+ <span data-selected={selected}>{interval.id}</span>
141
+ )}
142
+ renderLaneLabel={(lane) => <span>{lane.id}</span>}
143
+ selectedIntervalIds={['strings']}
144
+ testID="time-grid"
145
+ width={100}
146
+ zoom={2}
147
+ />,
148
+ );
149
+ });
150
+ expect(viewportOffsets).toEqual([30, 56]);
124
151
  await new Promise((resolve) => setTimeout(resolve, 100));
125
152
  } finally {
126
153
  act(() => root.unmount());
@@ -1,4 +1,9 @@
1
- import type { GridRectItem, GridViewport } from '@ankhorage/grid-view';
1
+ import type {
2
+ GridRectItem,
3
+ GridViewport,
4
+ GridViewportConstraints,
5
+ GridZoomLimits,
6
+ } from '@ankhorage/grid-view';
2
7
  import type React from 'react';
3
8
 
4
9
  import type { ZoraBaseProps } from './base';
@@ -10,6 +15,20 @@ export interface GridViewProps extends ZoraBaseProps {
10
15
  contentHeight: number;
11
16
  width: number;
12
17
  height: number;
18
+ /**
19
+ * Canonical controlled world viewport. Supplying this value makes `onViewportChange`
20
+ * report interaction proposals without mutating renderer-owned state.
21
+ */
22
+ viewport?: GridViewport;
23
+ /** Initial world position and independent axis scales for uncontrolled rendering. */
24
+ defaultViewport?: Partial<
25
+ Pick<GridViewport, 'offsetX' | 'offsetY' | 'pixelsPerUnitX' | 'pixelsPerUnitY'>
26
+ >;
27
+ /** Finite world bounds and optional overscroll for panning and focused-item reveal. */
28
+ viewportConstraints?: GridViewportConstraints;
29
+ /** Independent X/Y scale limits applied to focal-point zoom interactions. */
30
+ zoomLimits?: GridZoomLimits;
31
+ /** @deprecated Use `defaultViewport` for new code. */
13
32
  zoom?: number;
14
33
  overscanPixels?: number;
15
34
  /** Stable item identity that must be scrolled into view without duplicating viewport geometry. */
@@ -34,11 +53,18 @@ export interface TileGridProps extends ZoraBaseProps {
34
53
  tileSize?: number;
35
54
  gap?: number;
36
55
  zoom?: number;
56
+ viewport?: GridViewport;
57
+ defaultViewport?: Partial<
58
+ Pick<GridViewport, 'offsetX' | 'offsetY' | 'pixelsPerUnitX' | 'pixelsPerUnitY'>
59
+ >;
60
+ viewportConstraints?: GridViewportConstraints;
61
+ zoomLimits?: GridZoomLimits;
37
62
  overscanPixels?: number;
38
63
  focusedItemId?: string;
39
64
  revealPaddingPixels?: number;
40
65
  /** Reports the same measured column count used to position tiles. */
41
66
  onColumnsChange?: (columns: number) => void;
67
+ onViewportChange?: (viewport: GridViewport) => void;
42
68
  onVisibleItemIdsChange?: (ids: readonly string[]) => void;
43
69
  renderItem: (item: TileGridItem) => React.ReactNode;
44
70
  }
@@ -143,6 +143,18 @@ function isAlignment(value) {
143
143
  return value === "start" || value === "center" || value === "end";
144
144
  }
145
145
 
146
+ // node_modules/@ankhorage/grid-view/dist/features/viewport/panViewport.js
147
+ function panViewport(viewport, displacement, constraints) {
148
+ if (![displacement.x, displacement.y].every(Number.isFinite)) {
149
+ throw new RangeError("Pan displacement must be finite.");
150
+ }
151
+ return constrainViewport({
152
+ ...viewport,
153
+ offsetX: viewport.offsetX - displacement.x / viewport.pixelsPerUnitX,
154
+ offsetY: viewport.offsetY - displacement.y / viewport.pixelsPerUnitY
155
+ }, constraints);
156
+ }
157
+
146
158
  // node_modules/@ankhorage/grid-view/dist/features/viewport/revealWorldRect.js
147
159
  function revealWorldRect(viewport, rect, paddingPixels = 0, constraints) {
148
160
  validateRevealInput(viewport, rect, paddingPixels);
@@ -192,6 +204,34 @@ function worldToViewport(point, viewport) {
192
204
  };
193
205
  }
194
206
 
207
+ // node_modules/@ankhorage/grid-view/dist/features/viewport/zoomViewportAt.js
208
+ function zoomViewportAt(viewport, focalPoint, nextScale, limits = {}, constraints) {
209
+ if (![focalPoint.x, focalPoint.y, nextScale.pixelsPerUnitX, nextScale.pixelsPerUnitY].every(Number.isFinite)) {
210
+ throw new RangeError("Focal points and scales must be finite.");
211
+ }
212
+ const focalWorld = viewportToWorld(focalPoint, viewport);
213
+ const x = constrainScale(nextScale.pixelsPerUnitX, limits.minX, limits.maxX);
214
+ const y = constrainScale(nextScale.pixelsPerUnitY, limits.minY, limits.maxY);
215
+ return constrainViewport({
216
+ ...viewport,
217
+ pixelsPerUnitX: x,
218
+ pixelsPerUnitY: y,
219
+ offsetX: focalWorld.x - focalPoint.x / x,
220
+ offsetY: focalWorld.y - focalPoint.y / y
221
+ }, constraints);
222
+ }
223
+ function constrainScale(scale, minimum, maximum) {
224
+ const min = minimum ?? Number.EPSILON;
225
+ const max = maximum ?? Infinity;
226
+ if (!Number.isFinite(min) || min <= 0 || maximum !== undefined && (!Number.isFinite(max) || max <= 0 || min > max)) {
227
+ throw new RangeError("Zoom limits must be finite positive ranges.");
228
+ }
229
+ if (scale <= 0) {
230
+ throw new RangeError("Viewport scales must be positive.");
231
+ }
232
+ return Math.max(min, Math.min(max, scale));
233
+ }
234
+
195
235
  // node_modules/@ankhorage/grid-view/dist/features/axes/getAxisCategoryTicks.js
196
236
  function getAxisCategoryTicks(viewport, axis, categories) {
197
237
  const bounds = getVisibleWorldBounds(viewport);
@@ -290,4 +330,4 @@ function validateUniqueIds(intervals) {
290
330
  function intersects(firstStart, firstExtent, secondStart, secondExtent) {
291
331
  return firstStart <= secondStart + secondExtent && firstStart + firstExtent >= secondStart;
292
332
  }
293
- export { getAxisCategoryTicks, getAxisTicks, getVisibleGridItems, getLaneIntervalPlacement, getVisibleLaneIntervals, revealWorldRect, worldToViewport };
333
+ export { getAxisCategoryTicks, getAxisTicks, getVisibleGridItems, getLaneIntervalPlacement, getVisibleLaneIntervals, constrainViewport, panViewport, revealWorldRect, worldToViewport, zoomViewportAt };
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  TileGrid2
4
- } from "./Accordion-gxj5qbpr.js";
4
+ } from "./Accordion-q8exgc3h.js";
5
5
  import {
6
6
  withZoraThemeScope
7
7
  } from "./Accordion-42jfy1a1.js";