radiant-docs 0.1.25 → 0.1.26
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/package.json
CHANGED
|
@@ -5,9 +5,77 @@ import { renderMarkdown } from "../../lib/utils";
|
|
|
5
5
|
|
|
6
6
|
interface Props extends HTMLAttributes<"img"> {
|
|
7
7
|
src: string;
|
|
8
|
+
zoom?: boolean;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
const { title, ...attrs } = Astro.props;
|
|
11
|
+
const { title, zoom = true, ...attrs } = Astro.props as Props;
|
|
12
|
+
const zoomEnabled = zoom !== false;
|
|
13
|
+
const attrsRecord = attrs as Record<string, unknown>;
|
|
14
|
+
const rawStyle = attrsRecord.style;
|
|
15
|
+
const rawWidth = attrsRecord.width;
|
|
16
|
+
const zoomAttrs: Record<string, unknown> = { ...attrsRecord };
|
|
17
|
+
delete zoomAttrs.style;
|
|
18
|
+
delete zoomAttrs.width;
|
|
19
|
+
delete zoomAttrs.height;
|
|
20
|
+
delete zoomAttrs.class;
|
|
21
|
+
delete zoomAttrs.className;
|
|
22
|
+
|
|
23
|
+
function isConstrainedWidthValue(value: unknown): boolean {
|
|
24
|
+
if (typeof value === "number") {
|
|
25
|
+
return Number.isFinite(value) && value > 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof value !== "string") return false;
|
|
29
|
+
const normalized = value.trim().toLowerCase();
|
|
30
|
+
if (normalized.length === 0) return false;
|
|
31
|
+
|
|
32
|
+
if (
|
|
33
|
+
normalized === "auto" ||
|
|
34
|
+
normalized === "none" ||
|
|
35
|
+
normalized === "inherit" ||
|
|
36
|
+
normalized === "initial" ||
|
|
37
|
+
normalized === "unset" ||
|
|
38
|
+
normalized === "revert" ||
|
|
39
|
+
normalized === "revert-layer"
|
|
40
|
+
) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (normalized.endsWith("%")) {
|
|
45
|
+
const percent = Number.parseFloat(normalized);
|
|
46
|
+
if (Number.isFinite(percent) && percent >= 100) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function hasStyleWidthConstraint(styleValue: unknown): boolean {
|
|
55
|
+
if (typeof styleValue === "string") {
|
|
56
|
+
const widthMatch = styleValue.match(/(?:^|;)\s*width\s*:\s*([^;]+)/i);
|
|
57
|
+
const maxWidthMatch = styleValue.match(
|
|
58
|
+
/(?:^|;)\s*max-width\s*:\s*([^;]+)/i,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
isConstrainedWidthValue(widthMatch?.[1]) ||
|
|
63
|
+
isConstrainedWidthValue(maxWidthMatch?.[1])
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!styleValue || typeof styleValue !== "object") return false;
|
|
68
|
+
const styleObject = styleValue as Record<string, unknown>;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
isConstrainedWidthValue(styleObject.width) ||
|
|
72
|
+
isConstrainedWidthValue(styleObject.maxWidth) ||
|
|
73
|
+
isConstrainedWidthValue(styleObject["max-width"])
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const hasCustomImageWidth =
|
|
78
|
+
isConstrainedWidthValue(rawWidth) || hasStyleWidthConstraint(rawStyle);
|
|
11
79
|
|
|
12
80
|
const captionHtml =
|
|
13
81
|
typeof title === "string" && title.trim().length > 0
|
|
@@ -21,19 +89,60 @@ validateProps(
|
|
|
21
89
|
src: { required: true, type: "string" },
|
|
22
90
|
alt: { type: "string" },
|
|
23
91
|
title: { type: "string" },
|
|
92
|
+
zoom: { type: "boolean" },
|
|
24
93
|
},
|
|
25
94
|
Astro.url.pathname,
|
|
26
95
|
);
|
|
27
96
|
---
|
|
28
97
|
|
|
29
98
|
<figure
|
|
30
|
-
class=
|
|
99
|
+
class:list={[
|
|
100
|
+
"p-1.5 pb-1 xs:pb-1.5 group border border-neutral-200/80 dark:border-neutral-800 shadow-xs bg-neutral-50 dark:bg-neutral-900 rounded-2xl",
|
|
101
|
+
hasCustomImageWidth ? "w-fit max-w-full" : "w-full",
|
|
102
|
+
]}
|
|
31
103
|
x-data="{
|
|
32
104
|
open: false,
|
|
33
105
|
showZoomed: false,
|
|
106
|
+
zoomMaxWidth: 1400,
|
|
107
|
+
zoomSize: '',
|
|
34
108
|
style: 'visibility: hidden;',
|
|
35
109
|
fullShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
|
36
110
|
noShadow: '0 0 0 rgba(0, 0, 0, 0)',
|
|
111
|
+
computeZoomSize() {
|
|
112
|
+
const img = this.$refs.img;
|
|
113
|
+
const zoomContainer = this.$refs.zoomViewport;
|
|
114
|
+
if (!img || !zoomContainer) {
|
|
115
|
+
this.zoomSize = `width: min(92vw, ${this.zoomMaxWidth}px);`;
|
|
116
|
+
return this.zoomSize;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const renderedRect = img.getBoundingClientRect();
|
|
120
|
+
const naturalWidth =
|
|
121
|
+
Number(img.naturalWidth) || Number(renderedRect.width) || 1;
|
|
122
|
+
const naturalHeight =
|
|
123
|
+
Number(img.naturalHeight) || Number(renderedRect.height) || 1;
|
|
124
|
+
const containerRect = zoomContainer.getBoundingClientRect();
|
|
125
|
+
const containerStyles = window.getComputedStyle(zoomContainer);
|
|
126
|
+
const horizontalPadding =
|
|
127
|
+
Number.parseFloat(containerStyles.paddingLeft || '0') +
|
|
128
|
+
Number.parseFloat(containerStyles.paddingRight || '0');
|
|
129
|
+
const verticalPadding =
|
|
130
|
+
Number.parseFloat(containerStyles.paddingTop || '0') +
|
|
131
|
+
Number.parseFloat(containerStyles.paddingBottom || '0');
|
|
132
|
+
|
|
133
|
+
const availableWidth = Math.max(1, containerRect.width - horizontalPadding);
|
|
134
|
+
const availableHeight = Math.max(1, containerRect.height - verticalPadding);
|
|
135
|
+
const imageAspectRatio = naturalWidth / naturalHeight;
|
|
136
|
+
const maxWidthByHeight = availableHeight * imageAspectRatio;
|
|
137
|
+
|
|
138
|
+
const targetWidth = Math.max(
|
|
139
|
+
1,
|
|
140
|
+
Math.min(availableWidth, this.zoomMaxWidth, maxWidthByHeight),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
this.zoomSize = `width: ${targetWidth}px;`;
|
|
144
|
+
return this.zoomSize;
|
|
145
|
+
},
|
|
37
146
|
async zoom() {
|
|
38
147
|
// 1. Lock scroll and measure
|
|
39
148
|
document.body.style.overflow = 'hidden';
|
|
@@ -44,6 +153,9 @@ validateProps(
|
|
|
44
153
|
this.open = true;
|
|
45
154
|
this.showZoomed = false;
|
|
46
155
|
|
|
156
|
+
await this.$nextTick();
|
|
157
|
+
this.computeZoomSize();
|
|
158
|
+
this.style = `${this.zoomSize} opacity: 0; transition: none;`;
|
|
47
159
|
await this.$nextTick();
|
|
48
160
|
const zoomed = this.$refs.zoomedImg;
|
|
49
161
|
const zRect = zoomed.getBoundingClientRect();
|
|
@@ -54,17 +166,17 @@ validateProps(
|
|
|
54
166
|
const ty = (rect.top + rect.height/2) - (zRect.top + zRect.height/2);
|
|
55
167
|
|
|
56
168
|
// 4. Snap to initial position (still invisible)
|
|
57
|
-
this.style =
|
|
169
|
+
this.style = `${this.zoomSize} transform: translate(${tx}px, ${ty}px) scale(${scale}); box-shadow: ${this.noShadow}; opacity: 0; transition: none;`;
|
|
58
170
|
|
|
59
171
|
// 5. Triple-frame buffer to ensure paint completion
|
|
60
172
|
requestAnimationFrame(() => {
|
|
61
173
|
// Reveal zoomed image exactly over the thumbnail
|
|
62
|
-
this.style =
|
|
174
|
+
this.style = `${this.zoomSize} transform: translate(${tx}px, ${ty}px) scale(${scale}); box-shadow: ${this.noShadow}; opacity: 1; transition: none;`;
|
|
63
175
|
|
|
64
176
|
requestAnimationFrame(() => {
|
|
65
177
|
// Now start the animation and hide the thumbnail simultaneously
|
|
66
178
|
this.showZoomed = true;
|
|
67
|
-
this.style =
|
|
179
|
+
this.style = `${this.zoomSize} transform: translate(0,0) scale(1); box-shadow: ${this.fullShadow}; opacity: 1; transition: transform 450ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 450ms cubic-bezier(0.4, 0, 0.2, 1);`;
|
|
68
180
|
});
|
|
69
181
|
});
|
|
70
182
|
},
|
|
@@ -77,7 +189,7 @@ validateProps(
|
|
|
77
189
|
const tx = (rect.left + rect.width/2) - (zRect.left + zRect.width/2);
|
|
78
190
|
const ty = (rect.top + rect.height/2) - (zRect.top + zRect.height/2);
|
|
79
191
|
|
|
80
|
-
this.style =
|
|
192
|
+
this.style = `${this.zoomSize} transform: translate(${tx}px, ${ty}px) scale(${scale}); box-shadow: ${this.noShadow}; opacity: 1; transition: transform 400ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 400ms cubic-bezier(0.4, 0, 0.2, 1);`;
|
|
81
193
|
this.showZoomed = false;
|
|
82
194
|
|
|
83
195
|
setTimeout(() => {
|
|
@@ -94,45 +206,54 @@ validateProps(
|
|
|
94
206
|
{...attrs}
|
|
95
207
|
x-ref="img"
|
|
96
208
|
title={title}
|
|
97
|
-
class=
|
|
209
|
+
class:list={[
|
|
210
|
+
"h-auto my-0! block transition-opacity",
|
|
211
|
+
zoomEnabled && "cursor-zoom-in",
|
|
212
|
+
hasCustomImageWidth ? "max-w-full" : "w-full",
|
|
213
|
+
]}
|
|
98
214
|
:class="showZoomed ? 'opacity-0 duration-0' : 'opacity-100 duration-300'"
|
|
99
|
-
@click="zoom()"
|
|
215
|
+
@click={zoomEnabled ? "zoom()" : undefined}
|
|
100
216
|
/>
|
|
101
217
|
</div>
|
|
102
218
|
{
|
|
103
219
|
title && (
|
|
104
|
-
<figcaption class="mt-
|
|
220
|
+
<figcaption class="mt-1! xs:mt-1.5! text-center text-xs! xs:text-sm! text-neutral-500 dark:text-neutral-400 font-medium whitespace-pre-line leading-relaxed px-2">
|
|
105
221
|
<span set:html={captionHtml} />
|
|
106
222
|
</figcaption>
|
|
107
223
|
)
|
|
108
224
|
}
|
|
109
225
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
x-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
226
|
+
{
|
|
227
|
+
zoomEnabled && (
|
|
228
|
+
<template x-teleport="body">
|
|
229
|
+
<div
|
|
230
|
+
x-show="open"
|
|
231
|
+
x-ref="zoomViewport"
|
|
232
|
+
class="fixed bottom-0 top-1 inset-x-1 rounded-t-2xl z-20 flex items-center justify-center pt-20 pb-4 px-4 md:pt-28 md:pb-12 md:px-12 overflow-hidden"
|
|
233
|
+
@keydown.escape.window="close()"
|
|
234
|
+
style="display: none;"
|
|
235
|
+
>
|
|
236
|
+
<div
|
|
237
|
+
x-show="showZoomed"
|
|
238
|
+
x-transition:enter="transition ease-out duration-300"
|
|
239
|
+
x-transition:enter-start="opacity-0"
|
|
240
|
+
x-transition:enter-end="opacity-100"
|
|
241
|
+
x-transition:leave="transition ease-in duration-400"
|
|
242
|
+
x-transition:leave-start="opacity-100"
|
|
243
|
+
x-transition:leave-end="opacity-0"
|
|
244
|
+
class="absolute inset-0 bg-white/90 dark:bg-black/90 backdrop-blur-xl cursor-zoom-out"
|
|
245
|
+
@click="close()"
|
|
246
|
+
>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<img
|
|
250
|
+
{...zoomAttrs}
|
|
251
|
+
x-ref="zoomedImg"
|
|
252
|
+
class="relative z-10 max-w-full max-h-full object-contain rounded-2xl shadow-none will-change-transform pointer-events-none"
|
|
253
|
+
:style="style"
|
|
254
|
+
/>
|
|
255
|
+
</div>
|
|
256
|
+
</template>
|
|
257
|
+
)
|
|
258
|
+
}
|
|
138
259
|
</figure>
|