@internetarchive/collection-browser 4.8.1-webdev-9227.4 → 4.9.1-webdev-9670.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 (105) hide show
  1. package/README.md +11 -33
  2. package/dist/src/app-root.d.ts +11 -0
  3. package/dist/src/app-root.js +95 -1
  4. package/dist/src/app-root.js.map +1 -1
  5. package/dist/src/collection-browser.d.ts +43 -2
  6. package/dist/src/collection-browser.js +221 -15
  7. package/dist/src/collection-browser.js.map +1 -1
  8. package/dist/src/collection-facets/facet-row-template.d.ts +1 -1
  9. package/dist/src/collection-facets/facet-row-template.js +7 -14
  10. package/dist/src/collection-facets/facet-row-template.js.map +1 -1
  11. package/dist/src/collection-facets/facet-row.js +1 -3
  12. package/dist/src/collection-facets/facet-row.js.map +1 -1
  13. package/dist/src/collection-facets/more-facets-content.js +5 -6
  14. package/dist/src/collection-facets/more-facets-content.js.map +1 -1
  15. package/dist/src/collection-facets/more-facets-scroller.js +2 -3
  16. package/dist/src/collection-facets/more-facets-scroller.js.map +1 -1
  17. package/dist/src/collection-facets/smart-facets/heuristics/wikidata/wikidata-entity-map.js +4 -13
  18. package/dist/src/collection-facets/smart-facets/heuristics/wikidata/wikidata-entity-map.js.map +1 -1
  19. package/dist/src/collection-facets/smart-facets/smart-facet-bar.js +2 -6
  20. package/dist/src/collection-facets/smart-facets/smart-facet-bar.js.map +1 -1
  21. package/dist/src/collection-facets.js +5 -6
  22. package/dist/src/collection-facets.js.map +1 -1
  23. package/dist/src/empty-placeholder.d.ts +7 -7
  24. package/dist/src/empty-placeholder.js +15 -29
  25. package/dist/src/empty-placeholder.js.map +1 -1
  26. package/dist/src/expanded-date-picker.js +2 -3
  27. package/dist/src/expanded-date-picker.js.map +1 -1
  28. package/dist/src/manage/manage-bar.d.ts +2 -3
  29. package/dist/src/manage/manage-bar.js +16 -15
  30. package/dist/src/manage/manage-bar.js.map +1 -1
  31. package/dist/src/manage/remove-items-modal-content.js +2 -3
  32. package/dist/src/manage/remove-items-modal-content.js.map +1 -1
  33. package/dist/src/mediatype/mediatype-config.js +18 -55
  34. package/dist/src/mediatype/mediatype-config.js.map +1 -1
  35. package/dist/src/models.js +40 -120
  36. package/dist/src/models.js.map +1 -1
  37. package/dist/src/sort-filter-bar/alpha-bar-tooltip.js +2 -8
  38. package/dist/src/sort-filter-bar/alpha-bar-tooltip.js.map +1 -1
  39. package/dist/src/sort-filter-bar/alpha-bar.js +2 -7
  40. package/dist/src/sort-filter-bar/alpha-bar.js.map +1 -1
  41. package/dist/src/sort-filter-bar/sort-filter-bar.d.ts +12 -0
  42. package/dist/src/sort-filter-bar/sort-filter-bar.js +109 -28
  43. package/dist/src/sort-filter-bar/sort-filter-bar.js.map +1 -1
  44. package/dist/src/tiles/grid/account-tile.js +2 -3
  45. package/dist/src/tiles/grid/account-tile.js.map +1 -1
  46. package/dist/src/tiles/grid/collection-tile.js +11 -12
  47. package/dist/src/tiles/grid/collection-tile.js.map +1 -1
  48. package/dist/src/tiles/grid/item-tile.d.ts +4 -0
  49. package/dist/src/tiles/grid/item-tile.js +23 -8
  50. package/dist/src/tiles/grid/item-tile.js.map +1 -1
  51. package/dist/src/tiles/grid/tile-stats.js +3 -6
  52. package/dist/src/tiles/grid/tile-stats.js.map +1 -1
  53. package/dist/src/tiles/hover/hover-pane-controller.d.ts +2 -1
  54. package/dist/src/tiles/hover/hover-pane-controller.js +1 -1
  55. package/dist/src/tiles/hover/hover-pane-controller.js.map +1 -1
  56. package/dist/src/tiles/image-block.d.ts +5 -0
  57. package/dist/src/tiles/image-block.js +29 -0
  58. package/dist/src/tiles/image-block.js.map +1 -1
  59. package/dist/src/tiles/list/tile-list-compact-header.js +2 -3
  60. package/dist/src/tiles/list/tile-list-compact-header.js.map +1 -1
  61. package/dist/src/tiles/list/tile-list-compact.js +1 -3
  62. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  63. package/dist/src/tiles/list/tile-list.js +5 -15
  64. package/dist/src/tiles/list/tile-list.js.map +1 -1
  65. package/dist/src/tiles/models.d.ts +2 -1
  66. package/dist/src/tiles/models.js.map +1 -1
  67. package/dist/src/tiles/overlay/text-overlay.js +1 -3
  68. package/dist/src/tiles/overlay/text-overlay.js.map +1 -1
  69. package/dist/src/tiles/preview/tile-preview-controller.d.ts +61 -0
  70. package/dist/src/tiles/preview/tile-preview-controller.js +216 -0
  71. package/dist/src/tiles/preview/tile-preview-controller.js.map +1 -0
  72. package/dist/src/tiles/preview/tile-preview-media.d.ts +44 -0
  73. package/dist/src/tiles/preview/tile-preview-media.js +151 -0
  74. package/dist/src/tiles/preview/tile-preview-media.js.map +1 -0
  75. package/dist/src/tiles/preview/tile-preview.d.ts +66 -0
  76. package/dist/src/tiles/preview/tile-preview.js +494 -0
  77. package/dist/src/tiles/preview/tile-preview.js.map +1 -0
  78. package/dist/src/tiles/review-block.js +2 -3
  79. package/dist/src/tiles/review-block.js.map +1 -1
  80. package/dist/src/tiles/tile-dispatcher.d.ts +8 -2
  81. package/dist/src/tiles/tile-dispatcher.js +36 -6
  82. package/dist/src/tiles/tile-dispatcher.js.map +1 -1
  83. package/dist/src/tiles/tile-mediatype-icon.js +1 -3
  84. package/dist/src/tiles/tile-mediatype-icon.js.map +1 -1
  85. package/dist/src/utils/format-count.d.ts +1 -3
  86. package/dist/src/utils/format-count.js +59 -19
  87. package/dist/src/utils/format-count.js.map +1 -1
  88. package/dist/src/utils/format-unit-size.d.ts +1 -1
  89. package/dist/src/utils/format-unit-size.js +18 -21
  90. package/dist/src/utils/format-unit-size.js.map +1 -1
  91. package/dist/src/utils/grid-zoom-controller.d.ts +69 -0
  92. package/dist/src/utils/grid-zoom-controller.js +261 -0
  93. package/dist/src/utils/grid-zoom-controller.js.map +1 -0
  94. package/dist/src/utils/grid-zoom.d.ts +31 -0
  95. package/dist/src/utils/grid-zoom.js +109 -0
  96. package/dist/src/utils/grid-zoom.js.map +1 -0
  97. package/package.json +2 -6
  98. package/dist/src/locales/es.d.ts +0 -200
  99. package/dist/src/locales/es.js +0 -215
  100. package/dist/src/locales/es.js.map +0 -1
  101. package/dist/src/utils/get-locale.d.ts +0 -14
  102. package/dist/src/utils/get-locale.js +0 -20
  103. package/dist/src/utils/get-locale.js.map +0 -1
  104. package/locales/es.d.ts +0 -1
  105. package/locales/es.js +0 -1
