@atom63/resume 0.1.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/index.js ADDED
@@ -0,0 +1,638 @@
1
+ import { PAGE, ResumeFontFamilyContext, PaginationReportContext, resumeMdxComponents, WRAPPER_PADDING_X } from './chunk-FL25EF7U.js';
2
+ export { BLOCK_GAP_PX, Columns, Entry, Footer, Group, Header, Main, PAGE, PaginatedResume, PaginationReportContext, ResumeFontFamilyContext, Rule, Section, Sidebar, packIntoPages, resumeMdxComponents, usePaginationReport, useResumeFontFamily } from './chunk-FL25EF7U.js';
3
+ import { MDXProvider } from '@mdx-js/react';
4
+ import clsx from 'clsx';
5
+ import { Maximize2, Minus, Plus, ClipboardCopy } from 'lucide-react';
6
+ import { useState, useEffect, useRef, useCallback, useLayoutEffect } from 'react';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+
9
+ function useIsMobile(breakpointPx = 768) {
10
+ const [isMobile, setIsMobile] = useState(false);
11
+ useEffect(() => {
12
+ const mql = window.matchMedia(`(max-width: ${breakpointPx}px)`);
13
+ const update = () => setIsMobile(mql.matches);
14
+ update();
15
+ mql.addEventListener("change", update);
16
+ return () => mql.removeEventListener("change", update);
17
+ }, [breakpointPx]);
18
+ return isMobile;
19
+ }
20
+
21
+ // src/viewer/print.ts
22
+ function createPrintClone(source) {
23
+ const clone = source.cloneNode(true);
24
+ clone.id = "resume-print-clone";
25
+ clone.style.cssText = "background:white;color:black";
26
+ clone.querySelector("[data-resume-measure]")?.remove();
27
+ return clone;
28
+ }
29
+ function hideBodyChildren() {
30
+ const hidden = [];
31
+ const preserveTags = /* @__PURE__ */ new Set(["STYLE", "LINK", "SCRIPT"]);
32
+ for (const child of Array.from(document.body.children)) {
33
+ if (!preserveTags.has(child.tagName)) {
34
+ child.style.setProperty("display", "none", "important");
35
+ hidden.push(child);
36
+ }
37
+ }
38
+ return hidden;
39
+ }
40
+ function registerPrintController(opts) {
41
+ let hiddenElements = [];
42
+ let savedTitle = "";
43
+ const beforePrint = () => {
44
+ if (!opts.shouldOwnPrint()) return;
45
+ const target = opts.getTarget();
46
+ if (!target) return;
47
+ savedTitle = document.title;
48
+ document.title = opts.getFilename();
49
+ const clone = createPrintClone(target);
50
+ hiddenElements = hideBodyChildren();
51
+ document.body.appendChild(clone);
52
+ };
53
+ const afterPrint = () => {
54
+ const clone = document.getElementById("resume-print-clone");
55
+ if (clone) clone.remove();
56
+ document.title = savedTitle;
57
+ for (const el of hiddenElements) {
58
+ el.style.removeProperty("display");
59
+ }
60
+ hiddenElements = [];
61
+ opts.onAfterPrint?.();
62
+ };
63
+ window.addEventListener("beforeprint", beforePrint);
64
+ window.addEventListener("afterprint", afterPrint);
65
+ return () => {
66
+ window.removeEventListener("beforeprint", beforePrint);
67
+ window.removeEventListener("afterprint", afterPrint);
68
+ };
69
+ }
70
+
71
+ // src/viewer/viewport.ts
72
+ var INTERACTIVE_PAN_SELECTOR = 'a, button, [role="button"], input, textarea, select';
73
+ var PAN_THRESHOLD_PX = 6;
74
+ function clampZoom(value, min, max) {
75
+ return Math.max(min, Math.min(value, max));
76
+ }
77
+ function isInteractivePanTarget(target) {
78
+ return target instanceof HTMLElement && Boolean(target.closest(INTERACTIVE_PAN_SELECTOR));
79
+ }
80
+ function getTouchDistance(touches) {
81
+ if (touches.length < 2) {
82
+ return 0;
83
+ }
84
+ const first = touches[0];
85
+ const second = touches[1];
86
+ if (!first || !second) {
87
+ return 0;
88
+ }
89
+ const deltaX = first.clientX - second.clientX;
90
+ const deltaY = first.clientY - second.clientY;
91
+ return Math.hypot(deltaX, deltaY);
92
+ }
93
+ function clampScrollPosition(element) {
94
+ const maxScrollLeft = Math.max(0, element.scrollWidth - element.clientWidth);
95
+ const maxScrollTop = Math.max(0, element.scrollHeight - element.clientHeight);
96
+ element.scrollLeft = Math.min(Math.max(0, element.scrollLeft), maxScrollLeft);
97
+ element.scrollTop = Math.min(Math.max(0, element.scrollTop), maxScrollTop);
98
+ }
99
+ function hasExceededPanThreshold(startX, startY, currentX, currentY, threshold = PAN_THRESHOLD_PX) {
100
+ return Math.abs(currentX - startX) > threshold || Math.abs(currentY - startY) > threshold;
101
+ }
102
+
103
+ // src/viewer/use-resume-viewport.ts
104
+ var SCALE_STEP = 0.1;
105
+ var MIN_SCALE = 0.25;
106
+ var MAX_SCALE = 2;
107
+ var WHEEL_ZOOM_SENSITIVITY = 0.01;
108
+ var WHEEL_ZOOM_CAP = 0.03;
109
+ var clampResumeZoom = (value) => clampZoom(value, MIN_SCALE, MAX_SCALE);
110
+ function isInputFocused() {
111
+ const tag = document.activeElement?.tagName;
112
+ return tag === "INPUT" || tag === "TEXTAREA";
113
+ }
114
+ function useResumeViewport({
115
+ scrollRef,
116
+ contentRef,
117
+ isMobile,
118
+ isActive,
119
+ pdfFilename,
120
+ onCopy,
121
+ onError
122
+ }) {
123
+ const isActiveRef = useRef(isActive);
124
+ isActiveRef.current = isActive;
125
+ const printRequestedRef = useRef(false);
126
+ const requestPrint = useCallback(() => {
127
+ printRequestedRef.current = true;
128
+ window.print();
129
+ }, []);
130
+ const zoomAnchorRef = useRef(null);
131
+ const [pageCount, setPageCount] = useState(1);
132
+ const [zoom, setZoom] = useState(1);
133
+ const [fitScale, setFitScale] = useState(1);
134
+ const [isFitToWidth, setIsFitToWidth] = useState(true);
135
+ const [, forceRender] = useState(0);
136
+ const dragRef = useRef(null);
137
+ const touchPanRef = useRef(null);
138
+ const pinchRef = useRef(null);
139
+ const effectiveScale = isFitToWidth ? fitScale : zoom;
140
+ const effectiveScaleRef = useRef(effectiveScale);
141
+ const isFitToWidthRef = useRef(isFitToWidth);
142
+ const fitScaleRef = useRef(fitScale);
143
+ effectiveScaleRef.current = effectiveScale;
144
+ isFitToWidthRef.current = isFitToWidth;
145
+ fitScaleRef.current = fitScale;
146
+ useEffect(() => {
147
+ if (isFitToWidth) setZoom(fitScale);
148
+ }, [isFitToWidth, fitScale]);
149
+ useEffect(() => {
150
+ const viewport = scrollRef.current;
151
+ if (!viewport) return;
152
+ const updateFitScale = () => {
153
+ const available = Math.max(0, viewport.clientWidth - WRAPPER_PADDING_X);
154
+ setFitScale(clampResumeZoom(Math.min(1, available / PAGE.widthPx)));
155
+ };
156
+ updateFitScale();
157
+ const observer = new ResizeObserver(updateFitScale);
158
+ observer.observe(viewport);
159
+ window.visualViewport?.addEventListener("resize", updateFitScale);
160
+ return () => {
161
+ observer.disconnect();
162
+ window.visualViewport?.removeEventListener("resize", updateFitScale);
163
+ };
164
+ }, [scrollRef]);
165
+ const captureViewportCenter = useCallback(() => {
166
+ const el = scrollRef.current;
167
+ if (!el) return;
168
+ zoomAnchorRef.current = {
169
+ pointX: el.scrollLeft + el.clientWidth / 2,
170
+ pointY: el.scrollTop + el.clientHeight / 2,
171
+ viewportX: el.clientWidth / 2,
172
+ viewportY: el.clientHeight / 2,
173
+ prevScale: effectiveScaleRef.current
174
+ };
175
+ }, [scrollRef]);
176
+ const handleZoomIn = useCallback(() => {
177
+ captureViewportCenter();
178
+ setIsFitToWidth(false);
179
+ setZoom((prev) => clampResumeZoom(prev + SCALE_STEP));
180
+ }, [captureViewportCenter]);
181
+ const handleZoomOut = useCallback(() => {
182
+ captureViewportCenter();
183
+ setIsFitToWidth(false);
184
+ setZoom((prev) => clampResumeZoom(prev - SCALE_STEP));
185
+ }, [captureViewportCenter]);
186
+ const handleActualSize = useCallback(() => {
187
+ captureViewportCenter();
188
+ setIsFitToWidth(false);
189
+ setZoom(1);
190
+ }, [captureViewportCenter]);
191
+ const handleFitToWidth = useCallback(() => {
192
+ setIsFitToWidth(true);
193
+ }, []);
194
+ useEffect(() => {
195
+ const el = scrollRef.current;
196
+ if (!el) return;
197
+ let pendingDelta = 0;
198
+ let rafId = 0;
199
+ let lastCursorX = 0;
200
+ let lastCursorY = 0;
201
+ const flush = () => {
202
+ rafId = 0;
203
+ const delta = pendingDelta;
204
+ pendingDelta = 0;
205
+ const cursorX = lastCursorX;
206
+ const cursorY = lastCursorY;
207
+ setZoom((prev) => {
208
+ const next = clampResumeZoom(prev + delta);
209
+ if (next === prev) return prev;
210
+ zoomAnchorRef.current = {
211
+ pointX: el.scrollLeft + cursorX,
212
+ pointY: el.scrollTop + cursorY,
213
+ viewportX: cursorX,
214
+ viewportY: cursorY,
215
+ prevScale: isFitToWidthRef.current ? fitScaleRef.current : prev
216
+ };
217
+ return next;
218
+ });
219
+ setIsFitToWidth(false);
220
+ };
221
+ const onWheel = (e) => {
222
+ if (!e.ctrlKey) return;
223
+ e.preventDefault();
224
+ pendingDelta = Math.max(
225
+ -WHEEL_ZOOM_CAP,
226
+ Math.min(pendingDelta + -e.deltaY * WHEEL_ZOOM_SENSITIVITY, WHEEL_ZOOM_CAP)
227
+ );
228
+ const rect = el.getBoundingClientRect();
229
+ lastCursorX = e.clientX - rect.left;
230
+ lastCursorY = e.clientY - rect.top;
231
+ if (rafId === 0) {
232
+ rafId = requestAnimationFrame(flush);
233
+ }
234
+ };
235
+ el.addEventListener("wheel", onWheel, { passive: false });
236
+ return () => {
237
+ el.removeEventListener("wheel", onWheel);
238
+ if (rafId !== 0) cancelAnimationFrame(rafId);
239
+ };
240
+ }, [scrollRef]);
241
+ useLayoutEffect(() => {
242
+ const anchor = zoomAnchorRef.current;
243
+ if (!anchor) return;
244
+ const el = scrollRef.current;
245
+ if (!el) return;
246
+ const ratio = effectiveScale / anchor.prevScale;
247
+ el.scrollLeft = anchor.pointX * ratio - anchor.viewportX;
248
+ el.scrollTop = anchor.pointY * ratio - anchor.viewportY;
249
+ zoomAnchorRef.current = null;
250
+ }, [effectiveScale, scrollRef]);
251
+ useEffect(
252
+ () => registerPrintController({
253
+ getTarget: () => contentRef.current,
254
+ getFilename: () => pdfFilename ?? "resume",
255
+ shouldOwnPrint: () => printRequestedRef.current || isActiveRef.current,
256
+ onAfterPrint: () => {
257
+ printRequestedRef.current = false;
258
+ forceRender((n) => n + 1);
259
+ }
260
+ }),
261
+ [pdfFilename, contentRef]
262
+ );
263
+ useEffect(() => {
264
+ const keyActions = {
265
+ "+": handleZoomIn,
266
+ "=": handleZoomIn,
267
+ "-": handleZoomOut,
268
+ _: handleZoomOut
269
+ };
270
+ const modKeyActions = {
271
+ "0": handleFitToWidth,
272
+ "1": handleActualSize,
273
+ p: requestPrint
274
+ };
275
+ const handleKeyDown = (e) => {
276
+ if (!isActiveRef.current || isInputFocused()) return;
277
+ const mod = e.ctrlKey || e.metaKey;
278
+ const action = mod ? modKeyActions[e.key] : keyActions[e.key];
279
+ if (action) {
280
+ e.preventDefault();
281
+ action();
282
+ }
283
+ };
284
+ globalThis.addEventListener("keydown", handleKeyDown);
285
+ return () => globalThis.removeEventListener("keydown", handleKeyDown);
286
+ }, [handleZoomIn, handleZoomOut, handleFitToWidth, handleActualSize, requestPrint]);
287
+ useEffect(() => {
288
+ const el = scrollRef.current;
289
+ if (!el) return;
290
+ const setPanActive = (active) => {
291
+ if (isMobile) {
292
+ el.dataset.panActive = active ? "" : void 0;
293
+ }
294
+ };
295
+ const moveDrag = (clientX, clientY) => {
296
+ const drag = dragRef.current;
297
+ if (!drag) return;
298
+ el.scrollLeft = drag.scrollX - (clientX - drag.startX);
299
+ el.scrollTop = drag.scrollY - (clientY - drag.startY);
300
+ clampScrollPosition(el);
301
+ };
302
+ const beginDrag = (clientX, clientY) => {
303
+ dragRef.current = {
304
+ startX: clientX,
305
+ startY: clientY,
306
+ scrollX: el.scrollLeft,
307
+ scrollY: el.scrollTop
308
+ };
309
+ setPanActive(true);
310
+ if (!isMobile) {
311
+ el.classList.replace("resume-viewer-scroll-grab", "resume-viewer-scroll-grabbing");
312
+ el.classList.replace("cursor-grab", "cursor-grabbing");
313
+ }
314
+ };
315
+ const endDrag = () => {
316
+ touchPanRef.current = null;
317
+ pinchRef.current = null;
318
+ if (!dragRef.current) return;
319
+ dragRef.current = null;
320
+ setPanActive(false);
321
+ if (!isMobile) {
322
+ el.classList.replace("resume-viewer-scroll-grabbing", "resume-viewer-scroll-grab");
323
+ el.classList.replace("cursor-grabbing", "cursor-grab");
324
+ }
325
+ };
326
+ const captureZoomAnchor = (clientX, clientY) => {
327
+ const rect = el.getBoundingClientRect();
328
+ zoomAnchorRef.current = {
329
+ pointX: el.scrollLeft + clientX - rect.left,
330
+ pointY: el.scrollTop + clientY - rect.top,
331
+ viewportX: clientX - rect.left,
332
+ viewportY: clientY - rect.top,
333
+ prevScale: effectiveScaleRef.current
334
+ };
335
+ };
336
+ const onMouseDown = (e) => {
337
+ if (isMobile || e.button !== 0) return;
338
+ if (isInteractivePanTarget(e.target)) return;
339
+ beginDrag(e.clientX, e.clientY);
340
+ e.preventDefault();
341
+ };
342
+ const onMouseMove = (e) => {
343
+ if (isMobile) return;
344
+ moveDrag(e.clientX, e.clientY);
345
+ };
346
+ const onTouchStart = (e) => {
347
+ if (!isMobile) return;
348
+ if (e.touches.length === 2) {
349
+ touchPanRef.current = null;
350
+ dragRef.current = null;
351
+ const distance = getTouchDistance(e.touches);
352
+ if (distance <= 0) return;
353
+ pinchRef.current = {
354
+ startDistance: distance,
355
+ startScale: effectiveScaleRef.current
356
+ };
357
+ setIsFitToWidth(false);
358
+ return;
359
+ }
360
+ if (e.touches.length !== 1 || pinchRef.current) return;
361
+ if (isInteractivePanTarget(e.target)) return;
362
+ const touch = e.touches[0];
363
+ if (!touch) return;
364
+ touchPanRef.current = {
365
+ startX: touch.clientX,
366
+ startY: touch.clientY,
367
+ scrollX: el.scrollLeft,
368
+ scrollY: el.scrollTop
369
+ };
370
+ };
371
+ const onTouchMove = (e) => {
372
+ if (!isMobile) return;
373
+ if (pinchRef.current && e.touches.length === 2) {
374
+ const distance = getTouchDistance(e.touches);
375
+ if (distance <= 0 || pinchRef.current.startDistance <= 0) return;
376
+ const first = e.touches[0];
377
+ const second = e.touches[1];
378
+ if (!first || !second) return;
379
+ const centerX = (first.clientX + second.clientX) / 2;
380
+ const centerY = (first.clientY + second.clientY) / 2;
381
+ const nextScale = clampResumeZoom(
382
+ pinchRef.current.startScale * (distance / pinchRef.current.startDistance)
383
+ );
384
+ captureZoomAnchor(centerX, centerY);
385
+ setZoom(nextScale);
386
+ setIsFitToWidth(false);
387
+ e.preventDefault();
388
+ return;
389
+ }
390
+ const pendingPan = touchPanRef.current;
391
+ const touch = e.touches[0];
392
+ if (!pendingPan || !touch) return;
393
+ if (!dragRef.current) {
394
+ if (!hasExceededPanThreshold(
395
+ pendingPan.startX,
396
+ pendingPan.startY,
397
+ touch.clientX,
398
+ touch.clientY
399
+ )) {
400
+ return;
401
+ }
402
+ dragRef.current = {
403
+ startX: pendingPan.startX,
404
+ startY: pendingPan.startY,
405
+ scrollX: pendingPan.scrollX,
406
+ scrollY: pendingPan.scrollY
407
+ };
408
+ setPanActive(true);
409
+ }
410
+ moveDrag(touch.clientX, touch.clientY);
411
+ e.preventDefault();
412
+ };
413
+ el.addEventListener("mousedown", onMouseDown);
414
+ window.addEventListener("mousemove", onMouseMove);
415
+ window.addEventListener("mouseup", endDrag);
416
+ if (isMobile) {
417
+ el.addEventListener("touchstart", onTouchStart, { passive: true });
418
+ el.addEventListener("touchmove", onTouchMove, { passive: false });
419
+ el.addEventListener("touchend", endDrag);
420
+ el.addEventListener("touchcancel", endDrag);
421
+ }
422
+ return () => {
423
+ el.removeEventListener("mousedown", onMouseDown);
424
+ window.removeEventListener("mousemove", onMouseMove);
425
+ window.removeEventListener("mouseup", endDrag);
426
+ delete el.dataset.panActive;
427
+ if (isMobile) {
428
+ el.removeEventListener("touchstart", onTouchStart);
429
+ el.removeEventListener("touchmove", onTouchMove);
430
+ el.removeEventListener("touchend", endDrag);
431
+ el.removeEventListener("touchcancel", endDrag);
432
+ }
433
+ };
434
+ }, [isMobile, scrollRef]);
435
+ const handleCopyText = useCallback(async () => {
436
+ const content = contentRef.current;
437
+ if (!content) return;
438
+ try {
439
+ await navigator.clipboard.writeText(content.innerText);
440
+ onCopy?.();
441
+ } catch (error) {
442
+ onError?.(error);
443
+ }
444
+ }, [contentRef, onCopy, onError]);
445
+ const totalHeight = pageCount * PAGE.heightPx + Math.max(0, pageCount - 1) * PAGE.gapPx;
446
+ return {
447
+ pageCount,
448
+ setPageCount,
449
+ effectiveScale,
450
+ isFitToWidth,
451
+ totalHeight,
452
+ handleZoomIn,
453
+ handleZoomOut,
454
+ handleActualSize,
455
+ handleFitToWidth,
456
+ requestPrint,
457
+ handleCopyText,
458
+ minScale: MIN_SCALE,
459
+ maxScale: MAX_SCALE
460
+ };
461
+ }
462
+ function ResumeViewer({
463
+ Content,
464
+ components,
465
+ fontFamily,
466
+ isActive = true,
467
+ pdfFilename,
468
+ onCopy,
469
+ onError,
470
+ className,
471
+ toolbarStart,
472
+ toolbarEnd
473
+ }) {
474
+ const isMobile = useIsMobile();
475
+ const scrollRef = useRef(null);
476
+ const contentRef = useRef(null);
477
+ const {
478
+ pageCount,
479
+ setPageCount,
480
+ effectiveScale,
481
+ isFitToWidth,
482
+ totalHeight,
483
+ handleZoomIn,
484
+ handleZoomOut,
485
+ handleActualSize,
486
+ handleFitToWidth,
487
+ requestPrint,
488
+ handleCopyText,
489
+ minScale,
490
+ maxScale
491
+ } = useResumeViewport({
492
+ scrollRef,
493
+ contentRef,
494
+ isMobile,
495
+ isActive,
496
+ pdfFilename,
497
+ onCopy,
498
+ onError
499
+ });
500
+ return /* @__PURE__ */ jsxs("div", { className: "resume-viewer", children: [
501
+ /* @__PURE__ */ jsxs("div", { className: "resume-viewer-toolbar", children: [
502
+ toolbarStart,
503
+ /* @__PURE__ */ jsxs("span", { className: "resume-viewer-pagecount", children: [
504
+ pageCount,
505
+ " page",
506
+ pageCount > 1 ? "s" : ""
507
+ ] }),
508
+ /* @__PURE__ */ jsxs("div", { className: "resume-viewer-controls", children: [
509
+ /* @__PURE__ */ jsx(
510
+ "button",
511
+ {
512
+ "aria-label": "Fit resume to width",
513
+ "aria-pressed": isFitToWidth,
514
+ className: clsx("resume-viewer-btn", isFitToWidth && "resume-viewer-btn-active"),
515
+ onClick: handleFitToWidth,
516
+ title: "Fit to width",
517
+ type: "button",
518
+ children: /* @__PURE__ */ jsx(Maximize2, { className: "resume-viewer-icon" })
519
+ }
520
+ ),
521
+ /* @__PURE__ */ jsxs("div", { className: "resume-viewer-btn-group", children: [
522
+ /* @__PURE__ */ jsx(
523
+ "button",
524
+ {
525
+ "aria-label": "Zoom out",
526
+ className: "resume-viewer-btn",
527
+ disabled: !isFitToWidth && effectiveScale <= minScale,
528
+ onClick: handleZoomOut,
529
+ title: "Zoom out",
530
+ type: "button",
531
+ children: /* @__PURE__ */ jsx(Minus, { className: "resume-viewer-icon" })
532
+ }
533
+ ),
534
+ /* @__PURE__ */ jsxs(
535
+ "button",
536
+ {
537
+ className: "resume-viewer-zoom-label",
538
+ onClick: handleActualSize,
539
+ title: "Actual size (100%)",
540
+ type: "button",
541
+ children: [
542
+ Math.round(effectiveScale * 100),
543
+ "%"
544
+ ]
545
+ }
546
+ ),
547
+ /* @__PURE__ */ jsx(
548
+ "button",
549
+ {
550
+ "aria-label": "Zoom in",
551
+ className: "resume-viewer-btn",
552
+ disabled: effectiveScale >= maxScale,
553
+ onClick: handleZoomIn,
554
+ title: "Zoom in",
555
+ type: "button",
556
+ children: /* @__PURE__ */ jsx(Plus, { className: "resume-viewer-icon" })
557
+ }
558
+ )
559
+ ] }),
560
+ /* @__PURE__ */ jsx(
561
+ "button",
562
+ {
563
+ "aria-label": "Copy resume text",
564
+ className: "resume-viewer-btn",
565
+ onClick: handleCopyText,
566
+ title: "Copy resume text",
567
+ type: "button",
568
+ children: /* @__PURE__ */ jsx(ClipboardCopy, { className: "resume-viewer-icon" })
569
+ }
570
+ ),
571
+ /* @__PURE__ */ jsx(
572
+ "button",
573
+ {
574
+ className: "resume-viewer-btn resume-viewer-btn-text",
575
+ onClick: requestPrint,
576
+ type: "button",
577
+ children: "Save to PDF"
578
+ }
579
+ ),
580
+ toolbarEnd
581
+ ] })
582
+ ] }),
583
+ /* @__PURE__ */ jsx(
584
+ "div",
585
+ {
586
+ className: clsx(
587
+ "resume-viewer-scroll",
588
+ isMobile ? "resume-viewer-scroll-mobile" : "resume-viewer-scroll-grab",
589
+ className
590
+ ),
591
+ ref: scrollRef,
592
+ children: /* @__PURE__ */ jsx(
593
+ "section",
594
+ {
595
+ "aria-label": isMobile ? "Resume preview. Drag with one finger to pan, pinch to zoom." : "Resume preview. Drag to pan, Ctrl+scroll to zoom.",
596
+ className: "resume-viewer-stage",
597
+ children: /* @__PURE__ */ jsx(
598
+ "div",
599
+ {
600
+ className: "resume-viewer-page-box",
601
+ style: {
602
+ width: PAGE.widthPx * effectiveScale,
603
+ height: totalHeight * effectiveScale
604
+ },
605
+ children: /* @__PURE__ */ jsx(
606
+ "div",
607
+ {
608
+ className: "resume-viewer-scale-root",
609
+ "data-resume-scale-root": "",
610
+ style: {
611
+ width: PAGE.widthPx,
612
+ height: totalHeight,
613
+ transform: effectiveScale === 1 ? void 0 : `scale(${effectiveScale})`
614
+ },
615
+ children: /* @__PURE__ */ jsx(ResumeFontFamilyContext.Provider, { value: fontFamily, children: /* @__PURE__ */ jsx(PaginationReportContext.Provider, { value: setPageCount, children: /* @__PURE__ */ jsx(MDXProvider, { components: components ?? resumeMdxComponents, children: /* @__PURE__ */ jsx(
616
+ "div",
617
+ {
618
+ className: "resume-viewer-content",
619
+ id: "resume-print-target",
620
+ ref: contentRef,
621
+ style: { zIndex: 1 },
622
+ children: /* @__PURE__ */ jsx(Content, {})
623
+ }
624
+ ) }) }) })
625
+ }
626
+ )
627
+ }
628
+ )
629
+ }
630
+ )
631
+ }
632
+ )
633
+ ] });
634
+ }
635
+
636
+ export { MAX_SCALE, MIN_SCALE, ResumeViewer, SCALE_STEP, useIsMobile as useResumeIsMobile, useResumeViewport };
637
+ //# sourceMappingURL=index.js.map
638
+ //# sourceMappingURL=index.js.map