@motion-proto/live-tokens 0.90.1 → 0.91.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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.91.0 — Overlay for ImageLightbox
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **ImageLightbox takes an `overlay`.** The snippet pins content such as a
|
|
8
|
+
CornerBadge to the image's frame. It renders on the thumbnail and travels
|
|
9
|
+
with the image when the lightbox opens, growing in proportion to the frame,
|
|
10
|
+
then shrinks back on close. It sits outside the thumbnail's clip and the
|
|
11
|
+
open view's zoom, so it can reach past the frame's edges and stays put while
|
|
12
|
+
the reader zooms and pans.
|
|
13
|
+
|
|
3
14
|
## 0.90.1 — Sketch mode spares the editor overlay
|
|
4
15
|
|
|
5
16
|
### Fixed
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
<script lang="ts">
|
|
20
|
-
import { onMount, tick } from 'svelte';
|
|
20
|
+
import { onMount, tick, type Snippet } from 'svelte';
|
|
21
21
|
import { portal } from '../internal/portal';
|
|
22
22
|
|
|
23
23
|
interface GalleryImage {
|
|
@@ -91,6 +91,11 @@
|
|
|
91
91
|
(from `width`/`height`, or the loaded image); until that's known the image
|
|
92
92
|
fits the viewport, then snaps to the cap once measured. */
|
|
93
93
|
capNatural?: boolean | number;
|
|
94
|
+
/** Content pinned to the image's frame, such as a CornerBadge. It renders
|
|
95
|
+
on the thumbnail and travels with the image into the open view, growing
|
|
96
|
+
in proportion to the frame. Zoom and pan leave it in place, and it may
|
|
97
|
+
extend past the frame's edges. */
|
|
98
|
+
overlay?: Snippet;
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
let {
|
|
@@ -110,6 +115,7 @@
|
|
|
110
115
|
extended = false,
|
|
111
116
|
maxZoom = undefined,
|
|
112
117
|
capNatural = false,
|
|
118
|
+
overlay = undefined,
|
|
113
119
|
}: Props = $props();
|
|
114
120
|
|
|
115
121
|
const items = $derived(
|
|
@@ -158,6 +164,10 @@
|
|
|
158
164
|
let counterEl: HTMLDivElement | undefined = $state();
|
|
159
165
|
let incomingEl: HTMLImageElement | undefined = $state();
|
|
160
166
|
let outgoingEl: HTMLImageElement | undefined = $state();
|
|
167
|
+
let attachedEl: HTMLDivElement | undefined = $state();
|
|
168
|
+
// The thumbnail width the overlay is laid out at; the open view scales it up
|
|
169
|
+
// from there so it grows with the frame instead of reflowing.
|
|
170
|
+
let attachedBase = 1;
|
|
161
171
|
|
|
162
172
|
// `mounted` keeps the portaled modal in the DOM through the close animation;
|
|
163
173
|
// `open` is the visual state the chrome reacts to.
|
|
@@ -267,11 +277,35 @@
|
|
|
267
277
|
prevOverlayVisibility = null;
|
|
268
278
|
}
|
|
269
279
|
|
|
280
|
+
// Width and easing match the stage's own keyframes, so a scale that is linear
|
|
281
|
+
// in width keeps the overlay on the frame for the whole flight.
|
|
282
|
+
function attachedFrame(rect: { width: number; height: number }) {
|
|
283
|
+
const s = rect.width / attachedBase;
|
|
284
|
+
return {
|
|
285
|
+
width: `${attachedBase}px`,
|
|
286
|
+
height: `${rect.height / s}px`,
|
|
287
|
+
transform: `scale(${s})`,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function animateAttached(from: { width: number; height: number } | null, to: { width: number; height: number }) {
|
|
292
|
+
if (!attachedEl) return;
|
|
293
|
+
if (!from) {
|
|
294
|
+
Object.assign(attachedEl.style, attachedFrame(to));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
attachedEl.animate([attachedFrame(from), attachedFrame(to)], {
|
|
298
|
+
duration: dur(),
|
|
299
|
+
easing: TRANSITION_EASE,
|
|
300
|
+
fill: 'forwards',
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
270
304
|
function cancelAnimations() {
|
|
271
305
|
// Commit each animation's current value to inline styles before cancelling,
|
|
272
306
|
// so a mid-flight cancel doesn't visually snap the element back to its
|
|
273
307
|
// pre-animation state.
|
|
274
|
-
for (const el of [stageEl, overlayEl, toolbarEl, closeBtnEl, prevBtnEl, nextBtnEl, counterEl]) {
|
|
308
|
+
for (const el of [stageEl, attachedEl, overlayEl, toolbarEl, closeBtnEl, prevBtnEl, nextBtnEl, counterEl]) {
|
|
275
309
|
if (!el) continue;
|
|
276
310
|
for (const a of el.getAnimations()) {
|
|
277
311
|
try { a.commitStyles(); } catch {}
|
|
@@ -306,6 +340,8 @@
|
|
|
306
340
|
],
|
|
307
341
|
{ duration: dur(), easing: TRANSITION_EASE, fill: 'forwards' },
|
|
308
342
|
);
|
|
343
|
+
attachedBase = start.width || 1;
|
|
344
|
+
animateAttached(start, target);
|
|
309
345
|
|
|
310
346
|
overlayEl.animate([{ opacity: 0 }, { opacity: 1 }], {
|
|
311
347
|
duration: dur(),
|
|
@@ -360,6 +396,8 @@
|
|
|
360
396
|
],
|
|
361
397
|
{ duration: dur(), easing: TRANSITION_EASE, fill: 'forwards' },
|
|
362
398
|
);
|
|
399
|
+
attachedBase = target.width || 1;
|
|
400
|
+
animateAttached(from, target);
|
|
363
401
|
|
|
364
402
|
overlayEl.animate([{ opacity: 1 }, { opacity: 0 }], {
|
|
365
403
|
duration: dur(),
|
|
@@ -395,10 +433,13 @@
|
|
|
395
433
|
const target = viewportTarget();
|
|
396
434
|
if (animate) {
|
|
397
435
|
const from = stageEl.getBoundingClientRect();
|
|
398
|
-
for (const
|
|
399
|
-
|
|
400
|
-
|
|
436
|
+
for (const el of [stageEl, attachedEl]) {
|
|
437
|
+
for (const a of el?.getAnimations() ?? []) {
|
|
438
|
+
try { a.commitStyles(); } catch {}
|
|
439
|
+
a.cancel();
|
|
440
|
+
}
|
|
401
441
|
}
|
|
442
|
+
animateAttached(from, target);
|
|
402
443
|
stageEl.animate(
|
|
403
444
|
[
|
|
404
445
|
{ top: `${from.top}px`, left: `${from.left}px`, width: `${from.width}px`, height: `${from.height}px` },
|
|
@@ -413,6 +454,7 @@
|
|
|
413
454
|
width: `${target.width}px`,
|
|
414
455
|
height: `${target.height}px`,
|
|
415
456
|
});
|
|
457
|
+
animateAttached(null, target);
|
|
416
458
|
}
|
|
417
459
|
if (scale > maxScale) scale = maxScale; // a larger viewport lowers the natural-size cap
|
|
418
460
|
offset = clampOffset(offset.x, offset.y, scale);
|
|
@@ -647,6 +689,13 @@
|
|
|
647
689
|
>
|
|
648
690
|
<img src={cover?.src} alt={cover?.alt} draggable="false" onload={onCoverLoad} />
|
|
649
691
|
</button>
|
|
692
|
+
{#if overlay}
|
|
693
|
+
<!-- Beside the thumb, not in it: the thumb clips its corners. Hidden while
|
|
694
|
+
open, so the copy in the stage reads as the same one in flight. -->
|
|
695
|
+
<div class="image-lightbox-attached" class:away={mounted}>
|
|
696
|
+
{@render overlay()}
|
|
697
|
+
</div>
|
|
698
|
+
{/if}
|
|
650
699
|
</div>
|
|
651
700
|
|
|
652
701
|
{#if mounted}
|
|
@@ -694,6 +743,11 @@
|
|
|
694
743
|
<img bind:this={incomingEl} class="image-lightbox-layer" src={current?.src} alt={current?.alt} draggable="false" onload={onImgLoad} />
|
|
695
744
|
</div>
|
|
696
745
|
</div>
|
|
746
|
+
{#if overlay}
|
|
747
|
+
<div bind:this={attachedEl} class="image-lightbox-attached image-lightbox-attached-stage">
|
|
748
|
+
{@render overlay()}
|
|
749
|
+
</div>
|
|
750
|
+
{/if}
|
|
697
751
|
</div>
|
|
698
752
|
|
|
699
753
|
<button
|
|
@@ -829,6 +883,29 @@
|
|
|
829
883
|
transform: scale(1.02);
|
|
830
884
|
}
|
|
831
885
|
|
|
886
|
+
.image-lightbox-attached {
|
|
887
|
+
position: absolute;
|
|
888
|
+
inset: 0;
|
|
889
|
+
pointer-events: none;
|
|
890
|
+
transition: transform 250ms ease;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
.image-lightbox-thumb:hover + .image-lightbox-attached {
|
|
894
|
+
transform: scale(1.02);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
.image-lightbox-attached.away {
|
|
898
|
+
visibility: hidden;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.image-lightbox-attached-stage {
|
|
902
|
+
inset: auto;
|
|
903
|
+
top: 0;
|
|
904
|
+
left: 0;
|
|
905
|
+
transform-origin: 0 0;
|
|
906
|
+
transition: none;
|
|
907
|
+
}
|
|
908
|
+
|
|
832
909
|
.image-lightbox-thumb:focus-visible {
|
|
833
910
|
outline: 2px solid var(--border-brand-medium);
|
|
834
911
|
outline-offset: 2px;
|