@internetarchive/elements 0.2.13 → 0.2.14-webdev-9022.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.
- package/dist/src/elements/ia-image-viewer/ia-image-viewer.d.ts +147 -0
- package/dist/src/elements/ia-image-viewer/ia-image-viewer.d.ts.map +1 -0
- package/dist/src/elements/ia-image-viewer/ia-image-viewer.js +533 -0
- package/dist/src/elements/ia-image-viewer/ia-image-viewer.js.map +1 -0
- package/dist/src/elements/ia-image-viewer/ia-imgview-controls.d.ts +22 -0
- package/dist/src/elements/ia-image-viewer/ia-imgview-controls.d.ts.map +1 -0
- package/dist/src/elements/ia-image-viewer/ia-imgview-controls.js +145 -0
- package/dist/src/elements/ia-image-viewer/ia-imgview-controls.js.map +1 -0
- package/dist/src/elements/ia-image-viewer/ia-imgview-slide.d.ts +44 -0
- package/dist/src/elements/ia-image-viewer/ia-imgview-slide.d.ts.map +1 -0
- package/dist/src/elements/ia-image-viewer/ia-imgview-slide.js +213 -0
- package/dist/src/elements/ia-image-viewer/ia-imgview-slide.js.map +1 -0
- package/dist/src/elements/ia-image-viewer/image-viewer-gestures.d.ts +79 -0
- package/dist/src/elements/ia-image-viewer/image-viewer-gestures.d.ts.map +1 -0
- package/dist/src/elements/ia-image-viewer/image-viewer-gestures.js +254 -0
- package/dist/src/elements/ia-image-viewer/image-viewer-gestures.js.map +1 -0
- package/dist/src/elements/ia-image-viewer/models.d.ts +20 -0
- package/dist/src/elements/ia-image-viewer/models.d.ts.map +1 -0
- package/dist/src/elements/ia-image-viewer/models.js +2 -0
- package/dist/src/elements/ia-image-viewer/models.js.map +1 -0
- package/dist/src/elements/ia-item-navigator/icons.js +1 -1
- package/dist/src/elements/ia-item-navigator/icons.js.map +1 -1
- package/dist/src/elements/ia-item-navigator/menus/ia-itemnav-sort-files-button.js +1 -1
- package/dist/src/elements/ia-item-navigator/menus/ia-itemnav-sort-files-button.js.map +1 -1
- package/dist/src/elements/ia-item-navigator/menus/ia-itemnav-viewable-files-panel.js +1 -1
- package/dist/src/elements/ia-item-navigator/menus/ia-itemnav-viewable-files-panel.js.map +1 -1
- package/dist/src/elements/ia-item-navigator/menus/share-icons.js +1 -1
- package/dist/src/elements/ia-item-navigator/menus/share-icons.js.map +1 -1
- package/dist/src/elements/ia-status-indicator/ia-status-indicator.d.ts +51 -5
- package/dist/src/elements/ia-status-indicator/ia-status-indicator.d.ts.map +1 -1
- package/dist/src/elements/ia-status-indicator/ia-status-indicator.js +156 -16
- package/dist/src/elements/ia-status-indicator/ia-status-indicator.js.map +1 -1
- package/dist/src/elements/ia-status-indicator/icons/audio.svg +5 -0
- package/dist/src/elements/ia-status-indicator/icons/collection.svg +5 -0
- package/dist/src/elements/ia-status-indicator/icons/etree.svg +5 -0
- package/dist/src/elements/ia-status-indicator/icons/images.svg +6 -0
- package/dist/src/elements/ia-status-indicator/icons/search.svg +10 -0
- package/dist/src/elements/ia-status-indicator/icons/software.svg +6 -0
- package/dist/src/elements/ia-status-indicator/icons/texts.svg +6 -0
- package/dist/src/elements/ia-status-indicator/icons/tv.svg +5 -0
- package/dist/src/elements/ia-status-indicator/icons/video.svg +6 -0
- package/dist/src/elements/ia-status-indicator/icons/web.svg +6 -0
- package/dist/src/elements/index.d.ts +1 -0
- package/dist/src/elements/index.d.ts.map +1 -1
- package/dist/src/elements/index.js +1 -0
- package/dist/src/elements/index.js.map +1 -1
- package/dist/src/util/masked-icon.d.ts.map +1 -0
- package/dist/src/util/masked-icon.js.map +1 -0
- package/dist/src/util/prefers-reduced-motion.d.ts +10 -0
- package/dist/src/util/prefers-reduced-motion.d.ts.map +1 -0
- package/dist/src/util/prefers-reduced-motion.js +12 -0
- package/dist/src/util/prefers-reduced-motion.js.map +1 -0
- package/package.json +1 -1
- package/dist/src/elements/ia-item-navigator/masked-icon.d.ts.map +0 -1
- package/dist/src/elements/ia-item-navigator/masked-icon.js.map +0 -1
- /package/dist/src/{elements/ia-item-navigator → util}/masked-icon.d.ts +0 -0
- /package/dist/src/{elements/ia-item-navigator → util}/masked-icon.js +0 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { prefersReducedMotion } from '../../util/prefers-reduced-motion.js';
|
|
2
|
+
/** Minimum horizontal travel before a gesture counts as a swipe at all */
|
|
3
|
+
const SWIPE_THRESHOLD = 30;
|
|
4
|
+
/** Minimum swipe distance (fraction of width) to commit navigation */
|
|
5
|
+
const SWIPE_COMMIT_FRACTION = 0.25;
|
|
6
|
+
/** Minimum velocity (px/ms) for a flick to commit regardless of distance */
|
|
7
|
+
const SWIPE_FLICK_VELOCITY = 0.3;
|
|
8
|
+
/** Longest the track takes to settle after the finger lifts */
|
|
9
|
+
const SETTLE_MAX_MS = 300;
|
|
10
|
+
/** How long a snap-back takes when the swipe didn't go far enough */
|
|
11
|
+
const SNAP_BACK_MS = 200;
|
|
12
|
+
/** Horizontal wheel travel that adds up to one navigation */
|
|
13
|
+
const WHEEL_THRESHOLD = 80;
|
|
14
|
+
/** How long after an animation to keep ignoring wheel inertia */
|
|
15
|
+
const WHEEL_COOLDOWN_MS = 300;
|
|
16
|
+
/** How far a finger travels before the gesture locks in as horizontal */
|
|
17
|
+
const SWIPE_LOCK_DISTANCE = 10;
|
|
18
|
+
/**
|
|
19
|
+
* Touch, wheel and keyboard navigation for the image viewer.
|
|
20
|
+
*
|
|
21
|
+
* A drag is driven imperatively: it translates the track directly and asks the
|
|
22
|
+
* viewer to insert neighbouring slides by hand, because a re-render mid-gesture
|
|
23
|
+
* would replace the element the touch started on and the browser would fire
|
|
24
|
+
* touchcancel.
|
|
25
|
+
*/
|
|
26
|
+
export class ImageViewerGestures {
|
|
27
|
+
constructor(host) {
|
|
28
|
+
/** Whether a drag is in progress. The viewer reads this while rendering. */
|
|
29
|
+
this.dragging = false;
|
|
30
|
+
/**
|
|
31
|
+
* The element the touch and wheel listeners are currently on, so they can
|
|
32
|
+
* follow the image area when it's replaced. A boolean wouldn't: the area is
|
|
33
|
+
* removed whenever the image list empties, and a later refill renders a
|
|
34
|
+
* different element.
|
|
35
|
+
*/
|
|
36
|
+
this.listeningTo = null;
|
|
37
|
+
this.touchStartX = null;
|
|
38
|
+
this.touchStartY = null;
|
|
39
|
+
this.touchStartTime = 0;
|
|
40
|
+
/** Whether the current gesture has locked in as a horizontal swipe */
|
|
41
|
+
this.swiping = false;
|
|
42
|
+
/** Set when a swipe completes, so the tap it ends on doesn't open the image */
|
|
43
|
+
this.swipeHandled = false;
|
|
44
|
+
this.dragOffsetX = 0;
|
|
45
|
+
this.wheelDeltaX = 0;
|
|
46
|
+
this.wheelAnimating = false;
|
|
47
|
+
this.wheelAnimationEndTime = 0;
|
|
48
|
+
this.onTouchStart = (e) => {
|
|
49
|
+
if (this.host.imageCount <= 1)
|
|
50
|
+
return;
|
|
51
|
+
this.host.settleIfAnimating();
|
|
52
|
+
const touch = e.touches[0];
|
|
53
|
+
this.touchStartX = touch.clientX;
|
|
54
|
+
this.touchStartY = touch.clientY;
|
|
55
|
+
this.touchStartTime = e.timeStamp;
|
|
56
|
+
this.swiping = false;
|
|
57
|
+
this.dragOffsetX = 0;
|
|
58
|
+
};
|
|
59
|
+
this.onTouchMove = (e) => {
|
|
60
|
+
if (this.touchStartX === null || this.touchStartY === null)
|
|
61
|
+
return;
|
|
62
|
+
const dx = e.touches[0].clientX - this.touchStartX;
|
|
63
|
+
const dy = e.touches[0].clientY - this.touchStartY;
|
|
64
|
+
const locksHorizontal = Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > SWIPE_LOCK_DISTANCE;
|
|
65
|
+
if (!this.swiping && locksHorizontal) {
|
|
66
|
+
this.swiping = true;
|
|
67
|
+
this.dragging = true;
|
|
68
|
+
this.host.preloadAroundIndex(this.host.currentIndex);
|
|
69
|
+
this.host.insertDragSlides();
|
|
70
|
+
}
|
|
71
|
+
if (!this.swiping)
|
|
72
|
+
return;
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
this.dragOffsetX = dx;
|
|
75
|
+
this.applyDragTransform();
|
|
76
|
+
};
|
|
77
|
+
this.onTouchEnd = (e) => {
|
|
78
|
+
if (this.touchStartX === null)
|
|
79
|
+
return;
|
|
80
|
+
const dx = e.changedTouches[0].clientX - this.touchStartX;
|
|
81
|
+
const elapsed = e.timeStamp - this.touchStartTime;
|
|
82
|
+
this.touchStartX = null;
|
|
83
|
+
this.touchStartY = null;
|
|
84
|
+
if (!this.swiping)
|
|
85
|
+
return;
|
|
86
|
+
this.swiping = false;
|
|
87
|
+
const track = this.host.slideTrack;
|
|
88
|
+
const area = this.host.imageArea;
|
|
89
|
+
if (!track || !area) {
|
|
90
|
+
this.dragging = false;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const width = area.offsetWidth;
|
|
94
|
+
const velocity = Math.abs(dx) / Math.max(elapsed, 1);
|
|
95
|
+
const pastThreshold = Math.abs(dx) > width * SWIPE_COMMIT_FRACTION;
|
|
96
|
+
const flicked = velocity > SWIPE_FLICK_VELOCITY;
|
|
97
|
+
const shouldCommit = (pastThreshold || flicked) && Math.abs(dx) >= SWIPE_THRESHOLD;
|
|
98
|
+
const direction = dx < 0 ? 'next' : 'prev';
|
|
99
|
+
this.swipeHandled = true;
|
|
100
|
+
// The rest of the way is proportional to how far the finger already got,
|
|
101
|
+
// so a nearly complete swipe finishes almost instantly.
|
|
102
|
+
const remainingFraction = (width - Math.abs(dx)) / width;
|
|
103
|
+
const completionDuration = shouldCommit
|
|
104
|
+
? Math.round(Math.max(100, Math.min(SETTLE_MAX_MS, remainingFraction * SETTLE_MAX_MS)))
|
|
105
|
+
: SNAP_BACK_MS;
|
|
106
|
+
const finalTransform = !shouldCommit
|
|
107
|
+
? 'translateX(-100%)'
|
|
108
|
+
: direction === 'next'
|
|
109
|
+
? 'translateX(-200%)'
|
|
110
|
+
: 'translateX(0%)';
|
|
111
|
+
const finish = () => {
|
|
112
|
+
track.style.transition = '';
|
|
113
|
+
track.style.transform = '';
|
|
114
|
+
if (shouldCommit)
|
|
115
|
+
this.host.commitDrag(direction);
|
|
116
|
+
this.dragging = false;
|
|
117
|
+
this.dragOffsetX = 0;
|
|
118
|
+
// Remove the hand-inserted slides before the viewer renders, since it
|
|
119
|
+
// doesn't track them and would leave them in the track.
|
|
120
|
+
this.host.removeDragSlides();
|
|
121
|
+
this.host.preloadAroundIndex(this.host.currentIndex);
|
|
122
|
+
this.host.requestUpdate();
|
|
123
|
+
};
|
|
124
|
+
// With reduced motion there's nothing to transition, and a zero-duration
|
|
125
|
+
// transition doesn't fire transitionend, so settle straight away rather
|
|
126
|
+
// than waiting on the timeout that would otherwise cover for it.
|
|
127
|
+
if (prefersReducedMotion()) {
|
|
128
|
+
track.style.transform = finalTransform;
|
|
129
|
+
finish();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
track.style.transition = `transform ${completionDuration}ms ease-out`;
|
|
133
|
+
track.style.transform = finalTransform;
|
|
134
|
+
let settled = false;
|
|
135
|
+
const onTransitionDone = () => {
|
|
136
|
+
if (settled)
|
|
137
|
+
return;
|
|
138
|
+
settled = true;
|
|
139
|
+
track.removeEventListener('transitionend', onTransitionDone);
|
|
140
|
+
finish();
|
|
141
|
+
};
|
|
142
|
+
track.addEventListener('transitionend', onTransitionDone);
|
|
143
|
+
// Fallback in case transitionend doesn't fire
|
|
144
|
+
setTimeout(onTransitionDone, completionDuration + 50);
|
|
145
|
+
};
|
|
146
|
+
this.onWheel = (e) => {
|
|
147
|
+
if (this.host.imageCount <= 1)
|
|
148
|
+
return;
|
|
149
|
+
// Ignore vertical-dominant scrolls
|
|
150
|
+
if (Math.abs(e.deltaY) > Math.abs(e.deltaX))
|
|
151
|
+
return;
|
|
152
|
+
if (e.deltaX === 0)
|
|
153
|
+
return;
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
// Block during animation and for a brief cooldown after to absorb inertia
|
|
156
|
+
const inCooldown = performance.now() - this.wheelAnimationEndTime < WHEEL_COOLDOWN_MS;
|
|
157
|
+
if (this.wheelAnimating || inCooldown) {
|
|
158
|
+
this.wheelDeltaX = 0;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this.wheelDeltaX += e.deltaX;
|
|
162
|
+
if (Math.abs(this.wheelDeltaX) < WHEEL_THRESHOLD)
|
|
163
|
+
return;
|
|
164
|
+
const forward = this.wheelDeltaX > 0;
|
|
165
|
+
this.wheelDeltaX = 0;
|
|
166
|
+
this.wheelAnimating = true;
|
|
167
|
+
if (forward)
|
|
168
|
+
this.host.showNext();
|
|
169
|
+
else
|
|
170
|
+
this.host.showPrevious();
|
|
171
|
+
};
|
|
172
|
+
this.onKeydown = (e) => {
|
|
173
|
+
if (this.host.imageCount <= 1)
|
|
174
|
+
return;
|
|
175
|
+
const target = e.target;
|
|
176
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')
|
|
177
|
+
return;
|
|
178
|
+
if (target.isContentEditable)
|
|
179
|
+
return;
|
|
180
|
+
if (e.key === 'ArrowLeft')
|
|
181
|
+
this.host.showPrevious();
|
|
182
|
+
else if (e.key === 'ArrowRight')
|
|
183
|
+
this.host.showNext();
|
|
184
|
+
};
|
|
185
|
+
this.host = host;
|
|
186
|
+
host.addController(this);
|
|
187
|
+
}
|
|
188
|
+
hostConnected() {
|
|
189
|
+
document.addEventListener('keydown', this.onKeydown);
|
|
190
|
+
// Reconnecting doesn't schedule an update, so the gesture listeners have
|
|
191
|
+
// to be put back here rather than waiting for one.
|
|
192
|
+
this.attachListeners();
|
|
193
|
+
}
|
|
194
|
+
hostUpdated() {
|
|
195
|
+
this.attachListeners();
|
|
196
|
+
}
|
|
197
|
+
hostDisconnected() {
|
|
198
|
+
document.removeEventListener('keydown', this.onKeydown);
|
|
199
|
+
this.detachListeners();
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Whether a swipe just ended on this tap, meaning the click that follows it
|
|
203
|
+
* should be ignored rather than treated as opening the image.
|
|
204
|
+
*/
|
|
205
|
+
consumeSwipeTap() {
|
|
206
|
+
if (!this.swipeHandled)
|
|
207
|
+
return false;
|
|
208
|
+
this.swipeHandled = false;
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
/** Lets wheel navigation resume once a slide animation has finished. */
|
|
212
|
+
animationSettled() {
|
|
213
|
+
this.wheelAnimating = false;
|
|
214
|
+
this.wheelAnimationEndTime = performance.now();
|
|
215
|
+
this.wheelDeltaX = 0;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Points the gesture listeners at the current image area, moving them off
|
|
219
|
+
* the previous one. The area isn't rendered until there are images to show,
|
|
220
|
+
* and it's torn down and rebuilt whenever the list empties and refills, so
|
|
221
|
+
* this runs on every update and re-attaches whenever the element changes.
|
|
222
|
+
*/
|
|
223
|
+
attachListeners() {
|
|
224
|
+
const area = this.host.imageArea;
|
|
225
|
+
if (area === this.listeningTo)
|
|
226
|
+
return;
|
|
227
|
+
this.detachListeners();
|
|
228
|
+
if (!area)
|
|
229
|
+
return;
|
|
230
|
+
this.listeningTo = area;
|
|
231
|
+
area.addEventListener('touchstart', this.onTouchStart, { passive: true });
|
|
232
|
+
area.addEventListener('touchmove', this.onTouchMove, { passive: false });
|
|
233
|
+
area.addEventListener('touchend', this.onTouchEnd, { passive: true });
|
|
234
|
+
area.addEventListener('wheel', this.onWheel, { passive: false });
|
|
235
|
+
}
|
|
236
|
+
detachListeners() {
|
|
237
|
+
const area = this.listeningTo;
|
|
238
|
+
if (!area)
|
|
239
|
+
return;
|
|
240
|
+
area.removeEventListener('touchstart', this.onTouchStart);
|
|
241
|
+
area.removeEventListener('touchmove', this.onTouchMove);
|
|
242
|
+
area.removeEventListener('touchend', this.onTouchEnd);
|
|
243
|
+
area.removeEventListener('wheel', this.onWheel);
|
|
244
|
+
this.listeningTo = null;
|
|
245
|
+
}
|
|
246
|
+
/** Applies the drag offset to the track during touch gestures. */
|
|
247
|
+
applyDragTransform() {
|
|
248
|
+
const track = this.host.slideTrack;
|
|
249
|
+
if (!this.dragging || !track)
|
|
250
|
+
return;
|
|
251
|
+
track.style.transform = `translateX(calc(-100% + ${this.dragOffsetX}px))`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=image-viewer-gestures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-viewer-gestures.js","sourceRoot":"","sources":["../../../../src/elements/ia-image-viewer/image-viewer-gestures.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit';\n\nimport { prefersReducedMotion } from '@src/util/prefers-reduced-motion';\nimport type { SlideDirection } from './models';\n\n/** What the gesture controller needs from the viewer it drives. */\nexport type ImageViewerGestureHost = ReactiveControllerHost & {\n readonly imageCount: number;\n\n readonly currentIndex: number;\n\n /** The element gestures are tracked against, once it has rendered. */\n readonly imageArea?: HTMLElement;\n\n /** The element a drag translates. */\n readonly slideTrack?: HTMLElement;\n\n /** Commits an in-flight slide animation so a new one starts from rest. */\n settleIfAnimating(): void;\n\n showNext(): void;\n\n showPrevious(): void;\n\n preloadAroundIndex(index: number): void;\n\n /** Adds the neighbouring slides a drag reveals on either side. */\n insertDragSlides(): void;\n\n removeDragSlides(): void;\n\n /** Advances to the neighbouring image a completed drag landed on. */\n commitDrag(direction: SlideDirection): void;\n};\n\n/** Minimum horizontal travel before a gesture counts as a swipe at all */\nconst SWIPE_THRESHOLD = 30;\n\n/** Minimum swipe distance (fraction of width) to commit navigation */\nconst SWIPE_COMMIT_FRACTION = 0.25;\n\n/** Minimum velocity (px/ms) for a flick to commit regardless of distance */\nconst SWIPE_FLICK_VELOCITY = 0.3;\n\n/** Longest the track takes to settle after the finger lifts */\nconst SETTLE_MAX_MS = 300;\n\n/** How long a snap-back takes when the swipe didn't go far enough */\nconst SNAP_BACK_MS = 200;\n\n/** Horizontal wheel travel that adds up to one navigation */\nconst WHEEL_THRESHOLD = 80;\n\n/** How long after an animation to keep ignoring wheel inertia */\nconst WHEEL_COOLDOWN_MS = 300;\n\n/** How far a finger travels before the gesture locks in as horizontal */\nconst SWIPE_LOCK_DISTANCE = 10;\n\n/**\n * Touch, wheel and keyboard navigation for the image viewer.\n *\n * A drag is driven imperatively: it translates the track directly and asks the\n * viewer to insert neighbouring slides by hand, because a re-render mid-gesture\n * would replace the element the touch started on and the browser would fire\n * touchcancel.\n */\nexport class ImageViewerGestures implements ReactiveController {\n /** Whether a drag is in progress. The viewer reads this while rendering. */\n dragging = false;\n\n private host: ImageViewerGestureHost;\n\n /**\n * The element the touch and wheel listeners are currently on, so they can\n * follow the image area when it's replaced. A boolean wouldn't: the area is\n * removed whenever the image list empties, and a later refill renders a\n * different element.\n */\n private listeningTo: HTMLElement | null = null;\n\n private touchStartX: number | null = null;\n\n private touchStartY: number | null = null;\n\n private touchStartTime = 0;\n\n /** Whether the current gesture has locked in as a horizontal swipe */\n private swiping = false;\n\n /** Set when a swipe completes, so the tap it ends on doesn't open the image */\n private swipeHandled = false;\n\n private dragOffsetX = 0;\n\n private wheelDeltaX = 0;\n\n private wheelAnimating = false;\n\n private wheelAnimationEndTime = 0;\n\n constructor(host: ImageViewerGestureHost) {\n this.host = host;\n host.addController(this);\n }\n\n hostConnected(): void {\n document.addEventListener('keydown', this.onKeydown);\n // Reconnecting doesn't schedule an update, so the gesture listeners have\n // to be put back here rather than waiting for one.\n this.attachListeners();\n }\n\n hostUpdated(): void {\n this.attachListeners();\n }\n\n hostDisconnected(): void {\n document.removeEventListener('keydown', this.onKeydown);\n this.detachListeners();\n }\n\n /**\n * Whether a swipe just ended on this tap, meaning the click that follows it\n * should be ignored rather than treated as opening the image.\n */\n consumeSwipeTap(): boolean {\n if (!this.swipeHandled) return false;\n this.swipeHandled = false;\n return true;\n }\n\n /** Lets wheel navigation resume once a slide animation has finished. */\n animationSettled(): void {\n this.wheelAnimating = false;\n this.wheelAnimationEndTime = performance.now();\n this.wheelDeltaX = 0;\n }\n\n /**\n * Points the gesture listeners at the current image area, moving them off\n * the previous one. The area isn't rendered until there are images to show,\n * and it's torn down and rebuilt whenever the list empties and refills, so\n * this runs on every update and re-attaches whenever the element changes.\n */\n private attachListeners(): void {\n const area = this.host.imageArea;\n if (area === this.listeningTo) return;\n this.detachListeners();\n if (!area) return;\n\n this.listeningTo = area;\n area.addEventListener('touchstart', this.onTouchStart, { passive: true });\n area.addEventListener('touchmove', this.onTouchMove, { passive: false });\n area.addEventListener('touchend', this.onTouchEnd, { passive: true });\n area.addEventListener('wheel', this.onWheel, { passive: false });\n }\n\n private detachListeners(): void {\n const area = this.listeningTo;\n if (!area) return;\n area.removeEventListener('touchstart', this.onTouchStart);\n area.removeEventListener('touchmove', this.onTouchMove);\n area.removeEventListener('touchend', this.onTouchEnd);\n area.removeEventListener('wheel', this.onWheel);\n this.listeningTo = null;\n }\n\n private onTouchStart = (e: TouchEvent): void => {\n if (this.host.imageCount <= 1) return;\n this.host.settleIfAnimating();\n const touch = e.touches[0];\n this.touchStartX = touch.clientX;\n this.touchStartY = touch.clientY;\n this.touchStartTime = e.timeStamp;\n this.swiping = false;\n this.dragOffsetX = 0;\n };\n\n private onTouchMove = (e: TouchEvent): void => {\n if (this.touchStartX === null || this.touchStartY === null) return;\n const dx = e.touches[0].clientX - this.touchStartX;\n const dy = e.touches[0].clientY - this.touchStartY;\n\n const locksHorizontal =\n Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > SWIPE_LOCK_DISTANCE;\n if (!this.swiping && locksHorizontal) {\n this.swiping = true;\n this.dragging = true;\n this.host.preloadAroundIndex(this.host.currentIndex);\n this.host.insertDragSlides();\n }\n\n if (!this.swiping) return;\n e.preventDefault();\n\n this.dragOffsetX = dx;\n this.applyDragTransform();\n };\n\n private onTouchEnd = (e: TouchEvent): void => {\n if (this.touchStartX === null) return;\n const dx = e.changedTouches[0].clientX - this.touchStartX;\n const elapsed = e.timeStamp - this.touchStartTime;\n this.touchStartX = null;\n this.touchStartY = null;\n\n if (!this.swiping) return;\n this.swiping = false;\n\n const track = this.host.slideTrack;\n const area = this.host.imageArea;\n if (!track || !area) {\n this.dragging = false;\n return;\n }\n\n const width = area.offsetWidth;\n const velocity = Math.abs(dx) / Math.max(elapsed, 1);\n const pastThreshold = Math.abs(dx) > width * SWIPE_COMMIT_FRACTION;\n const flicked = velocity > SWIPE_FLICK_VELOCITY;\n const shouldCommit =\n (pastThreshold || flicked) && Math.abs(dx) >= SWIPE_THRESHOLD;\n const direction: SlideDirection = dx < 0 ? 'next' : 'prev';\n\n this.swipeHandled = true;\n\n // The rest of the way is proportional to how far the finger already got,\n // so a nearly complete swipe finishes almost instantly.\n const remainingFraction = (width - Math.abs(dx)) / width;\n const completionDuration = shouldCommit\n ? Math.round(\n Math.max(\n 100,\n Math.min(SETTLE_MAX_MS, remainingFraction * SETTLE_MAX_MS),\n ),\n )\n : SNAP_BACK_MS;\n\n const finalTransform = !shouldCommit\n ? 'translateX(-100%)'\n : direction === 'next'\n ? 'translateX(-200%)'\n : 'translateX(0%)';\n\n const finish = (): void => {\n track.style.transition = '';\n track.style.transform = '';\n\n if (shouldCommit) this.host.commitDrag(direction);\n\n this.dragging = false;\n this.dragOffsetX = 0;\n // Remove the hand-inserted slides before the viewer renders, since it\n // doesn't track them and would leave them in the track.\n this.host.removeDragSlides();\n this.host.preloadAroundIndex(this.host.currentIndex);\n this.host.requestUpdate();\n };\n\n // With reduced motion there's nothing to transition, and a zero-duration\n // transition doesn't fire transitionend, so settle straight away rather\n // than waiting on the timeout that would otherwise cover for it.\n if (prefersReducedMotion()) {\n track.style.transform = finalTransform;\n finish();\n return;\n }\n\n track.style.transition = `transform ${completionDuration}ms ease-out`;\n track.style.transform = finalTransform;\n\n let settled = false;\n const onTransitionDone = (): void => {\n if (settled) return;\n settled = true;\n track.removeEventListener('transitionend', onTransitionDone);\n finish();\n };\n\n track.addEventListener('transitionend', onTransitionDone);\n // Fallback in case transitionend doesn't fire\n setTimeout(onTransitionDone, completionDuration + 50);\n };\n\n /** Applies the drag offset to the track during touch gestures. */\n private applyDragTransform(): void {\n const track = this.host.slideTrack;\n if (!this.dragging || !track) return;\n track.style.transform = `translateX(calc(-100% + ${this.dragOffsetX}px))`;\n }\n\n private onWheel = (e: WheelEvent): void => {\n if (this.host.imageCount <= 1) return;\n\n // Ignore vertical-dominant scrolls\n if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) return;\n if (e.deltaX === 0) return;\n\n e.preventDefault();\n\n // Block during animation and for a brief cooldown after to absorb inertia\n const inCooldown =\n performance.now() - this.wheelAnimationEndTime < WHEEL_COOLDOWN_MS;\n if (this.wheelAnimating || inCooldown) {\n this.wheelDeltaX = 0;\n return;\n }\n\n this.wheelDeltaX += e.deltaX;\n if (Math.abs(this.wheelDeltaX) < WHEEL_THRESHOLD) return;\n\n const forward = this.wheelDeltaX > 0;\n this.wheelDeltaX = 0;\n this.wheelAnimating = true;\n if (forward) this.host.showNext();\n else this.host.showPrevious();\n };\n\n private onKeydown = (e: KeyboardEvent): void => {\n if (this.host.imageCount <= 1) return;\n const target = e.target as HTMLElement;\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') return;\n if (target.isContentEditable) return;\n if (e.key === 'ArrowLeft') this.host.showPrevious();\n else if (e.key === 'ArrowRight') this.host.showNext();\n };\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One image the viewer can show.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately plain data. The viewer doesn't know where the images come from
|
|
5
|
+
* or how their URLs were built, so a host can feed it archive.org derivatives,
|
|
6
|
+
* a local directory listing, or anything else.
|
|
7
|
+
*/
|
|
8
|
+
export type ImageViewerImage = {
|
|
9
|
+
/** Identifies the image within the set. Used as the key in change events. */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Where to load the image from, and where the link points. */
|
|
12
|
+
url: string;
|
|
13
|
+
/** Alt text. Falls back to `name` when absent. */
|
|
14
|
+
title?: string;
|
|
15
|
+
};
|
|
16
|
+
/** Which way the viewer is moving through the set. */
|
|
17
|
+
export type SlideDirection = 'next' | 'prev';
|
|
18
|
+
/** Which edge the viewer just wrapped around. */
|
|
19
|
+
export type WrapEdge = 'start' | 'end';
|
|
20
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../../../src/elements/ia-image-viewer/models.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IAEb,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAC;IAEZ,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,sDAAsD;AACtD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;AAE7C,iDAAiD;AACjD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../../../src/elements/ia-image-viewer/models.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * One image the viewer can show.\n *\n * Deliberately plain data. The viewer doesn't know where the images come from\n * or how their URLs were built, so a host can feed it archive.org derivatives,\n * a local directory listing, or anything else.\n */\nexport type ImageViewerImage = {\n /** Identifies the image within the set. Used as the key in change events. */\n name: string;\n\n /** Where to load the image from, and where the link points. */\n url: string;\n\n /** Alt text. Falls back to `name` when absent. */\n title?: string;\n};\n\n/** Which way the viewer is moving through the set. */\nexport type SlideDirection = 'next' | 'prev';\n\n/** Which edge the viewer just wrapped around. */\nexport type WrapEdge = 'start' | 'end';\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icons.js","sourceRoot":"","sources":["../../../../src/elements/ia-item-navigator/icons.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"icons.js","sourceRoot":"","sources":["../../../../src/elements/ia-item-navigator/icons.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,WAAW,MAAM,sBAAsB,CAAC;AAC/C,OAAO,kBAAkB,MAAM,8BAA8B,CAAC;AAE9D;;;;;;;GAOG;AAEH,qEAAqE;AACrE,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;AAEpD,8EAA8E;AAC9E,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC,kBAAkB,CAAC,CAAC","sourcesContent":["import { maskedIcon } from '@src/util/masked-icon';\n\nimport ellipsesUrl from './icons/ellipses.svg';\nimport collapseSidebarUrl from './icons/collapse-sidebar.svg';\n\n/**\n * Icons taken from the `@internetarchive/icon-ellipses` and\n * `@internetarchive/icon-collapse-sidebar` packages (v1.4.1). They ship as\n * standalone `.svg` files — rather than icon package dependencies or inline\n * templates — so their path data stays out of the JS bundle. Each is rendered\n * as a CSS-masked span (see `maskedIcon`) so host components can still recolor\n * it via their own custom properties.\n */\n\n/** Three-dot \"more options\" glyph used by the menu toggle button. */\nexport const ellipsesIcon = maskedIcon(ellipsesUrl);\n\n/** Circular \"collapse sidebar\" glyph used by the menu/panel close buttons. */\nexport const collapseSidebarIcon = maskedIcon(collapseSidebarUrl);\n"]}
|
|
@@ -2,7 +2,7 @@ import { __decorate } from "tslib";
|
|
|
2
2
|
import { css, html, LitElement, } from 'lit';
|
|
3
3
|
import { customElement, property } from 'lit/decorators.js';
|
|
4
4
|
import themeStyles from '../../../themes/theme-styles.js';
|
|
5
|
-
import { maskedIcon } from '
|
|
5
|
+
import { maskedIcon } from '../../../util/masked-icon.js';
|
|
6
6
|
import sortAscUrl from './icons/sort-asc.svg';
|
|
7
7
|
import sortDescUrl from './icons/sort-desc.svg';
|
|
8
8
|
import sortNeutralUrl from './icons/sort-neutral.svg';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ia-itemnav-sort-files-button.js","sourceRoot":"","sources":["../../../../../src/elements/ia-item-navigator/menus/ia-itemnav-sort-files-button.ts"],"names":[],"mappings":";AAAA,OAAO,EACL,GAAG,EACH,IAAI,EACJ,UAAU,GAGX,MAAM,KAAK,CAAC;AACb,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,WAAW,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"ia-itemnav-sort-files-button.js","sourceRoot":"","sources":["../../../../../src/elements/ia-item-navigator/menus/ia-itemnav-sort-files-button.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n css,\n html,\n LitElement,\n type CSSResultGroup,\n TemplateResult,\n} from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport themeStyles from '@src/themes/theme-styles';\nimport { maskedIcon } from '@src/util/masked-icon';\nimport sortAscUrl from './icons/sort-asc.svg';\nimport sortDescUrl from './icons/sort-desc.svg';\nimport sortNeutralUrl from './icons/sort-neutral.svg';\nimport type { FileSortOption, ViewableFileInfo } from './models';\n\nconst sortAscIcon = maskedIcon(sortAscUrl);\n\nconst sortDescIcon = maskedIcon(sortDescUrl);\n\nconst sortNeutralIcon = maskedIcon(sortNeutralUrl);\n\n/**\n * A tri-state sort toggle for the viewable-files panel header. Cycles\n * default → title ascending → title descending → default, sorting the supplied\n * file list and emitting `fileListSorted` on each change.\n */\n@customElement('ia-itemnav-sort-files-button')\nexport class IAItemNavSortFilesButton extends LitElement {\n @property({ type: Array }) fileListRaw: ViewableFileInfo[] = [];\n\n @property({ type: Array }) fileListSorted: ViewableFileInfo[] = [];\n\n @property({ type: String, reflect: true }) sortOrderBy: FileSortOption =\n 'default';\n\n render(): TemplateResult {\n return html`<div class=\"sort-multi-file-list\">${this.sortButton}</div>`;\n }\n\n get sortButton(): TemplateResult {\n const sortIcons: Record<FileSortOption, TemplateResult> = {\n default: html`\n <button\n class=\"sort-by neutral-icon\"\n aria-label=\"Sort volumes in initial order\"\n @click=${() => this.sortVolumes('title_asc')}\n >\n ${sortNeutralIcon}\n </button>\n `,\n title_asc: html`\n <button\n class=\"sort-by asc-icon\"\n aria-label=\"Sort volumes in ascending order\"\n @click=${() => this.sortVolumes('title_desc')}\n >\n ${sortAscIcon}\n </button>\n `,\n title_desc: html`\n <button\n class=\"sort-by desc-icon\"\n aria-label=\"Sort volumes in descending order\"\n @click=${() => this.sortVolumes('default')}\n >\n ${sortDescIcon}\n </button>\n `,\n };\n\n return sortIcons[this.sortOrderBy];\n }\n\n sortVolumes(sortType: FileSortOption): void {\n this.sortOrderBy = sortType;\n\n const sortedFiles = [...this.fileListRaw].sort((a, b) => {\n if (sortType === 'title_asc') return a.title.localeCompare(b.title);\n if (sortType === 'title_desc') return b.title.localeCompare(a.title);\n return (a.orig_sort ?? 0) - (b.orig_sort ?? 0);\n });\n\n this.dispatchEvent(\n new CustomEvent('fileListSorted', {\n detail: {\n sortType,\n sortedFiles,\n },\n bubbles: true,\n composed: true,\n }),\n );\n this.fileListSorted = sortedFiles;\n }\n\n static get styles(): CSSResultGroup {\n return [\n themeStyles,\n css`\n :host {\n /* Every glyph is square, so one knob sizes both axes. Matches the\n panel header's close button, which sits beside this one. */\n --item-navigator-header-icon-size--: var(\n --item-navigator-header-icon-size,\n 2em\n );\n --item-navigator-icon-color--: var(\n --item-navigator-icon-color,\n var(--item-navigator-text-color, var(--true-white))\n );\n\n /* 10px base (petabox scale); internal sizing is em against it. */\n --item-navigator-base-font-size--: var(\n --item-navigator-base-font-size,\n 10px\n );\n font-size: var(--item-navigator-base-font-size--);\n }\n\n button.sort-by {\n padding: 0;\n background-color: transparent;\n border: 0;\n cursor: pointer;\n display: inline-flex;\n /* Buttons don't inherit font-size, and the UA default would make the\n em-sized glyph larger than the close button beside it. */\n font: inherit;\n }\n\n /* The glyph is a masked span: the mask supplies the shape, this\n supplies the paint. */\n button.sort-by .ia-icon {\n width: var(--item-navigator-header-icon-size--);\n height: var(--item-navigator-header-icon-size--);\n background-color: var(--item-navigator-icon-color--);\n }\n `,\n ];\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ia-itemnav-sort-files-button': IAItemNavSortFilesButton;\n }\n}\n"]}
|
|
@@ -3,7 +3,7 @@ import { css, html, LitElement, nothing, } from 'lit';
|
|
|
3
3
|
import { customElement, property } from 'lit/decorators.js';
|
|
4
4
|
import { repeat } from 'lit/directives/repeat.js';
|
|
5
5
|
import themeStyles from '../../../themes/theme-styles.js';
|
|
6
|
-
import { maskedIcon } from '
|
|
6
|
+
import { maskedIcon } from '../../../util/masked-icon.js';
|
|
7
7
|
import viewableFilesUrl from './icons/viewable-files.svg';
|
|
8
8
|
/** Icon for the viewable-files menu shortcut / menu entry. */
|
|
9
9
|
export const viewableFilesIcon = maskedIcon(viewableFilesUrl);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ia-itemnav-viewable-files-panel.js","sourceRoot":"","sources":["../../../../../src/elements/ia-item-navigator/menus/ia-itemnav-viewable-files-panel.ts"],"names":[],"mappings":";AAAA,OAAO,EACL,GAAG,EACH,IAAI,EACJ,UAAU,EACV,OAAO,GAIR,MAAM,KAAK,CAAC;AACb,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,WAAW,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"ia-itemnav-viewable-files-panel.js","sourceRoot":"","sources":["../../../../../src/elements/ia-item-navigator/menus/ia-itemnav-viewable-files-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n css,\n html,\n LitElement,\n nothing,\n type PropertyValues,\n TemplateResult,\n type CSSResultGroup,\n} from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { repeat } from 'lit/directives/repeat.js';\nimport themeStyles from '@src/themes/theme-styles';\nimport { maskedIcon } from '@src/util/masked-icon';\nimport viewableFilesUrl from './icons/viewable-files.svg';\nimport type { FileSortOption, ViewableFileInfo } from './models';\n\n/** Icon for the viewable-files menu shortcut / menu entry. */\nexport const viewableFilesIcon = maskedIcon(viewableFilesUrl);\n\n/**\n * The \"viewable files\" side panel: a scrollable list of the item's viewable\n * files/volumes, linking each to its page on the host and highlighting the one\n * matching the current `subPrefix`. Populated by the host via `fileList`\n * (typically re-ordered by an `ia-itemnav-sort-files-button`).\n */\n@customElement('ia-itemnav-viewable-files-panel')\nexport class IAItemNavViewableFilesPanel extends LitElement {\n @property({ type: String }) baseHost: string = 'archive.org';\n\n @property({ type: String }) sortOrderBy: FileSortOption = 'default';\n\n @property({ type: String }) subPrefix: string = '';\n\n @property({ type: Array }) fileList: ViewableFileInfo[] = [];\n\n @property({ type: Boolean, reflect: true }) addSortToUrl = false;\n\n /**\n * Keeps the active file in view whenever the list or the selection changes.\n *\n * Both are needed: hosts commonly build this panel once and hand it a\n * `fileList` afterwards, and the selection can move while the panel stays\n * open. Reacting to the change means there is nothing to wait on — the row\n * exists by the time the update completes.\n */\n protected updated(changed: PropertyValues): void {\n if (changed.has('fileList') || changed.has('subPrefix')) {\n this.revealActiveFile();\n }\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n // Hosts reuse this element across opens, and the scroll container it sits\n // in may have been reset while it was away.\n this.revealActiveFile();\n }\n\n private async revealActiveFile(): Promise<void> {\n await this.updateComplete;\n // `nearest` leaves an already-visible row alone, which is what the\n // Chrome-only `scrollIntoViewIfNeeded` used to buy us.\n this.shadowRoot\n ?.querySelector<HTMLElement>('.content.active')\n ?.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }\n\n fileUrl(item: ViewableFileInfo): string {\n const baseUrl = `//${this.baseHost}${item.url_path}`;\n if (this.addSortToUrl && this.sortOrderBy !== 'default') {\n return `${baseUrl}?sort=${this.sortOrderBy}`;\n }\n return baseUrl;\n }\n\n get pdfLabel(): TemplateResult {\n return html`<span class=\"pdf-label\"\n ><span class=\"sr-only\">view this</span> PDF</span\n >`;\n }\n\n fileLi(item: ViewableFileInfo): TemplateResult {\n const activeClass = this.subPrefix === item.file_subprefix ? ' active' : '';\n const hrefUrl = this.fileUrl(item);\n const isPdf = (item.file_source ?? '').match(/^[^+]+\\.pdf$/i);\n\n return html`\n <li>\n <div class=\"separator\"></div>\n <div class=\"content${activeClass}\">\n <a href=${hrefUrl}>\n <p class=\"item-title\">\n ${item.title}${isPdf ? this.pdfLabel : nothing}\n </p>\n </a>\n </div>\n </li>\n `;\n }\n\n get fileListTemplate(): TemplateResult {\n const filesDisplay = repeat(\n this.fileList,\n (file) => file?.file_prefix,\n this.fileLi.bind(this),\n );\n return html`\n <ul>\n ${filesDisplay}\n <div class=\"separator\"></div>\n </ul>\n `;\n }\n\n render(): TemplateResult | typeof nothing {\n return this.fileList.length ? this.fileListTemplate : nothing;\n }\n\n static get styles(): CSSResultGroup {\n return [\n themeStyles,\n css`\n :host {\n --item-navigator-text-color--: var(\n --item-navigator-text-color,\n var(--true-white)\n );\n --item-navigator-border-color--: var(\n --item-navigator-border-color,\n #4b4b4b\n );\n --item-navigator-active-file-border-color--: var(\n --item-navigator-active-file-border-color,\n #538bc5\n );\n\n display: block;\n overflow-y: auto;\n box-sizing: border-box;\n color: var(--item-navigator-text-color--);\n margin-top: 14px;\n margin-bottom: 2em;\n --active-border-width--: 2px;\n /* 10px base (petabox scale); internal sizing is em against it. */\n --item-navigator-base-font-size--: var(\n --item-navigator-base-font-size,\n 10px\n );\n font-size: var(--item-navigator-base-font-size--);\n }\n\n a {\n color: var(--item-navigator-text-color--);\n text-decoration: none;\n }\n\n ul {\n padding: 0;\n list-style: none;\n margin: var(--active-border-width--) 0.5em 1em 0;\n }\n\n ul > li:first-child .separator {\n display: none;\n }\n\n li {\n cursor: pointer;\n position: relative;\n }\n\n li .content {\n border: var(--active-border-width--) solid transparent;\n padding: 0.2em 0 0.4em 0.2em;\n }\n\n li .content.active {\n border: var(--active-border-width--) solid\n var(--item-navigator-active-file-border-color--);\n }\n\n li.content a {\n display: flex;\n }\n\n .item-title {\n margin-block-start: 0em;\n margin-block-end: 0em;\n font-size: 14px;\n font-weight: bold;\n word-wrap: break-word;\n padding-left: 5px;\n }\n\n .separator {\n background-color: var(--item-navigator-border-color--);\n width: 98%;\n margin: 1px auto;\n height: 1px;\n }\n\n .pdf-label {\n border: 1px solid;\n padding: 2px 5px;\n border-radius: 20px;\n display: inline-block;\n margin-left: 5px;\n font-size: 0.9em;\n }\n\n .pdf-label .sr-only {\n position: absolute;\n clip: rect(1px, 1px, 1px, 1px);\n padding: 0;\n border: 0;\n height: 1px;\n width: 1px;\n overflow: hidden;\n }\n `,\n ];\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ia-itemnav-viewable-files-panel': IAItemNavViewableFilesPanel;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"share-icons.js","sourceRoot":"","sources":["../../../../../src/elements/ia-item-navigator/menus/share-icons.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"share-icons.js","sourceRoot":"","sources":["../../../../../src/elements/ia-item-navigator/menus/share-icons.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,QAAQ,MAAM,mBAAmB,CAAC;AACzC,OAAO,UAAU,MAAM,qBAAqB,CAAC;AAC7C,OAAO,WAAW,MAAM,sBAAsB,CAAC;AAC/C,OAAO,SAAS,MAAM,oBAAoB,CAAC;AAC3C,OAAO,YAAY,MAAM,uBAAuB,CAAC;AACjD,OAAO,QAAQ,MAAM,mBAAmB,CAAC;AACzC,OAAO,OAAO,MAAM,kBAAkB,CAAC;AAEvC;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,SAAS,GAAmB,UAAU,CAAC,QAAQ,CAAC,CAAC;AAE9D,MAAM,CAAC,MAAM,WAAW,GAAmB,UAAU,CAAC,UAAU,CAAC,CAAC;AAElE,MAAM,CAAC,MAAM,YAAY,GAAmB,UAAU,CAAC,WAAW,CAAC,CAAC;AAEpE,MAAM,CAAC,MAAM,UAAU,GAAmB,UAAU,CAAC,SAAS,CAAC,CAAC;AAEhE,MAAM,CAAC,MAAM,aAAa,GAAmB,UAAU,CAAC,YAAY,CAAC,CAAC;AAEtE,MAAM,CAAC,MAAM,SAAS,GAAmB,UAAU,CAAC,QAAQ,CAAC,CAAC;AAE9D,MAAM,CAAC,MAAM,QAAQ,GAAmB,UAAU,CAAC,OAAO,CAAC,CAAC","sourcesContent":["import { type TemplateResult } from 'lit';\nimport { maskedIcon } from '@src/util/masked-icon';\n\nimport shareUrl from './icons/share.svg';\nimport twitterUrl from './icons/twitter.svg';\nimport facebookUrl from './icons/facebook.svg';\nimport tumblrUrl from './icons/tumblr.svg';\nimport pinterestUrl from './icons/pinterest.svg';\nimport emailUrl from './icons/email.svg';\nimport linkUrl from './icons/link.svg';\n\n/**\n * Icons taken from the `@internetarchive/icon-*` packages (v1.4.1) used by the\n * share panel. They ship as standalone `.svg` files — rather than icon package\n * dependencies or inline templates — so their path data stays out of the JS\n * bundle, and render as CSS-masked spans (see `maskedIcon`) so the panel can\n * still theme them. Icons are decorative, labelled by adjacent text.\n */\n\nexport const shareIcon: TemplateResult = maskedIcon(shareUrl);\n\nexport const twitterIcon: TemplateResult = maskedIcon(twitterUrl);\n\nexport const facebookIcon: TemplateResult = maskedIcon(facebookUrl);\n\nexport const tumblrIcon: TemplateResult = maskedIcon(tumblrUrl);\n\nexport const pinterestIcon: TemplateResult = maskedIcon(pinterestUrl);\n\nexport const emailIcon: TemplateResult = maskedIcon(emailUrl);\n\nexport const linkIcon: TemplateResult = maskedIcon(linkUrl);\n"]}
|
|
@@ -1,24 +1,70 @@
|
|
|
1
1
|
import { CSSResultGroup, LitElement, TemplateResult } from 'lit';
|
|
2
2
|
export type LoadingStatus = 'ready' | 'loading' | 'success' | 'error';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* The mediatype glyphs this element ships. These are *icon* names, which do not
|
|
5
|
+
* map one-to-one onto archive.org mediatype values — a consumer holding a
|
|
6
|
+
* `MediaType` translates at its own call site (e.g. `movies` -> `video`).
|
|
7
|
+
*/
|
|
8
|
+
export type MediaTypeIcon = 'audio' | 'collection' | 'etree' | 'images' | 'search' | 'software' | 'texts' | 'tv' | 'video' | 'web';
|
|
9
|
+
/**
|
|
10
|
+
* Renders an SVG indicator, which defaults to an animated circular indicator.
|
|
11
|
+
*
|
|
12
|
+
* In `loading` mode the middle of the ring resolves in this order:
|
|
13
|
+
* 1. slotted `icon` content
|
|
14
|
+
* 2. the `mediatype` glyph
|
|
15
|
+
* 3. nothing, when `hideDots` is set
|
|
16
|
+
* 4. three animated dots (the default)
|
|
5
17
|
*/
|
|
6
18
|
export declare class IAStatusIndicator extends LitElement {
|
|
7
19
|
loadingTitle: string;
|
|
8
20
|
successTitle: string;
|
|
9
21
|
errorTitle: string;
|
|
10
|
-
loadingStyle: 'ring' | 'ring-dots';
|
|
11
22
|
mode: LoadingStatus;
|
|
23
|
+
mediatype?: MediaTypeIcon;
|
|
24
|
+
hideDots: boolean;
|
|
25
|
+
private hasSlottedIcon;
|
|
12
26
|
render(): TemplateResult;
|
|
13
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* The `ready` state: reserves the indicator's space without drawing, so a
|
|
29
|
+
* consumer's layout doesn't shift once the indicator appears.
|
|
30
|
+
*/
|
|
14
31
|
private get placeholderTemplate();
|
|
15
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* The asset URL for the current mediatype, if it maps to a glyph we ship.
|
|
34
|
+
*
|
|
35
|
+
* Unmapped values (e.g. archive.org's `account` or `data`) resolve to
|
|
36
|
+
* undefined and degrade to the default dots rather than a broken mask.
|
|
37
|
+
*/
|
|
38
|
+
private get mediatypeIconUrl();
|
|
39
|
+
/** Whether the middle of the ring is occupied by a glyph rather than dots */
|
|
40
|
+
private get hasCenterIcon();
|
|
41
|
+
/**
|
|
42
|
+
* A circular loading indicator to render when processing.
|
|
43
|
+
*
|
|
44
|
+
* Two ring geometries are kept deliberately: the original 120x120 ring is
|
|
45
|
+
* tuned for the dots, and the roomier 100x100 ring leaves space for a center
|
|
46
|
+
* glyph. Keeping both means existing consumers see no visual change.
|
|
47
|
+
*/
|
|
16
48
|
private get loadingIndicatorTemplate();
|
|
49
|
+
/** The original ring, with the three animated dots in the middle */
|
|
50
|
+
private get dottedRingSvg();
|
|
51
|
+
/** The roomier ring, used when a glyph occupies the middle */
|
|
52
|
+
private get iconRingSvg();
|
|
53
|
+
/**
|
|
54
|
+
* The bundled mediatype glyph. `maskedIcon` supplies the mask geometry and
|
|
55
|
+
* the decorative `aria-hidden`; this component supplies the size and paint.
|
|
56
|
+
*/
|
|
57
|
+
private get mediatypeIconTemplate();
|
|
58
|
+
private handleIconSlotChange;
|
|
17
59
|
/** A check mark to render on success */
|
|
18
60
|
private get successIndicatorTemplate();
|
|
19
61
|
/** An "!" to render on error */
|
|
20
62
|
private get errorIndicatorTemplate();
|
|
21
|
-
private get shouldShowLoadingDots();
|
|
22
63
|
static get styles(): CSSResultGroup;
|
|
23
64
|
}
|
|
65
|
+
declare global {
|
|
66
|
+
interface HTMLElementTagNameMap {
|
|
67
|
+
'ia-status-indicator': IAStatusIndicator;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
24
70
|
//# sourceMappingURL=ia-status-indicator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ia-status-indicator.d.ts","sourceRoot":"","sources":["../../../../src/elements/ia-status-indicator/ia-status-indicator.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ia-status-indicator.d.ts","sourceRoot":"","sources":["../../../../src/elements/ia-status-indicator/ia-status-indicator.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,EAEd,UAAU,EAIV,cAAc,EACf,MAAM,KAAK,CAAC;AAmBb,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEtE;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACrB,OAAO,GACP,YAAY,GACZ,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,OAAO,GACP,IAAI,GACJ,OAAO,GACP,KAAK,CAAC;AAeV;;;;;;;;GAQG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IAEnB,YAAY,SAAqB;IAGjC,YAAY,SAAkB;IAG9B,UAAU,SAAgB;IAG1B,IAAI,EAAE,aAAa,CAAa;IAGhC,SAAS,CAAC,EAAE,aAAa,CAAC;IAGzB,QAAQ,UAAS;IAGrC,OAAO,CAAC,cAAc,CAAS;IAExC,MAAM,IAAI,cAAc;IASxB;;;OAGG;IACH,OAAO,KAAK,mBAAmB,GAE9B;IAED;;;;;OAKG;IACH,OAAO,KAAK,gBAAgB,GAG3B;IAED,6EAA6E;IAC7E,OAAO,KAAK,aAAa,GAExB;IAED;;;;;;OAMG;IACH,OAAO,KAAK,wBAAwB,GAYnC;IAED,oEAAoE;IACpE,OAAO,KAAK,aAAa,GA4BxB;IAED,8DAA8D;IAC9D,OAAO,KAAK,WAAW,GAoBtB;IAED;;;OAGG;IACH,OAAO,KAAK,qBAAqB,GAKhC;IAED,OAAO,CAAC,oBAAoB;IAK5B,wCAAwC;IACxC,OAAO,KAAK,wBAAwB,GAyBnC;IAED,gCAAgC;IAChC,OAAO,KAAK,sBAAsB,GAkBjC;IAED,MAAM,KAAK,MAAM,IAAI,cAAc,CA4HlC;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,qBAAqB,EAAE,iBAAiB,CAAC;KAC1C;CACF"}
|