@@ -0,0 +1,261 @@
1
+ import { clampGestureScale, GRID_ZOOM_COLUMNS, zoomLevelForScale, } from './grid-zoom';
2
+ const SETTLE_MS = 180;
3
+ const WHEEL_IDLE_MS = 160;
4
+ const WHEEL_SENSITIVITY = 0.01;
5
+ const MAX_WHEEL_STEP = 30;
6
+ /**
7
+ * Turns pinch gestures, ctrl+wheel (trackpad pinch in Chrome/Firefox), Safari
8
+ * gesture events and ctrl/cmd +/- into zoom level changes on the tile grid.
9
+ *
10
+ * While a gesture is in progress the grid is scaled with a CSS transform
11
+ * around the pinch point. When it ends, the transform eases to the size of the
12
+ * level it snapped to, then the real layout is swapped in.
13
+ */
14
+ export class GridZoomController {
15
+ constructor(host) {
16
+ this.host = host;
17
+ this.startColumns = 1;
18
+ this.scale = 1;
19
+ this.focal = { x: 0, y: 0 };
20
+ this.settling = false;
21
+ this.pointers = new Map();
22
+ this.startDistance = 0;
23
+ this.wheelTimer = 0;
24
+ this.hovering = false;
25
+ // Touch pinch: two pointers
26
+ this.pointerDown = (e) => {
27
+ if (e.pointerType !== 'touch')
28
+ return;
29
+ this.pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
30
+ if (this.pointers.size === 2 && !this.source) {
31
+ const [a, b] = [...this.pointers.values()];
32
+ this.startDistance = Math.hypot(a.x - b.x, a.y - b.y);
33
+ if (this.startDistance > 0) {
34
+ this.begin('pointer', { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });
35
+ }
36
+ }
37
+ };
38
+ this.pointerMove = (e) => {
39
+ this.lastPointer = { x: e.clientX, y: e.clientY };
40
+ this.hovering = true;
41
+ if (!this.pointers.has(e.pointerId))
42
+ return;
43
+ this.pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
44
+ if (this.source === 'pointer' && this.pointers.size >= 2) {
45
+ const [a, b] = [...this.pointers.values()];
46
+ this.update(Math.hypot(a.x - b.x, a.y - b.y) / this.startDistance);
47
+ }
48
+ };
49
+ this.pointerEnd = (e) => {
50
+ if (!this.pointers.delete(e.pointerId))
51
+ return;
52
+ if (this.source === 'pointer' && this.pointers.size < 2)
53
+ this.finish();
54
+ };
55
+ this.pointerLeave = () => {
56
+ this.hovering = false;
57
+ };
58
+ // Trackpad pinch in Chrome and Firefox arrives as ctrl+wheel
59
+ this.wheel = (e) => {
60
+ if (!e.ctrlKey)
61
+ return;
62
+ e.preventDefault();
63
+ if (this.settling)
64
+ return;
65
+ if (!this.source)
66
+ this.begin('wheel', { x: e.clientX, y: e.clientY });
67
+ if (this.source !== 'wheel')
68
+ return;
69
+ const step = Math.max(-MAX_WHEEL_STEP, Math.min(MAX_WHEEL_STEP, e.deltaY));
70
+ this.update(this.scale * Math.exp(-step * WHEEL_SENSITIVITY));
71
+ window.clearTimeout(this.wheelTimer);
72
+ this.wheelTimer = window.setTimeout(() => {
73
+ if (this.source === 'wheel')
74
+ this.finish();
75
+ }, WHEEL_IDLE_MS);
76
+ };
77
+ // Safari reports pinches (trackpad and touch) as gesture events
78
+ this.safariStart = (e) => {
79
+ const { clientX, clientY } = e;
80
+ e.preventDefault();
81
+ // Touch pinches also produce pointer events; the gesture events win.
82
+ if (this.source === 'pointer')
83
+ this.source = undefined;
84
+ this.begin('safari', { x: clientX, y: clientY });
85
+ };
86
+ this.safariChange = (e) => {
87
+ e.preventDefault();
88
+ if (this.source === 'safari')
89
+ this.update(e.scale);
90
+ };
91
+ this.safariEnd = (e) => {
92
+ e.preventDefault();
93
+ if (this.source === 'safari')
94
+ this.finish();
95
+ };
96
+ // ctrl/cmd + and - while the pointer or focus is on the grid
97
+ this.keyDown = (e) => {
98
+ if (!(e.ctrlKey || e.metaKey) || e.altKey)
99
+ return;
100
+ const inGrid = this.hovering ||
101
+ (this.surface && e.composedPath().includes(this.surface));
102
+ if (!inGrid)
103
+ return;
104
+ const level = this.host.getLevel();
105
+ let target;
106
+ if (e.key === '=' || e.key === '+')
107
+ target = level - 1;
108
+ else if (e.key === '-' || e.key === '_')
109
+ target = level + 1;
110
+ else
111
+ return;
112
+ if (target < 0 || target >= GRID_ZOOM_COLUMNS.length)
113
+ return;
114
+ e.preventDefault();
115
+ this.zoomToLevel(target, this.hovering ? this.lastPointer : undefined);
116
+ };
117
+ }
118
+ /** Starts listening. Safe to call again after the surface element has changed. */
119
+ connect() {
120
+ const surface = this.host.getSurface();
121
+ if (!surface || surface === this.surface)
122
+ return;
123
+ this.disconnect();
124
+ this.surface = surface;
125
+ surface.style.touchAction = 'pan-x pan-y';
126
+ surface.addEventListener('pointerdown', this.pointerDown);
127
+ surface.addEventListener('pointermove', this.pointerMove);
128
+ surface.addEventListener('pointerup', this.pointerEnd);
129
+ surface.addEventListener('pointercancel', this.pointerEnd);
130
+ surface.addEventListener('pointerleave', this.pointerLeave);
131
+ surface.addEventListener('wheel', this.wheel, { passive: false });
132
+ surface.addEventListener('gesturestart', this.safariStart);
133
+ surface.addEventListener('gesturechange', this.safariChange);
134
+ surface.addEventListener('gestureend', this.safariEnd);
135
+ window.addEventListener('keydown', this.keyDown);
136
+ }
137
+ disconnect() {
138
+ const { surface } = this;
139
+ if (surface) {
140
+ surface.style.touchAction = '';
141
+ surface.removeEventListener('pointerdown', this.pointerDown);
142
+ surface.removeEventListener('pointermove', this.pointerMove);
143
+ surface.removeEventListener('pointerup', this.pointerEnd);
144
+ surface.removeEventListener('pointercancel', this.pointerEnd);
145
+ surface.removeEventListener('pointerleave', this.pointerLeave);
146
+ surface.removeEventListener('wheel', this.wheel);
147
+ surface.removeEventListener('gesturestart', this.safariStart);
148
+ surface.removeEventListener('gesturechange', this.safariChange);
149
+ surface.removeEventListener('gestureend', this.safariEnd);
150
+ this.clearTransform();
151
+ surface.style.transition = '';
152
+ surface.style.opacity = '';
153
+ }
154
+ window.removeEventListener('keydown', this.keyDown);
155
+ window.clearTimeout(this.wheelTimer);
156
+ this.pointers.clear();
157
+ this.source = undefined;
158
+ this.settling = false;
159
+ this.pendingLevel = undefined;
160
+ this.hovering = false;
161
+ this.lastPointer = undefined;
162
+ this.surface = undefined;
163
+ }
164
+ /** Moves to `level` with the same animated settle a pinch gets. */
165
+ async zoomToLevel(level, focal) {
166
+ if (this.source || this.settling) {
167
+ this.pendingLevel = level;
168
+ return;
169
+ }
170
+ const columns = this.host.getColumns();
171
+ const targetColumns = GRID_ZOOM_COLUMNS[level];
172
+ if (targetColumns === undefined || level === this.host.getLevel())
173
+ return;
174
+ this.begin('programmatic', focal ?? this.viewportCenter());
175
+ await this.finish(targetColumns / columns);
176
+ }
177
+ begin(source, focal) {
178
+ const { surface } = this;
179
+ if (!surface || this.source || this.settling)
180
+ return;
181
+ this.source = source;
182
+ this.focal = focal;
183
+ this.scale = 1;
184
+ this.startColumns = this.host.getColumns();
185
+ this.anchor = this.host.captureAnchor(focal);
186
+ const rect = surface.getBoundingClientRect();
187
+ surface.style.transformOrigin = `${focal.x - rect.left}px ${focal.y - rect.top}px`;
188
+ surface.style.willChange = 'transform';
189
+ surface.style.transition = 'none';
190
+ }
191
+ update(scale) {
192
+ if (!this.source || !this.surface)
193
+ return;
194
+ this.scale = clampGestureScale(this.startColumns, scale);
195
+ this.surface.style.transform = `scale(${this.scale})`;
196
+ }
197
+ /**
198
+ * Ends the gesture, snapping to the level its scale is closest to. When
199
+ * `forcedRatio` is given (zooming to a specific level), that level is used.
200
+ */
201
+ async finish(forcedRatio) {
202
+ const { surface } = this;
203
+ if (!surface || !this.source)
204
+ return;
205
+ const { focal, anchor } = this;
206
+ const targetLevel = forcedRatio !== undefined
207
+ ? zoomLevelForScale(this.startColumns, 1 / forcedRatio)
208
+ : zoomLevelForScale(this.startColumns, this.scale);
209
+ const targetColumns = GRID_ZOOM_COLUMNS[targetLevel];
210
+ this.source = undefined;
211
+ this.settling = true;
212
+ // The scale at which the current tiles are the size the target level's tiles will be.
213
+ const settledScale = this.startColumns / targetColumns;
214
+ surface.style.transition = `transform ${SETTLE_MS}ms ease-out`;
215
+ surface.style.transform = `scale(${settledScale})`;
216
+ await new Promise(resolve => {
217
+ window.setTimeout(resolve, SETTLE_MS + 20);
218
+ });
219
+ if (!this.surface)
220
+ return;
221
+ if (targetLevel !== this.host.getLevel()) {
222
+ // Swap layouts behind a brief fade so the reflow isn't seen mid-way.
223
+ surface.style.transition = 'none';
224
+ surface.style.opacity = '0';
225
+ this.clearTransform();
226
+ try {
227
+ await this.host.commitLevel(targetLevel, focal, anchor);
228
+ }
229
+ finally {
230
+ surface.style.transition = 'opacity 120ms ease-in';
231
+ surface.style.opacity = '';
232
+ }
233
+ }
234
+ else {
235
+ this.clearTransform();
236
+ }
237
+ this.settling = false;
238
+ const { pendingLevel } = this;
239
+ this.pendingLevel = undefined;
240
+ if (pendingLevel !== undefined)
241
+ this.zoomToLevel(pendingLevel, focal);
242
+ }
243
+ clearTransform() {
244
+ const { surface } = this;
245
+ if (!surface)
246
+ return;
247
+ surface.style.transform = '';
248
+ surface.style.transformOrigin = '';
249
+ surface.style.willChange = '';
250
+ }
251
+ viewportCenter() {
252
+ const rect = this.surface?.getBoundingClientRect();
253
+ const top = Math.max(0, rect?.top ?? 0);
254
+ const bottom = Math.min(window.innerHeight, rect?.bottom ?? 0);
255
+ return {
256
+ x: (rect?.left ?? 0) + (rect?.width ?? window.innerWidth) / 2,
257
+ y: top + Math.max(0, bottom - top) / 2,
258
+ };
259
+ }
260
+ }
261
+ //# sourceMappingURL=grid-zoom-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grid-zoom-controller.js","sourceRoot":"","sources":["../../../src/utils/grid-zoom-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n clampGestureScale,\n GRID_ZOOM_COLUMNS,\n zoomLevelForScale,\n} from './grid-zoom';\n\nexport interface Point {\n x: number;\n y: number;\n}\n\n/** What the controller needs from the element that owns the grid. */\nexport interface GridZoomHost<Anchor = unknown> {\n /** The element the gestures are listened on and the preview transform is applied to. */\n getSurface(): HTMLElement | undefined;\n /** How many columns the grid is showing right now. */\n getColumns(): number;\n /** The zoom level that is currently in effect. */\n getLevel(): number;\n /** Notes what is under `focal` (viewport coordinates) before anything is scaled. */\n captureAnchor(focal: Point): Anchor | undefined;\n /**\n * Switches the layout to `level`, keeping the captured anchor under `focal`.\n * Resolves once the new layout is in place.\n */\n commitLevel(level: number, focal: Point, anchor?: Anchor): Promise<void>;\n}\n\nconst SETTLE_MS = 180;\nconst WHEEL_IDLE_MS = 160;\nconst WHEEL_SENSITIVITY = 0.01;\nconst MAX_WHEEL_STEP = 30;\n\ntype GestureSource = 'pointer' | 'wheel' | 'safari' | 'programmatic';\n\ninterface SafariGestureEvent extends UIEvent {\n scale: number;\n clientX: number;\n clientY: number;\n}\n\n/**\n * Turns pinch gestures, ctrl+wheel (trackpad pinch in Chrome/Firefox), Safari\n * gesture events and ctrl/cmd +/- into zoom level changes on the tile grid.\n *\n * While a gesture is in progress the grid is scaled with a CSS transform\n * around the pinch point. When it ends, the transform eases to the size of the\n * level it snapped to, then the real layout is swapped in.\n */\nexport class GridZoomController<Anchor = unknown> {\n private surface?: HTMLElement;\n\n private source?: GestureSource;\n\n private startColumns = 1;\n\n private scale = 1;\n\n private focal: Point = { x: 0, y: 0 };\n\n private anchor?: Anchor;\n\n private settling = false;\n\n /** The latest level asked for while a zoom was still settling. */\n private pendingLevel?: number;\n\n private pointers = new Map<number, Point>();\n\n private startDistance = 0;\n\n private wheelTimer = 0;\n\n private hovering = false;\n\n private lastPointer?: Point;\n\n constructor(private host: GridZoomHost<Anchor>) {}\n\n /** Starts listening. Safe to call again after the surface element has changed. */\n connect(): void {\n const surface = this.host.getSurface();\n if (!surface || surface === this.surface) return;\n this.disconnect();\n this.surface = surface;\n\n surface.style.touchAction = 'pan-x pan-y';\n surface.addEventListener('pointerdown', this.pointerDown);\n surface.addEventListener('pointermove', this.pointerMove);\n surface.addEventListener('pointerup', this.pointerEnd);\n surface.addEventListener('pointercancel', this.pointerEnd);\n surface.addEventListener('pointerleave', this.pointerLeave);\n surface.addEventListener('wheel', this.wheel, { passive: false });\n surface.addEventListener('gesturestart', this.safariStart);\n surface.addEventListener('gesturechange', this.safariChange);\n surface.addEventListener('gestureend', this.safariEnd);\n window.addEventListener('keydown', this.keyDown);\n }\n\n disconnect(): void {\n const { surface } = this;\n if (surface) {\n surface.style.touchAction = '';\n surface.removeEventListener('pointerdown', this.pointerDown);\n surface.removeEventListener('pointermove', this.pointerMove);\n surface.removeEventListener('pointerup', this.pointerEnd);\n surface.removeEventListener('pointercancel', this.pointerEnd);\n surface.removeEventListener('pointerleave', this.pointerLeave);\n surface.removeEventListener('wheel', this.wheel);\n surface.removeEventListener('gesturestart', this.safariStart);\n surface.removeEventListener('gesturechange', this.safariChange);\n surface.removeEventListener('gestureend', this.safariEnd);\n this.clearTransform();\n surface.style.transition = '';\n surface.style.opacity = '';\n }\n window.removeEventListener('keydown', this.keyDown);\n window.clearTimeout(this.wheelTimer);\n this.pointers.clear();\n this.source = undefined;\n this.settling = false;\n this.pendingLevel = undefined;\n this.hovering = false;\n this.lastPointer = undefined;\n this.surface = undefined;\n }\n\n /** Moves to `level` with the same animated settle a pinch gets. */\n async zoomToLevel(level: number, focal?: Point): Promise<void> {\n if (this.source || this.settling) {\n this.pendingLevel = level;\n return;\n }\n const columns = this.host.getColumns();\n const targetColumns = GRID_ZOOM_COLUMNS[level];\n if (targetColumns === undefined || level === this.host.getLevel()) return;\n this.begin('programmatic', focal ?? this.viewportCenter());\n await this.finish(targetColumns / columns);\n }\n\n private begin(source: GestureSource, focal: Point): void {\n const { surface } = this;\n if (!surface || this.source || this.settling) return;\n this.source = source;\n this.focal = focal;\n this.scale = 1;\n this.startColumns = this.host.getColumns();\n this.anchor = this.host.captureAnchor(focal);\n\n const rect = surface.getBoundingClientRect();\n surface.style.transformOrigin = `${focal.x - rect.left}px ${focal.y - rect.top}px`;\n surface.style.willChange = 'transform';\n surface.style.transition = 'none';\n }\n\n private update(scale: number): void {\n if (!this.source || !this.surface) return;\n this.scale = clampGestureScale(this.startColumns, scale);\n this.surface.style.transform = `scale(${this.scale})`;\n }\n\n /**\n * Ends the gesture, snapping to the level its scale is closest to. When\n * `forcedRatio` is given (zooming to a specific level), that level is used.\n */\n private async finish(forcedRatio?: number): Promise<void> {\n const { surface } = this;\n if (!surface || !this.source) return;\n const { focal, anchor } = this;\n const targetLevel =\n forcedRatio !== undefined\n ? zoomLevelForScale(this.startColumns, 1 / forcedRatio)\n : zoomLevelForScale(this.startColumns, this.scale);\n const targetColumns = GRID_ZOOM_COLUMNS[targetLevel];\n this.source = undefined;\n this.settling = true;\n\n // The scale at which the current tiles are the size the target level's tiles will be.\n const settledScale = this.startColumns / targetColumns;\n surface.style.transition = `transform ${SETTLE_MS}ms ease-out`;\n surface.style.transform = `scale(${settledScale})`;\n await new Promise<void>(resolve => {\n window.setTimeout(resolve, SETTLE_MS + 20);\n });\n\n if (!this.surface) return;\n if (targetLevel !== this.host.getLevel()) {\n // Swap layouts behind a brief fade so the reflow isn't seen mid-way.\n surface.style.transition = 'none';\n surface.style.opacity = '0';\n this.clearTransform();\n try {\n await this.host.commitLevel(targetLevel, focal, anchor);\n } finally {\n surface.style.transition = 'opacity 120ms ease-in';\n surface.style.opacity = '';\n }\n } else {\n this.clearTransform();\n }\n this.settling = false;\n\n const { pendingLevel } = this;\n this.pendingLevel = undefined;\n if (pendingLevel !== undefined) this.zoomToLevel(pendingLevel, focal);\n }\n\n private clearTransform(): void {\n const { surface } = this;\n if (!surface) return;\n surface.style.transform = '';\n surface.style.transformOrigin = '';\n surface.style.willChange = '';\n }\n\n private viewportCenter(): Point {\n const rect = this.surface?.getBoundingClientRect();\n const top = Math.max(0, rect?.top ?? 0);\n const bottom = Math.min(window.innerHeight, rect?.bottom ?? 0);\n return {\n x: (rect?.left ?? 0) + (rect?.width ?? window.innerWidth) / 2,\n y: top + Math.max(0, bottom - top) / 2,\n };\n }\n\n // Touch pinch: two pointers\n\n private pointerDown = (e: PointerEvent): void => {\n if (e.pointerType !== 'touch') return;\n this.pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (this.pointers.size === 2 && !this.source) {\n const [a, b] = [...this.pointers.values()];\n this.startDistance = Math.hypot(a.x - b.x, a.y - b.y);\n if (this.startDistance > 0) {\n this.begin('pointer', { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });\n }\n }\n };\n\n private pointerMove = (e: PointerEvent): void => {\n this.lastPointer = { x: e.clientX, y: e.clientY };\n this.hovering = true;\n if (!this.pointers.has(e.pointerId)) return;\n this.pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (this.source === 'pointer' && this.pointers.size >= 2) {\n const [a, b] = [...this.pointers.values()];\n this.update(Math.hypot(a.x - b.x, a.y - b.y) / this.startDistance);\n }\n };\n\n private pointerEnd = (e: PointerEvent): void => {\n if (!this.pointers.delete(e.pointerId)) return;\n if (this.source === 'pointer' && this.pointers.size < 2) this.finish();\n };\n\n private pointerLeave = (): void => {\n this.hovering = false;\n };\n\n // Trackpad pinch in Chrome and Firefox arrives as ctrl+wheel\n\n private wheel = (e: WheelEvent): void => {\n if (!e.ctrlKey) return;\n e.preventDefault();\n if (this.settling) return;\n if (!this.source) this.begin('wheel', { x: e.clientX, y: e.clientY });\n if (this.source !== 'wheel') return;\n\n const step = Math.max(-MAX_WHEEL_STEP, Math.min(MAX_WHEEL_STEP, e.deltaY));\n this.update(this.scale * Math.exp(-step * WHEEL_SENSITIVITY));\n window.clearTimeout(this.wheelTimer);\n this.wheelTimer = window.setTimeout(() => {\n if (this.source === 'wheel') this.finish();\n }, WHEEL_IDLE_MS);\n };\n\n // Safari reports pinches (trackpad and touch) as gesture events\n\n private safariStart = (e: Event): void => {\n const { clientX, clientY } = e as SafariGestureEvent;\n e.preventDefault();\n // Touch pinches also produce pointer events; the gesture events win.\n if (this.source === 'pointer') this.source = undefined;\n this.begin('safari', { x: clientX, y: clientY });\n };\n\n private safariChange = (e: Event): void => {\n e.preventDefault();\n if (this.source === 'safari') this.update((e as SafariGestureEvent).scale);\n };\n\n private safariEnd = (e: Event): void => {\n e.preventDefault();\n if (this.source === 'safari') this.finish();\n };\n\n // ctrl/cmd + and - while the pointer or focus is on the grid\n\n private keyDown = (e: KeyboardEvent): void => {\n if (!(e.ctrlKey || e.metaKey) || e.altKey) return;\n const inGrid =\n this.hovering ||\n (this.surface && e.composedPath().includes(this.surface));\n if (!inGrid) return;\n\n const level = this.host.getLevel();\n let target: number;\n if (e.key === '=' || e.key === '+') target = level - 1;\n else if (e.key === '-' || e.key === '_') target = level + 1;\n else return;\n\n if (target < 0 || target >= GRID_ZOOM_COLUMNS.length) return;\n e.preventDefault();\n this.zoomToLevel(target, this.hovering ? this.lastPointer : undefined);\n };\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import type { LayoutType } from '../tiles/models';
2
+ /**
3
+ * The column counts the tile grid can snap to when pinch-zoom is enabled,
4
+ * from the biggest tiles to the densest thumbnail mosaic.
5
+ */
6
+ export declare const GRID_ZOOM_COLUMNS: readonly [1, 2, 3, 4, 6, 8, 12];
7
+ export declare const GRID_ZOOM_STORAGE_KEY = "ia-collection-browser-grid-zoom";
8
+ export declare const MIN_ZOOM_LEVEL = 0;
9
+ export declare const MAX_ZOOM_LEVEL: number;
10
+ /** Returns the index of the zoom level whose column count is closest to `columns`. */
11
+ export declare function nearestZoomLevel(columns: number): number;
12
+ export declare function clampZoomLevel(level: number): number;
13
+ /**
14
+ * Clamps a gesture scale so the tiles can't be stretched past the biggest or
15
+ * smallest level (with a little give so the limit feels soft).
16
+ * A scale above 1 means bigger tiles, so fewer columns.
17
+ */
18
+ export declare function clampGestureScale(startColumns: number, scale: number): number;
19
+ /** The level a gesture of the given scale should settle on. */
20
+ export declare function zoomLevelForScale(startColumns: number, scale: number): number;
21
+ /** Reads a persisted zoom level (stored as a column count), if there is a valid one. */
22
+ export declare function loadStoredZoomLevel(): number | undefined;
23
+ export declare function storeZoomLevel(level: number | undefined): void;
24
+ /**
25
+ * The inline CSS variables that give the infinite scroller `level`'s layout.
26
+ * `gridWidth` is the scroller's width in px, which sets the thumbnail size
27
+ * at the levels that show only thumbnails.
28
+ */
29
+ export declare function zoomLevelStyles(level: number, gridWidth: number): Record<string, string>;
30
+ /** The tile layout to use at `level`: dense levels drop details, the densest show only thumbnails. */
31
+ export declare function zoomLayoutType(level: number): LayoutType | undefined;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The column counts the tile grid can snap to when pinch-zoom is enabled,
3
+ * from the biggest tiles to the densest thumbnail mosaic.
4
+ */
5
+ export const GRID_ZOOM_COLUMNS = [1, 2, 3, 4, 6, 8, 12];
6
+ /** The column gap (in px) used at each zoom level, indexed like `GRID_ZOOM_COLUMNS`. */
7
+ const GRID_ZOOM_GAPS = [17, 17, 14, 12, 8, 5, 2];
8
+ /** Levels at or beyond this many columns use the tiles' minimal layout. */
9
+ const MINIMAL_LAYOUT_COLUMNS = 6;
10
+ /** Levels at or beyond this many columns show only thumbnails. */
11
+ const THUMBNAIL_ONLY_COLUMNS = 8;
12
+ /** How far past the biggest or smallest level a gesture can stretch before snapping. */
13
+ const MAX_OVERSHOOT = 1.15;
14
+ export const GRID_ZOOM_STORAGE_KEY = 'ia-collection-browser-grid-zoom';
15
+ export const MIN_ZOOM_LEVEL = 0;
16
+ export const MAX_ZOOM_LEVEL = GRID_ZOOM_COLUMNS.length - 1;
17
+ /** Returns the index of the zoom level whose column count is closest to `columns`. */
18
+ export function nearestZoomLevel(columns) {
19
+ let best = 0;
20
+ let bestDistance = Infinity;
21
+ GRID_ZOOM_COLUMNS.forEach((levelColumns, level) => {
22
+ const distance = Math.abs(Math.log(levelColumns / columns));
23
+ if (distance < bestDistance) {
24
+ best = level;
25
+ bestDistance = distance;
26
+ }
27
+ });
28
+ return best;
29
+ }
30
+ export function clampZoomLevel(level) {
31
+ return Math.min(MAX_ZOOM_LEVEL, Math.max(MIN_ZOOM_LEVEL, Math.round(level)));
32
+ }
33
+ /**
34
+ * Clamps a gesture scale so the tiles can't be stretched past the biggest or
35
+ * smallest level (with a little give so the limit feels soft).
36
+ * A scale above 1 means bigger tiles, so fewer columns.
37
+ */
38
+ export function clampGestureScale(startColumns, scale) {
39
+ const maxScale = (startColumns / GRID_ZOOM_COLUMNS[MIN_ZOOM_LEVEL]) * MAX_OVERSHOOT;
40
+ const minScale = startColumns / GRID_ZOOM_COLUMNS[MAX_ZOOM_LEVEL] / MAX_OVERSHOOT;
41
+ return Math.min(maxScale, Math.max(minScale, scale));
42
+ }
43
+ /** The level a gesture of the given scale should settle on. */
44
+ export function zoomLevelForScale(startColumns, scale) {
45
+ return nearestZoomLevel(startColumns / scale);
46
+ }
47
+ /** Reads a persisted zoom level (stored as a column count), if there is a valid one. */
48
+ export function loadStoredZoomLevel() {
49
+ try {
50
+ const stored = Number(localStorage.getItem(GRID_ZOOM_STORAGE_KEY));
51
+ if (!stored)
52
+ return undefined;
53
+ return nearestZoomLevel(stored);
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
59
+ export function storeZoomLevel(level) {
60
+ try {
61
+ if (level === undefined) {
62
+ localStorage.removeItem(GRID_ZOOM_STORAGE_KEY);
63
+ }
64
+ else {
65
+ localStorage.setItem(GRID_ZOOM_STORAGE_KEY, String(GRID_ZOOM_COLUMNS[level]));
66
+ }
67
+ }
68
+ catch {
69
+ // Storage can be unavailable (private windows, blocked site data).
70
+ }
71
+ }
72
+ /**
73
+ * The inline CSS variables that give the infinite scroller `level`'s layout.
74
+ * `gridWidth` is the scroller's width in px, which sets the thumbnail size
75
+ * at the levels that show only thumbnails.
76
+ */
77
+ export function zoomLevelStyles(level, gridWidth) {
78
+ const columns = GRID_ZOOM_COLUMNS[level];
79
+ const gap = GRID_ZOOM_GAPS[level];
80
+ // Sized so that exactly `columns` tracks fit (the hair subtracted keeps
81
+ // rounding from dropping a column).
82
+ const minWidth = `calc((100% - ${(columns - 1) * gap}px) / ${columns} - 0.5px)`;
83
+ const styles = {
84
+ '--infiniteScrollerCellMinWidth': minWidth,
85
+ '--infiniteScrollerColGap': `${gap}px`,
86
+ '--infiniteScrollerRowGap': `${gap}px`,
87
+ '--infiniteScrollerCellMinHeight': '0px',
88
+ };
89
+ if (columns >= THUMBNAIL_ONLY_COLUMNS && gridWidth > 0) {
90
+ const tileWidth = Math.floor((gridWidth - (columns - 1) * gap) / columns);
91
+ styles['--imgBlockHeight'] = `${tileWidth}px`;
92
+ styles['--imgHeight'] = `${tileWidth}px`;
93
+ }
94
+ else {
95
+ styles['--infiniteScrollerCellMinHeight'] =
96
+ columns >= 6 ? '12rem' : '22.5rem';
97
+ }
98
+ return styles;
99
+ }
100
+ /** The tile layout to use at `level`: dense levels drop details, the densest show only thumbnails. */
101
+ export function zoomLayoutType(level) {
102
+ const columns = GRID_ZOOM_COLUMNS[level];
103
+ if (columns >= THUMBNAIL_ONLY_COLUMNS)
104
+ return 'thumbnail-only';
105
+ if (columns >= MINIMAL_LAYOUT_COLUMNS)
106
+ return 'minimal';
107
+ return undefined;
108
+ }
109
+ //# sourceMappingURL=grid-zoom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grid-zoom.js","sourceRoot":"","sources":["../../../src/utils/grid-zoom.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LayoutType } from '../tiles/models';\n\n/**\n * The column counts the tile grid can snap to when pinch-zoom is enabled,\n * from the biggest tiles to the densest thumbnail mosaic.\n */\nexport const GRID_ZOOM_COLUMNS = [1, 2, 3, 4, 6, 8, 12] as const;\n\n/** The column gap (in px) used at each zoom level, indexed like `GRID_ZOOM_COLUMNS`. */\nconst GRID_ZOOM_GAPS = [17, 17, 14, 12, 8, 5, 2] as const;\n\n/** Levels at or beyond this many columns use the tiles' minimal layout. */\nconst MINIMAL_LAYOUT_COLUMNS = 6;\n\n/** Levels at or beyond this many columns show only thumbnails. */\nconst THUMBNAIL_ONLY_COLUMNS = 8;\n\n/** How far past the biggest or smallest level a gesture can stretch before snapping. */\nconst MAX_OVERSHOOT = 1.15;\n\nexport const GRID_ZOOM_STORAGE_KEY = 'ia-collection-browser-grid-zoom';\n\nexport const MIN_ZOOM_LEVEL = 0;\nexport const MAX_ZOOM_LEVEL = GRID_ZOOM_COLUMNS.length - 1;\n\n/** Returns the index of the zoom level whose column count is closest to `columns`. */\nexport function nearestZoomLevel(columns: number): number {\n let best = 0;\n let bestDistance = Infinity;\n GRID_ZOOM_COLUMNS.forEach((levelColumns, level) => {\n const distance = Math.abs(Math.log(levelColumns / columns));\n if (distance < bestDistance) {\n best = level;\n bestDistance = distance;\n }\n });\n return best;\n}\n\nexport function clampZoomLevel(level: number): number {\n return Math.min(MAX_ZOOM_LEVEL, Math.max(MIN_ZOOM_LEVEL, Math.round(level)));\n}\n\n/**\n * Clamps a gesture scale so the tiles can't be stretched past the biggest or\n * smallest level (with a little give so the limit feels soft).\n * A scale above 1 means bigger tiles, so fewer columns.\n */\nexport function clampGestureScale(startColumns: number, scale: number): number {\n const maxScale =\n (startColumns / GRID_ZOOM_COLUMNS[MIN_ZOOM_LEVEL]) * MAX_OVERSHOOT;\n const minScale =\n startColumns / GRID_ZOOM_COLUMNS[MAX_ZOOM_LEVEL] / MAX_OVERSHOOT;\n return Math.min(maxScale, Math.max(minScale, scale));\n}\n\n/** The level a gesture of the given scale should settle on. */\nexport function zoomLevelForScale(startColumns: number, scale: number): number {\n return nearestZoomLevel(startColumns / scale);\n}\n\n/** Reads a persisted zoom level (stored as a column count), if there is a valid one. */\nexport function loadStoredZoomLevel(): number | undefined {\n try {\n const stored = Number(localStorage.getItem(GRID_ZOOM_STORAGE_KEY));\n if (!stored) return undefined;\n return nearestZoomLevel(stored);\n } catch {\n return undefined;\n }\n}\n\nexport function storeZoomLevel(level: number | undefined): void {\n try {\n if (level === undefined) {\n localStorage.removeItem(GRID_ZOOM_STORAGE_KEY);\n } else {\n localStorage.setItem(\n GRID_ZOOM_STORAGE_KEY,\n String(GRID_ZOOM_COLUMNS[level]),\n );\n }\n } catch {\n // Storage can be unavailable (private windows, blocked site data).\n }\n}\n\n/**\n * The inline CSS variables that give the infinite scroller `level`'s layout.\n * `gridWidth` is the scroller's width in px, which sets the thumbnail size\n * at the levels that show only thumbnails.\n */\nexport function zoomLevelStyles(\n level: number,\n gridWidth: number,\n): Record<string, string> {\n const columns = GRID_ZOOM_COLUMNS[level];\n const gap = GRID_ZOOM_GAPS[level];\n // Sized so that exactly `columns` tracks fit (the hair subtracted keeps\n // rounding from dropping a column).\n const minWidth = `calc((100% - ${(columns - 1) * gap}px) / ${columns} - 0.5px)`;\n const styles: Record<string, string> = {\n '--infiniteScrollerCellMinWidth': minWidth,\n '--infiniteScrollerColGap': `${gap}px`,\n '--infiniteScrollerRowGap': `${gap}px`,\n '--infiniteScrollerCellMinHeight': '0px',\n };\n if (columns >= THUMBNAIL_ONLY_COLUMNS && gridWidth > 0) {\n const tileWidth = Math.floor((gridWidth - (columns - 1) * gap) / columns);\n styles['--imgBlockHeight'] = `${tileWidth}px`;\n styles['--imgHeight'] = `${tileWidth}px`;\n } else {\n styles['--infiniteScrollerCellMinHeight'] =\n columns >= 6 ? '12rem' : '22.5rem';\n }\n return styles;\n}\n\n/** The tile layout to use at `level`: dense levels drop details, the densest show only thumbnails. */\nexport function zoomLayoutType(level: number): LayoutType | undefined {\n const columns = GRID_ZOOM_COLUMNS[level];\n if (columns >= THUMBNAIL_ONLY_COLUMNS) return 'thumbnail-only';\n if (columns >= MINIMAL_LAYOUT_COLUMNS) return 'minimal';\n return undefined;\n}\n"]}
package/package.json CHANGED
@@ -7,13 +7,12 @@
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/internetarchive/iaux-collection-browser.git"
9
9
  },
10
- "version": "4.8.1-webdev-9227.4",
10
+ "version": "4.9.1-webdev-9670.0",
11
11
  "main": "dist/index.js",
12
12
  "module": "dist/index.js",
13
13
  "files": [
14
14
  "dist",
15
- "!dist/test",
16
- "locales"
15
+ "!dist/test"
17
16
  ],
18
17
  "packageManager": "pnpm@11.27.1",
19
18
  "engines": {
@@ -27,8 +26,6 @@
27
26
  "lint": "eslint . && prettier \"**/*.ts\" --check",
28
27
  "format": "eslint . --fix && prettier \"**/*.ts\" --write",
29
28
  "circular": "madge --circular --extensions ts .",
30
- "strings:extract": "lit-localize extract",
31
- "strings:build": "node scripts/build-locales.mts && prettier --write \"src/locales/*.ts\"",
32
29
  "test": "tsc && pnpm run lint && pnpm run circular && wtr --coverage",
33
30
  "test:fast": "tsc && wtr --coverage",
34
31
  "test:watch": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wtr --watch\"",
@@ -61,7 +58,6 @@
61
58
  "@eslint/eslintrc": "^3.3.7",
62
59
  "@eslint/js": "^9.22.0",
63
60
  "@internetarchive/result-type": "^0.0.1",
64
- "@lit/localize-tools": "^0.8.2",
65
61
  "@open-wc/eslint-config": "^13.0.0",
66
62
  "@open-wc/testing": "^4.0.0",
67
63
  "@rollup/plugin-image": "^3.0.3",