@atom63/resume 0.1.0 → 0.2.1
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 +39 -0
- package/README.md +138 -0
- package/dist/{chunk-FL25EF7U.js → chunk-FI5DMK4W.js} +62 -37
- package/dist/chunk-FI5DMK4W.js.map +1 -0
- package/dist/editor/index.css +0 -173
- package/dist/editor/index.css.map +1 -1
- package/dist/editor/index.js +1 -1
- package/dist/index.d.ts +31 -12
- package/dist/index.js +17 -10
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/src/styles/document.css +29 -2
- package/src/styles/tokens.css +14 -15
- package/src/styles/viewer.css +60 -23
- package/dist/chunk-FL25EF7U.js.map +0 -1
- package/dist/index.css +0 -334
- package/dist/index.css.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -102,21 +102,34 @@ declare const resumeMdxComponents: {
|
|
|
102
102
|
img: (props: React.ImgHTMLAttributes<HTMLImageElement>) => react_jsx_runtime.JSX.Element;
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
type PageSize = 'letter' | 'a4';
|
|
106
|
+
interface PageGeometry {
|
|
107
|
+
widthPx: number;
|
|
108
|
+
heightPx: number;
|
|
109
|
+
padXPx: number;
|
|
110
|
+
padYPx: number;
|
|
111
|
+
/** Screen-only gap between page frames. */
|
|
112
|
+
gapPx: number;
|
|
113
|
+
contentWidthPx: number;
|
|
114
|
+
usableHeightPx: number;
|
|
115
|
+
/** Value for the CSS `@page { size: … }` print rule. */
|
|
116
|
+
cssPageSize: 'letter' | 'A4';
|
|
117
|
+
}
|
|
118
|
+
/** Resolve the print-fixed geometry for a page size (defaults to US Letter). */
|
|
119
|
+
declare function getPageGeometry(size?: PageSize): PageGeometry;
|
|
120
|
+
/** Back-compat default geometry (US Letter). */
|
|
121
|
+
declare const PAGE: PageGeometry;
|
|
122
|
+
declare const BLOCK_GAP_PX = 6;
|
|
123
|
+
|
|
105
124
|
/** PaginatedResume reports its computed page count up to the host (ResumeViewer). */
|
|
106
125
|
declare const PaginationReportContext: react.Context<((pageCount: number) => void) | null>;
|
|
107
126
|
declare function usePaginationReport(): (pageCount: number) => void;
|
|
108
127
|
/** Optional font-family hint — changes trigger re-measure/re-pagination. */
|
|
109
128
|
declare const ResumeFontFamilyContext: react.Context<string | undefined>;
|
|
110
129
|
declare const useResumeFontFamily: () => string | undefined;
|
|
111
|
-
|
|
112
|
-
declare const
|
|
113
|
-
|
|
114
|
-
readonly heightPx: number;
|
|
115
|
-
readonly padXPx: number;
|
|
116
|
-
readonly padYPx: number;
|
|
117
|
-
readonly gapPx: 32;
|
|
118
|
-
};
|
|
119
|
-
declare const BLOCK_GAP_PX = 6;
|
|
130
|
+
/** Page size for the document — a change re-measures/re-paginates. */
|
|
131
|
+
declare const ResumePageSizeContext: react.Context<PageSize>;
|
|
132
|
+
declare const useResumePageSize: () => PageSize;
|
|
120
133
|
|
|
121
134
|
interface Block {
|
|
122
135
|
id: string;
|
|
@@ -155,6 +168,8 @@ interface ResumeViewerProps {
|
|
|
155
168
|
fontFamily?: string;
|
|
156
169
|
/** Whether this viewer owns global print + keyboard shortcuts. Default true. */
|
|
157
170
|
isActive?: boolean;
|
|
171
|
+
/** Paper size for the document + print output. Default 'letter'. */
|
|
172
|
+
pageSize?: PageSize;
|
|
158
173
|
/** Suggested PDF filename (becomes document.title during print). */
|
|
159
174
|
pdfFilename?: string;
|
|
160
175
|
/** Called after a successful copy-to-clipboard (host shows its own toast). */
|
|
@@ -177,7 +192,7 @@ interface ResumeViewerProps {
|
|
|
177
192
|
toolbarEnd?: ReactNode;
|
|
178
193
|
}
|
|
179
194
|
|
|
180
|
-
declare function ResumeViewer({ Content, components, fontFamily, isActive, pdfFilename, onCopy, onError, className, toolbarStart, toolbarEnd, }: ResumeViewerProps): react_jsx_runtime.JSX.Element;
|
|
195
|
+
declare function ResumeViewer({ Content, components, fontFamily, isActive, pageSize, pdfFilename, onCopy, onError, className, toolbarStart, toolbarEnd, }: ResumeViewerProps): react_jsx_runtime.JSX.Element;
|
|
181
196
|
|
|
182
197
|
/** True when the viewport is narrow (<= 768px). SSR-safe (defaults false). */
|
|
183
198
|
declare function useIsMobile(breakpointPx?: number): boolean;
|
|
@@ -194,6 +209,8 @@ interface UseResumeViewportOptions {
|
|
|
194
209
|
isMobile: boolean;
|
|
195
210
|
/** Whether this viewport owns global print + keyboard shortcuts. */
|
|
196
211
|
isActive: boolean;
|
|
212
|
+
/** Paper size driving the page dimensions + fit-to-width. Default 'letter'. */
|
|
213
|
+
pageSize?: PageSize;
|
|
197
214
|
/** Suggested PDF filename (becomes document.title during print). */
|
|
198
215
|
pdfFilename?: string;
|
|
199
216
|
/** Called after a successful copy-to-clipboard. */
|
|
@@ -212,6 +229,8 @@ interface UseResumeViewport {
|
|
|
212
229
|
isFitToWidth: boolean;
|
|
213
230
|
/** Total stacked page height (unscaled px) — for sizing the page box. */
|
|
214
231
|
totalHeight: number;
|
|
232
|
+
/** Resolved geometry for the active page size — for sizing the scaled box. */
|
|
233
|
+
geo: PageGeometry;
|
|
215
234
|
handleZoomIn: () => void;
|
|
216
235
|
handleZoomOut: () => void;
|
|
217
236
|
handleActualSize: () => void;
|
|
@@ -227,6 +246,6 @@ interface UseResumeViewport {
|
|
|
227
246
|
* derived state + action handlers. The host renders the scaled page stage; the
|
|
228
247
|
* pagination engine inside it reports its page count through `setPageCount`.
|
|
229
248
|
*/
|
|
230
|
-
declare function useResumeViewport({ scrollRef, contentRef, isMobile, isActive, pdfFilename, onCopy, onError, }: UseResumeViewportOptions): UseResumeViewport;
|
|
249
|
+
declare function useResumeViewport({ scrollRef, contentRef, isMobile, isActive, pageSize, pdfFilename, onCopy, onError, }: UseResumeViewportOptions): UseResumeViewport;
|
|
231
250
|
|
|
232
|
-
export { BLOCK_GAP_PX, type Block, type ColumnKey, Columns, Entry, Footer, Group, Header, MAX_SCALE, MIN_SCALE, Main, PAGE, type PackInput, type PackResult, PaginatedResume, PaginationReportContext, ResumeFontFamilyContext, ResumeViewer, type ResumeViewerProps, Rule, SCALE_STEP, Section, Sidebar, type UseResumeViewport, type UseResumeViewportOptions, packIntoPages, resumeMdxComponents, usePaginationReport, useResumeFontFamily, useIsMobile as useResumeIsMobile, useResumeViewport };
|
|
251
|
+
export { BLOCK_GAP_PX, type Block, type ColumnKey, Columns, Entry, Footer, Group, Header, MAX_SCALE, MIN_SCALE, Main, PAGE, type PackInput, type PackResult, type PageGeometry, type PageSize, PaginatedResume, PaginationReportContext, ResumeFontFamilyContext, ResumePageSizeContext, ResumeViewer, type ResumeViewerProps, Rule, SCALE_STEP, Section, Sidebar, type UseResumeViewport, type UseResumeViewportOptions, getPageGeometry, packIntoPages, resumeMdxComponents, usePaginationReport, useResumeFontFamily, useIsMobile as useResumeIsMobile, useResumePageSize, useResumeViewport };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { BLOCK_GAP_PX, Columns, Entry, Footer, Group, Header, Main, PAGE, PaginatedResume, PaginationReportContext, ResumeFontFamilyContext, Rule, Section, Sidebar, packIntoPages, resumeMdxComponents, usePaginationReport, useResumeFontFamily } from './chunk-
|
|
1
|
+
import { getPageGeometry, ResumePageSizeContext, ResumeFontFamilyContext, PaginationReportContext, resumeMdxComponents, WRAPPER_PADDING_X } from './chunk-FI5DMK4W.js';
|
|
2
|
+
export { BLOCK_GAP_PX, Columns, Entry, Footer, Group, Header, Main, PAGE, PaginatedResume, PaginationReportContext, ResumeFontFamilyContext, ResumePageSizeContext, Rule, Section, Sidebar, getPageGeometry, packIntoPages, resumeMdxComponents, usePaginationReport, useResumeFontFamily, useResumePageSize } from './chunk-FI5DMK4W.js';
|
|
3
3
|
import { MDXProvider } from '@mdx-js/react';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
5
|
import { Maximize2, Minus, Plus, ClipboardCopy } from 'lucide-react';
|
|
@@ -116,10 +116,12 @@ function useResumeViewport({
|
|
|
116
116
|
contentRef,
|
|
117
117
|
isMobile,
|
|
118
118
|
isActive,
|
|
119
|
+
pageSize = "letter",
|
|
119
120
|
pdfFilename,
|
|
120
121
|
onCopy,
|
|
121
122
|
onError
|
|
122
123
|
}) {
|
|
124
|
+
const geo = getPageGeometry(pageSize);
|
|
123
125
|
const isActiveRef = useRef(isActive);
|
|
124
126
|
isActiveRef.current = isActive;
|
|
125
127
|
const printRequestedRef = useRef(false);
|
|
@@ -151,7 +153,7 @@ function useResumeViewport({
|
|
|
151
153
|
if (!viewport) return;
|
|
152
154
|
const updateFitScale = () => {
|
|
153
155
|
const available = Math.max(0, viewport.clientWidth - WRAPPER_PADDING_X);
|
|
154
|
-
setFitScale(clampResumeZoom(Math.min(1, available /
|
|
156
|
+
setFitScale(clampResumeZoom(Math.min(1, available / geo.widthPx)));
|
|
155
157
|
};
|
|
156
158
|
updateFitScale();
|
|
157
159
|
const observer = new ResizeObserver(updateFitScale);
|
|
@@ -161,7 +163,7 @@ function useResumeViewport({
|
|
|
161
163
|
observer.disconnect();
|
|
162
164
|
window.visualViewport?.removeEventListener("resize", updateFitScale);
|
|
163
165
|
};
|
|
164
|
-
}, [scrollRef]);
|
|
166
|
+
}, [scrollRef, geo.widthPx]);
|
|
165
167
|
const captureViewportCenter = useCallback(() => {
|
|
166
168
|
const el = scrollRef.current;
|
|
167
169
|
if (!el) return;
|
|
@@ -442,13 +444,14 @@ function useResumeViewport({
|
|
|
442
444
|
onError?.(error);
|
|
443
445
|
}
|
|
444
446
|
}, [contentRef, onCopy, onError]);
|
|
445
|
-
const totalHeight = pageCount *
|
|
447
|
+
const totalHeight = pageCount * geo.heightPx + Math.max(0, pageCount - 1) * geo.gapPx;
|
|
446
448
|
return {
|
|
447
449
|
pageCount,
|
|
448
450
|
setPageCount,
|
|
449
451
|
effectiveScale,
|
|
450
452
|
isFitToWidth,
|
|
451
453
|
totalHeight,
|
|
454
|
+
geo,
|
|
452
455
|
handleZoomIn,
|
|
453
456
|
handleZoomOut,
|
|
454
457
|
handleActualSize,
|
|
@@ -464,6 +467,7 @@ function ResumeViewer({
|
|
|
464
467
|
components,
|
|
465
468
|
fontFamily,
|
|
466
469
|
isActive = true,
|
|
470
|
+
pageSize = "letter",
|
|
467
471
|
pdfFilename,
|
|
468
472
|
onCopy,
|
|
469
473
|
onError,
|
|
@@ -480,6 +484,7 @@ function ResumeViewer({
|
|
|
480
484
|
effectiveScale,
|
|
481
485
|
isFitToWidth,
|
|
482
486
|
totalHeight,
|
|
487
|
+
geo,
|
|
483
488
|
handleZoomIn,
|
|
484
489
|
handleZoomOut,
|
|
485
490
|
handleActualSize,
|
|
@@ -493,11 +498,13 @@ function ResumeViewer({
|
|
|
493
498
|
contentRef,
|
|
494
499
|
isMobile,
|
|
495
500
|
isActive,
|
|
501
|
+
pageSize,
|
|
496
502
|
pdfFilename,
|
|
497
503
|
onCopy,
|
|
498
504
|
onError
|
|
499
505
|
});
|
|
500
506
|
return /* @__PURE__ */ jsxs("div", { className: "resume-viewer", children: [
|
|
507
|
+
/* @__PURE__ */ jsx("style", { children: `@page { size: ${geo.cssPageSize}; margin: 0; }` }),
|
|
501
508
|
/* @__PURE__ */ jsxs("div", { className: "resume-viewer-toolbar", children: [
|
|
502
509
|
toolbarStart,
|
|
503
510
|
/* @__PURE__ */ jsxs("span", { className: "resume-viewer-pagecount", children: [
|
|
@@ -571,7 +578,7 @@ function ResumeViewer({
|
|
|
571
578
|
/* @__PURE__ */ jsx(
|
|
572
579
|
"button",
|
|
573
580
|
{
|
|
574
|
-
className: "resume-viewer-btn resume-viewer-btn-
|
|
581
|
+
className: "resume-viewer-btn resume-viewer-btn-primary",
|
|
575
582
|
onClick: requestPrint,
|
|
576
583
|
type: "button",
|
|
577
584
|
children: "Save to PDF"
|
|
@@ -599,7 +606,7 @@ function ResumeViewer({
|
|
|
599
606
|
{
|
|
600
607
|
className: "resume-viewer-page-box",
|
|
601
608
|
style: {
|
|
602
|
-
width:
|
|
609
|
+
width: geo.widthPx * effectiveScale,
|
|
603
610
|
height: totalHeight * effectiveScale
|
|
604
611
|
},
|
|
605
612
|
children: /* @__PURE__ */ jsx(
|
|
@@ -608,11 +615,11 @@ function ResumeViewer({
|
|
|
608
615
|
className: "resume-viewer-scale-root",
|
|
609
616
|
"data-resume-scale-root": "",
|
|
610
617
|
style: {
|
|
611
|
-
width:
|
|
618
|
+
width: geo.widthPx,
|
|
612
619
|
height: totalHeight,
|
|
613
620
|
transform: effectiveScale === 1 ? void 0 : `scale(${effectiveScale})`
|
|
614
621
|
},
|
|
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(
|
|
622
|
+
children: /* @__PURE__ */ jsx(ResumePageSizeContext.Provider, { value: pageSize, 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
623
|
"div",
|
|
617
624
|
{
|
|
618
625
|
className: "resume-viewer-content",
|
|
@@ -621,7 +628,7 @@ function ResumeViewer({
|
|
|
621
628
|
style: { zIndex: 1 },
|
|
622
629
|
children: /* @__PURE__ */ jsx(Content, {})
|
|
623
630
|
}
|
|
624
|
-
) }) }) })
|
|
631
|
+
) }) }) }) })
|
|
625
632
|
}
|
|
626
633
|
)
|
|
627
634
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/viewer/use-is-mobile.ts","../src/viewer/print.ts","../src/viewer/viewport.ts","../src/viewer/use-resume-viewport.ts","../src/viewer/resume-viewer.tsx"],"names":["useState","useEffect","useRef"],"mappings":"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","file":"index.js","sourcesContent":["import { useEffect, useState } from 'react'\n\n/** True when the viewport is narrow (<= 768px). SSR-safe (defaults false). */\nexport function useIsMobile(breakpointPx = 768): boolean {\n const [isMobile, setIsMobile] = useState(false)\n useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${breakpointPx}px)`)\n const update = () => setIsMobile(mql.matches)\n update()\n mql.addEventListener('change', update)\n return () => mql.removeEventListener('change', update)\n }, [breakpointPx])\n return isMobile\n}\n","/**\n * Build a print-ready clone of the resume content element. The pagination\n * engine already renders real 8.5x11 `[data-resume-page]` frames, so the clone\n * is a plain wrapper holding those frames (no extra page sizing/padding); the\n * print stylesheet maps each frame to a physical page. The hidden measuring\n * layer is dropped so only the real pages print.\n */\nexport function createPrintClone(source: HTMLElement): HTMLElement {\n const clone = source.cloneNode(true) as HTMLElement\n clone.id = 'resume-print-clone'\n clone.style.cssText = 'background:white;color:black'\n clone.querySelector('[data-resume-measure]')?.remove()\n return clone\n}\n\n/** Hide all non-essential body children for printing; returns the hidden elements. */\nexport function hideBodyChildren(): HTMLElement[] {\n const hidden: HTMLElement[] = []\n const preserveTags = new Set(['STYLE', 'LINK', 'SCRIPT'])\n for (const child of Array.from(document.body.children) as HTMLElement[]) {\n if (!preserveTags.has(child.tagName)) {\n child.style.setProperty('display', 'none', 'important')\n hidden.push(child)\n }\n }\n return hidden\n}\n\nexport interface PrintControllerOptions {\n /** The element holding the rendered resume page frames (contentRef.current). */\n getTarget: () => HTMLElement | null\n /** Suggested PDF filename — becomes document.title during print. */\n getFilename: () => string\n /** Whether THIS viewer should own the print (e.g. it requested print, or it's the active/focused view). */\n shouldOwnPrint: () => boolean\n /** Optional: called after print is torn down (e.g. to force a re-render). */\n onAfterPrint?: () => void\n}\n\n/**\n * Register beforeprint/afterprint handlers implementing the clone-and-hide\n * print strategy. Returns an unsubscribe function that removes the listeners.\n */\nexport function registerPrintController(opts: PrintControllerOptions): () => void {\n let hiddenElements: HTMLElement[] = []\n let savedTitle = ''\n\n const beforePrint = () => {\n // Own the print only when this viewer should (its Save-to-PDF button /\n // shortcut requested it, or it's the active/focused view). Otherwise leave\n // it alone so another app isn't cloned/printed underneath, and so this\n // viewer isn't skipped when focus hasn't flushed yet.\n if (!opts.shouldOwnPrint()) return\n const target = opts.getTarget()\n if (!target) return\n savedTitle = document.title\n document.title = opts.getFilename()\n const clone = createPrintClone(target)\n hiddenElements = hideBodyChildren()\n document.body.appendChild(clone)\n }\n\n const afterPrint = () => {\n const clone = document.getElementById('resume-print-clone')\n if (clone) clone.remove()\n document.title = savedTitle\n for (const el of hiddenElements) {\n el.style.removeProperty('display')\n }\n hiddenElements = []\n opts.onAfterPrint?.()\n }\n\n window.addEventListener('beforeprint', beforePrint)\n window.addEventListener('afterprint', afterPrint)\n return () => {\n window.removeEventListener('beforeprint', beforePrint)\n window.removeEventListener('afterprint', afterPrint)\n }\n}\n","export const INTERACTIVE_PAN_SELECTOR = 'a, button, [role=\"button\"], input, textarea, select'\n\nexport const PAN_THRESHOLD_PX = 6\n\nexport function clampZoom(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(value, max))\n}\n\nexport function isInteractivePanTarget(target: EventTarget | null): boolean {\n return target instanceof HTMLElement && Boolean(target.closest(INTERACTIVE_PAN_SELECTOR))\n}\n\nexport function getTouchDistance(touches: TouchList): number {\n if (touches.length < 2) {\n return 0\n }\n\n const first = touches[0]\n const second = touches[1]\n if (!first || !second) {\n return 0\n }\n\n const deltaX = first.clientX - second.clientX\n const deltaY = first.clientY - second.clientY\n return Math.hypot(deltaX, deltaY)\n}\n\nexport function clampScrollPosition(element: HTMLElement): void {\n const maxScrollLeft = Math.max(0, element.scrollWidth - element.clientWidth)\n const maxScrollTop = Math.max(0, element.scrollHeight - element.clientHeight)\n element.scrollLeft = Math.min(Math.max(0, element.scrollLeft), maxScrollLeft)\n element.scrollTop = Math.min(Math.max(0, element.scrollTop), maxScrollTop)\n}\n\nexport function hasExceededPanThreshold(\n startX: number,\n startY: number,\n currentX: number,\n currentY: number,\n threshold = PAN_THRESHOLD_PX\n): boolean {\n return Math.abs(currentX - startX) > threshold || Math.abs(currentY - startY) > threshold\n}\n","// packages/resume/src/viewer/use-resume-viewport.ts\n//\n// Headless viewport engine for a paginated resume: zoom (buttons / Ctrl+wheel /\n// pinch), pan (mouse drag / touch), fit-to-width, focus-aware print + keyboard\n// shortcuts. The single source of truth for this behaviour — both the built-in\n// `ResumeViewer` and bespoke hosts (e.g. an OS window with its own toolbar\n// chrome) consume this hook so the logic is never duplicated.\n//\n// The host owns the markup and supplies the scroll + content refs; the hook\n// wires every listener to them and returns the derived scale, page count, and\n// the toolbar action handlers.\nimport { type RefObject, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport { PAGE, WRAPPER_PADDING_X } from '../geometry'\nimport { registerPrintController } from './print'\nimport {\n clampScrollPosition,\n clampZoom,\n getTouchDistance,\n hasExceededPanThreshold,\n isInteractivePanTarget,\n} from './viewport'\n\nexport const SCALE_STEP = 0.1\nexport const MIN_SCALE = 0.25\nexport const MAX_SCALE = 2\nconst WHEEL_ZOOM_SENSITIVITY = 0.01\nconst WHEEL_ZOOM_CAP = 0.03\n\nconst clampResumeZoom = (value: number) => clampZoom(value, MIN_SCALE, MAX_SCALE)\n\nfunction isInputFocused(): boolean {\n const tag = document.activeElement?.tagName\n return tag === 'INPUT' || tag === 'TEXTAREA'\n}\n\nexport interface UseResumeViewportOptions {\n /** Scroll viewport element (the host attaches this to its scrollable region). */\n scrollRef: RefObject<HTMLDivElement | null>\n /** Element holding the rendered page frames (the print/copy target). */\n contentRef: RefObject<HTMLDivElement | null>\n /** Narrow-viewport flag — drives touch pan/pinch vs mouse drag. */\n isMobile: boolean\n /** Whether this viewport owns global print + keyboard shortcuts. */\n isActive: boolean\n /** Suggested PDF filename (becomes document.title during print). */\n pdfFilename?: string\n /** Called after a successful copy-to-clipboard. */\n onCopy?: () => void\n /** Called when copy-to-clipboard fails. */\n onError?: (error: unknown) => void\n}\n\nexport interface UseResumeViewport {\n /** Current page count (reported by the pagination engine via setPageCount). */\n pageCount: number\n /** Reporter wired into PaginationReportContext. */\n setPageCount: (n: number) => void\n /** The scale actually applied to the page stage (fit-to-width or manual zoom). */\n effectiveScale: number\n /** Whether fit-to-width mode is active. */\n isFitToWidth: boolean\n /** Total stacked page height (unscaled px) — for sizing the page box. */\n totalHeight: number\n handleZoomIn: () => void\n handleZoomOut: () => void\n handleActualSize: () => void\n handleFitToWidth: () => void\n requestPrint: () => void\n handleCopyText: () => Promise<void>\n /** Lower/upper zoom bounds (for disabling +/- controls). */\n minScale: number\n maxScale: number\n}\n\n/**\n * Wire the resume viewport behaviour to host-supplied refs and return the\n * derived state + action handlers. The host renders the scaled page stage; the\n * pagination engine inside it reports its page count through `setPageCount`.\n */\nexport function useResumeViewport({\n scrollRef,\n contentRef,\n isMobile,\n isActive,\n pdfFilename,\n onCopy,\n onError,\n}: UseResumeViewportOptions): UseResumeViewport {\n // Keep the latest active state readable from global listeners (print/keyboard)\n // without re-registering them, so this viewport only hijacks printing when\n // it's the active view (avoids clashing with other apps' print handlers).\n const isActiveRef = useRef(isActive)\n isActiveRef.current = isActive\n\n // Set synchronously right before window.print() so the global beforeprint\n // listener knows THIS viewport initiated the print (active state can lag a click).\n const printRequestedRef = useRef(false)\n const requestPrint = useCallback(() => {\n printRequestedRef.current = true\n window.print()\n }, [])\n\n const zoomAnchorRef = useRef<{\n pointX: number\n pointY: number\n viewportX: number\n viewportY: number\n prevScale: number\n } | null>(null)\n const [pageCount, setPageCount] = useState(1)\n const [zoom, setZoom] = useState(1)\n const [fitScale, setFitScale] = useState(1)\n const [isFitToWidth, setIsFitToWidth] = useState(true)\n const [, forceRender] = useState(0)\n const dragRef = useRef<{\n startX: number\n startY: number\n scrollX: number\n scrollY: number\n } | null>(null)\n const touchPanRef = useRef<{\n scrollX: number\n scrollY: number\n startX: number\n startY: number\n } | null>(null)\n const pinchRef = useRef<{\n startDistance: number\n startScale: number\n } | null>(null)\n\n const effectiveScale = isFitToWidth ? fitScale : zoom\n\n // Refs for stable closures — avoids tearing down/re-registering listeners on every zoom tick\n const effectiveScaleRef = useRef(effectiveScale)\n const isFitToWidthRef = useRef(isFitToWidth)\n const fitScaleRef = useRef(fitScale)\n effectiveScaleRef.current = effectiveScale\n isFitToWidthRef.current = isFitToWidth\n fitScaleRef.current = fitScale\n\n // Keep zoom synced to fitScale while in fit-to-width mode\n useEffect(() => {\n if (isFitToWidth) setZoom(fitScale)\n }, [isFitToWidth, fitScale])\n\n // Responsive scaling — compute fit-to-width from the scroll viewport.\n useEffect(() => {\n const viewport = scrollRef.current\n if (!viewport) return\n\n const updateFitScale = () => {\n const available = Math.max(0, viewport.clientWidth - WRAPPER_PADDING_X)\n setFitScale(clampResumeZoom(Math.min(1, available / PAGE.widthPx)))\n }\n\n updateFitScale()\n const observer = new ResizeObserver(updateFitScale)\n observer.observe(viewport)\n window.visualViewport?.addEventListener('resize', updateFitScale)\n\n return () => {\n observer.disconnect()\n window.visualViewport?.removeEventListener('resize', updateFitScale)\n }\n }, [scrollRef])\n\n const captureViewportCenter = useCallback(() => {\n const el = scrollRef.current\n if (!el) return\n zoomAnchorRef.current = {\n pointX: el.scrollLeft + el.clientWidth / 2,\n pointY: el.scrollTop + el.clientHeight / 2,\n viewportX: el.clientWidth / 2,\n viewportY: el.clientHeight / 2,\n prevScale: effectiveScaleRef.current,\n }\n }, [scrollRef])\n\n const handleZoomIn = useCallback(() => {\n captureViewportCenter()\n setIsFitToWidth(false)\n setZoom(prev => clampResumeZoom(prev + SCALE_STEP))\n }, [captureViewportCenter])\n\n const handleZoomOut = useCallback(() => {\n captureViewportCenter()\n setIsFitToWidth(false)\n setZoom(prev => clampResumeZoom(prev - SCALE_STEP))\n }, [captureViewportCenter])\n\n const handleActualSize = useCallback(() => {\n captureViewportCenter()\n setIsFitToWidth(false)\n setZoom(1)\n }, [captureViewportCenter])\n\n const handleFitToWidth = useCallback(() => {\n setIsFitToWidth(true)\n }, [])\n\n // Ctrl+wheel zoom — rAF-batched for smooth trackpad pinch\n useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n\n let pendingDelta = 0\n let rafId = 0\n let lastCursorX = 0\n let lastCursorY = 0\n\n const flush = () => {\n rafId = 0\n const delta = pendingDelta\n pendingDelta = 0\n const cursorX = lastCursorX\n const cursorY = lastCursorY\n\n setZoom(prev => {\n const next = clampResumeZoom(prev + delta)\n if (next === prev) return prev\n zoomAnchorRef.current = {\n pointX: el.scrollLeft + cursorX,\n pointY: el.scrollTop + cursorY,\n viewportX: cursorX,\n viewportY: cursorY,\n prevScale: isFitToWidthRef.current ? fitScaleRef.current : prev,\n }\n return next\n })\n setIsFitToWidth(false)\n }\n\n const onWheel = (e: WheelEvent) => {\n if (!e.ctrlKey) return\n e.preventDefault()\n\n pendingDelta = Math.max(\n -WHEEL_ZOOM_CAP,\n Math.min(pendingDelta + -e.deltaY * WHEEL_ZOOM_SENSITIVITY, WHEEL_ZOOM_CAP)\n )\n const rect = el.getBoundingClientRect()\n lastCursorX = e.clientX - rect.left\n lastCursorY = e.clientY - rect.top\n\n if (rafId === 0) {\n rafId = requestAnimationFrame(flush)\n }\n }\n\n el.addEventListener('wheel', onWheel, { passive: false })\n return () => {\n el.removeEventListener('wheel', onWheel)\n if (rafId !== 0) cancelAnimationFrame(rafId)\n }\n }, [scrollRef])\n\n // Scroll anchoring — adjust scroll position after zoom so the focal point stays put\n useLayoutEffect(() => {\n const anchor = zoomAnchorRef.current\n if (!anchor) return\n const el = scrollRef.current\n if (!el) return\n\n const ratio = effectiveScale / anchor.prevScale\n el.scrollLeft = anchor.pointX * ratio - anchor.viewportX\n el.scrollTop = anchor.pointY * ratio - anchor.viewportY\n zoomAnchorRef.current = null\n }, [effectiveScale, scrollRef])\n\n // Print handling — clone-and-hide strategy, gated on this viewport owning the print.\n useEffect(\n () =>\n registerPrintController({\n getTarget: () => contentRef.current,\n getFilename: () => pdfFilename ?? 'resume',\n shouldOwnPrint: () => printRequestedRef.current || isActiveRef.current,\n onAfterPrint: () => {\n printRequestedRef.current = false\n forceRender(n => n + 1)\n },\n }),\n [pdfFilename, contentRef]\n )\n\n // Keyboard shortcuts\n useEffect(() => {\n const keyActions: Record<string, () => void> = {\n '+': handleZoomIn,\n '=': handleZoomIn,\n '-': handleZoomOut,\n _: handleZoomOut,\n }\n const modKeyActions: Record<string, () => void> = {\n '0': handleFitToWidth,\n '1': handleActualSize,\n p: requestPrint,\n }\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (!isActiveRef.current || isInputFocused()) return\n const mod = e.ctrlKey || e.metaKey\n const action = mod ? modKeyActions[e.key] : keyActions[e.key]\n if (action) {\n e.preventDefault()\n action()\n }\n }\n\n globalThis.addEventListener('keydown', handleKeyDown)\n return () => globalThis.removeEventListener('keydown', handleKeyDown)\n }, [handleZoomIn, handleZoomOut, handleFitToWidth, handleActualSize, requestPrint])\n\n // Click-and-drag panning (hand tool). Mobile uses touch pan + pinch-to-zoom.\n useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n\n const setPanActive = (active: boolean) => {\n if (isMobile) {\n el.dataset.panActive = active ? '' : undefined\n }\n }\n\n const moveDrag = (clientX: number, clientY: number) => {\n const drag = dragRef.current\n if (!drag) return\n el.scrollLeft = drag.scrollX - (clientX - drag.startX)\n el.scrollTop = drag.scrollY - (clientY - drag.startY)\n clampScrollPosition(el)\n }\n\n const beginDrag = (clientX: number, clientY: number) => {\n dragRef.current = {\n startX: clientX,\n startY: clientY,\n scrollX: el.scrollLeft,\n scrollY: el.scrollTop,\n }\n setPanActive(true)\n if (!isMobile) {\n el.classList.replace('resume-viewer-scroll-grab', 'resume-viewer-scroll-grabbing')\n el.classList.replace('cursor-grab', 'cursor-grabbing')\n }\n }\n\n const endDrag = () => {\n touchPanRef.current = null\n pinchRef.current = null\n if (!dragRef.current) return\n dragRef.current = null\n setPanActive(false)\n if (!isMobile) {\n el.classList.replace('resume-viewer-scroll-grabbing', 'resume-viewer-scroll-grab')\n el.classList.replace('cursor-grabbing', 'cursor-grab')\n }\n }\n\n const captureZoomAnchor = (clientX: number, clientY: number) => {\n const rect = el.getBoundingClientRect()\n zoomAnchorRef.current = {\n pointX: el.scrollLeft + clientX - rect.left,\n pointY: el.scrollTop + clientY - rect.top,\n viewportX: clientX - rect.left,\n viewportY: clientY - rect.top,\n prevScale: effectiveScaleRef.current,\n }\n }\n\n const onMouseDown = (e: MouseEvent) => {\n if (isMobile || e.button !== 0) return\n if (isInteractivePanTarget(e.target)) return\n\n beginDrag(e.clientX, e.clientY)\n e.preventDefault()\n }\n\n const onMouseMove = (e: MouseEvent) => {\n if (isMobile) return\n moveDrag(e.clientX, e.clientY)\n }\n\n const onTouchStart = (e: TouchEvent) => {\n if (!isMobile) return\n\n if (e.touches.length === 2) {\n touchPanRef.current = null\n dragRef.current = null\n const distance = getTouchDistance(e.touches)\n if (distance <= 0) return\n pinchRef.current = {\n startDistance: distance,\n startScale: effectiveScaleRef.current,\n }\n setIsFitToWidth(false)\n return\n }\n\n if (e.touches.length !== 1 || pinchRef.current) return\n if (isInteractivePanTarget(e.target)) return\n\n const touch = e.touches[0]\n if (!touch) return\n touchPanRef.current = {\n startX: touch.clientX,\n startY: touch.clientY,\n scrollX: el.scrollLeft,\n scrollY: el.scrollTop,\n }\n }\n\n const onTouchMove = (e: TouchEvent) => {\n if (!isMobile) return\n\n if (pinchRef.current && e.touches.length === 2) {\n const distance = getTouchDistance(e.touches)\n if (distance <= 0 || pinchRef.current.startDistance <= 0) return\n\n const first = e.touches[0]\n const second = e.touches[1]\n if (!first || !second) return\n\n const centerX = (first.clientX + second.clientX) / 2\n const centerY = (first.clientY + second.clientY) / 2\n const nextScale = clampResumeZoom(\n pinchRef.current.startScale * (distance / pinchRef.current.startDistance)\n )\n\n captureZoomAnchor(centerX, centerY)\n setZoom(nextScale)\n setIsFitToWidth(false)\n e.preventDefault()\n return\n }\n\n const pendingPan = touchPanRef.current\n const touch = e.touches[0]\n if (!pendingPan || !touch) return\n\n if (!dragRef.current) {\n if (\n !hasExceededPanThreshold(\n pendingPan.startX,\n pendingPan.startY,\n touch.clientX,\n touch.clientY\n )\n ) {\n return\n }\n\n dragRef.current = {\n startX: pendingPan.startX,\n startY: pendingPan.startY,\n scrollX: pendingPan.scrollX,\n scrollY: pendingPan.scrollY,\n }\n setPanActive(true)\n }\n\n moveDrag(touch.clientX, touch.clientY)\n e.preventDefault()\n }\n\n el.addEventListener('mousedown', onMouseDown)\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('mouseup', endDrag)\n\n if (isMobile) {\n el.addEventListener('touchstart', onTouchStart, { passive: true })\n el.addEventListener('touchmove', onTouchMove, { passive: false })\n el.addEventListener('touchend', endDrag)\n el.addEventListener('touchcancel', endDrag)\n }\n\n return () => {\n el.removeEventListener('mousedown', onMouseDown)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('mouseup', endDrag)\n delete el.dataset.panActive\n if (isMobile) {\n el.removeEventListener('touchstart', onTouchStart)\n el.removeEventListener('touchmove', onTouchMove)\n el.removeEventListener('touchend', endDrag)\n el.removeEventListener('touchcancel', endDrag)\n }\n }\n }, [isMobile, scrollRef])\n\n const handleCopyText = useCallback(async () => {\n const content = contentRef.current\n if (!content) return\n try {\n await navigator.clipboard.writeText(content.innerText)\n onCopy?.()\n } catch (error) {\n onError?.(error)\n }\n }, [contentRef, onCopy, onError])\n\n const totalHeight = pageCount * PAGE.heightPx + Math.max(0, pageCount - 1) * PAGE.gapPx\n\n return {\n pageCount,\n setPageCount,\n effectiveScale,\n isFitToWidth,\n totalHeight,\n handleZoomIn,\n handleZoomOut,\n handleActualSize,\n handleFitToWidth,\n requestPrint,\n handleCopyText,\n minScale: MIN_SCALE,\n maxScale: MAX_SCALE,\n }\n}\n","import { MDXProvider } from '@mdx-js/react'\nimport clsx from 'clsx'\nimport { ClipboardCopy, Maximize2, Minus, Plus } from 'lucide-react'\nimport { useRef } from 'react'\nimport { resumeMdxComponents } from '../document/components'\nimport { PaginationReportContext, ResumeFontFamilyContext } from '../document/pagination-context'\nimport { PAGE } from '../geometry'\nimport '../styles/tokens.css'\nimport '../styles/document.css'\nimport '../styles/viewer.css'\nimport type { ResumeViewerProps } from './resume-viewer.types'\nimport { useIsMobile } from './use-is-mobile'\nimport { useResumeViewport } from './use-resume-viewport'\n\nexport function ResumeViewer({\n Content,\n components,\n fontFamily,\n isActive = true,\n pdfFilename,\n onCopy,\n onError,\n className,\n toolbarStart,\n toolbarEnd,\n}: ResumeViewerProps) {\n const isMobile = useIsMobile()\n const scrollRef = useRef<HTMLDivElement>(null)\n const contentRef = useRef<HTMLDivElement>(null)\n\n const {\n pageCount,\n setPageCount,\n effectiveScale,\n isFitToWidth,\n totalHeight,\n handleZoomIn,\n handleZoomOut,\n handleActualSize,\n handleFitToWidth,\n requestPrint,\n handleCopyText,\n minScale,\n maxScale,\n } = useResumeViewport({\n scrollRef,\n contentRef,\n isMobile,\n isActive,\n pdfFilename,\n onCopy,\n onError,\n })\n\n return (\n <div className=\"resume-viewer\">\n <div className=\"resume-viewer-toolbar\">\n {toolbarStart}\n <span className=\"resume-viewer-pagecount\">\n {pageCount} page{pageCount > 1 ? 's' : ''}\n </span>\n <div className=\"resume-viewer-controls\">\n <button\n aria-label=\"Fit resume to width\"\n aria-pressed={isFitToWidth}\n className={clsx('resume-viewer-btn', isFitToWidth && 'resume-viewer-btn-active')}\n onClick={handleFitToWidth}\n title=\"Fit to width\"\n type=\"button\"\n >\n <Maximize2 className=\"resume-viewer-icon\" />\n </button>\n <div className=\"resume-viewer-btn-group\">\n <button\n aria-label=\"Zoom out\"\n className=\"resume-viewer-btn\"\n disabled={!isFitToWidth && effectiveScale <= minScale}\n onClick={handleZoomOut}\n title=\"Zoom out\"\n type=\"button\"\n >\n <Minus className=\"resume-viewer-icon\" />\n </button>\n <button\n className=\"resume-viewer-zoom-label\"\n onClick={handleActualSize}\n title=\"Actual size (100%)\"\n type=\"button\"\n >\n {Math.round(effectiveScale * 100)}%\n </button>\n <button\n aria-label=\"Zoom in\"\n className=\"resume-viewer-btn\"\n disabled={effectiveScale >= maxScale}\n onClick={handleZoomIn}\n title=\"Zoom in\"\n type=\"button\"\n >\n <Plus className=\"resume-viewer-icon\" />\n </button>\n </div>\n <button\n aria-label=\"Copy resume text\"\n className=\"resume-viewer-btn\"\n onClick={handleCopyText}\n title=\"Copy resume text\"\n type=\"button\"\n >\n <ClipboardCopy className=\"resume-viewer-icon\" />\n </button>\n <button\n className=\"resume-viewer-btn resume-viewer-btn-text\"\n onClick={requestPrint}\n type=\"button\"\n >\n Save to PDF\n </button>\n {toolbarEnd}\n </div>\n </div>\n\n <div\n className={clsx(\n 'resume-viewer-scroll',\n isMobile ? 'resume-viewer-scroll-mobile' : 'resume-viewer-scroll-grab',\n className\n )}\n ref={scrollRef}\n >\n <section\n aria-label={\n isMobile\n ? 'Resume preview. Drag with one finger to pan, pinch to zoom.'\n : 'Resume preview. Drag to pan, Ctrl+scroll to zoom.'\n }\n className=\"resume-viewer-stage\"\n >\n <div\n className=\"resume-viewer-page-box\"\n style={{\n width: PAGE.widthPx * effectiveScale,\n height: totalHeight * effectiveScale,\n }}\n >\n <div\n className=\"resume-viewer-scale-root\"\n data-resume-scale-root=\"\"\n style={{\n width: PAGE.widthPx,\n height: totalHeight,\n transform: effectiveScale === 1 ? undefined : `scale(${effectiveScale})`,\n }}\n >\n <ResumeFontFamilyContext.Provider value={fontFamily}>\n <PaginationReportContext.Provider value={setPageCount}>\n <MDXProvider components={components ?? resumeMdxComponents}>\n <div\n className=\"resume-viewer-content\"\n id=\"resume-print-target\"\n ref={contentRef}\n style={{ zIndex: 1 }}\n >\n <Content />\n </div>\n </MDXProvider>\n </PaginationReportContext.Provider>\n </ResumeFontFamilyContext.Provider>\n </div>\n </div>\n </section>\n </div>\n </div>\n )\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/viewer/use-is-mobile.ts","../src/viewer/print.ts","../src/viewer/viewport.ts","../src/viewer/use-resume-viewport.ts","../src/viewer/resume-viewer.tsx"],"names":["useState","useEffect","useRef"],"mappings":"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","file":"index.js","sourcesContent":["import { useEffect, useState } from 'react'\n\n/** True when the viewport is narrow (<= 768px). SSR-safe (defaults false). */\nexport function useIsMobile(breakpointPx = 768): boolean {\n const [isMobile, setIsMobile] = useState(false)\n useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${breakpointPx}px)`)\n const update = () => setIsMobile(mql.matches)\n update()\n mql.addEventListener('change', update)\n return () => mql.removeEventListener('change', update)\n }, [breakpointPx])\n return isMobile\n}\n","/**\n * Build a print-ready clone of the resume content element. The pagination\n * engine already renders real 8.5x11 `[data-resume-page]` frames, so the clone\n * is a plain wrapper holding those frames (no extra page sizing/padding); the\n * print stylesheet maps each frame to a physical page. The hidden measuring\n * layer is dropped so only the real pages print.\n */\nexport function createPrintClone(source: HTMLElement): HTMLElement {\n const clone = source.cloneNode(true) as HTMLElement\n clone.id = 'resume-print-clone'\n clone.style.cssText = 'background:white;color:black'\n clone.querySelector('[data-resume-measure]')?.remove()\n return clone\n}\n\n/** Hide all non-essential body children for printing; returns the hidden elements. */\nexport function hideBodyChildren(): HTMLElement[] {\n const hidden: HTMLElement[] = []\n const preserveTags = new Set(['STYLE', 'LINK', 'SCRIPT'])\n for (const child of Array.from(document.body.children) as HTMLElement[]) {\n if (!preserveTags.has(child.tagName)) {\n child.style.setProperty('display', 'none', 'important')\n hidden.push(child)\n }\n }\n return hidden\n}\n\nexport interface PrintControllerOptions {\n /** The element holding the rendered resume page frames (contentRef.current). */\n getTarget: () => HTMLElement | null\n /** Suggested PDF filename — becomes document.title during print. */\n getFilename: () => string\n /** Whether THIS viewer should own the print (e.g. it requested print, or it's the active/focused view). */\n shouldOwnPrint: () => boolean\n /** Optional: called after print is torn down (e.g. to force a re-render). */\n onAfterPrint?: () => void\n}\n\n/**\n * Register beforeprint/afterprint handlers implementing the clone-and-hide\n * print strategy. Returns an unsubscribe function that removes the listeners.\n */\nexport function registerPrintController(opts: PrintControllerOptions): () => void {\n let hiddenElements: HTMLElement[] = []\n let savedTitle = ''\n\n const beforePrint = () => {\n // Own the print only when this viewer should (its Save-to-PDF button /\n // shortcut requested it, or it's the active/focused view). Otherwise leave\n // it alone so another app isn't cloned/printed underneath, and so this\n // viewer isn't skipped when focus hasn't flushed yet.\n if (!opts.shouldOwnPrint()) return\n const target = opts.getTarget()\n if (!target) return\n savedTitle = document.title\n document.title = opts.getFilename()\n const clone = createPrintClone(target)\n hiddenElements = hideBodyChildren()\n document.body.appendChild(clone)\n }\n\n const afterPrint = () => {\n const clone = document.getElementById('resume-print-clone')\n if (clone) clone.remove()\n document.title = savedTitle\n for (const el of hiddenElements) {\n el.style.removeProperty('display')\n }\n hiddenElements = []\n opts.onAfterPrint?.()\n }\n\n window.addEventListener('beforeprint', beforePrint)\n window.addEventListener('afterprint', afterPrint)\n return () => {\n window.removeEventListener('beforeprint', beforePrint)\n window.removeEventListener('afterprint', afterPrint)\n }\n}\n","export const INTERACTIVE_PAN_SELECTOR = 'a, button, [role=\"button\"], input, textarea, select'\n\nexport const PAN_THRESHOLD_PX = 6\n\nexport function clampZoom(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(value, max))\n}\n\nexport function isInteractivePanTarget(target: EventTarget | null): boolean {\n return target instanceof HTMLElement && Boolean(target.closest(INTERACTIVE_PAN_SELECTOR))\n}\n\nexport function getTouchDistance(touches: TouchList): number {\n if (touches.length < 2) {\n return 0\n }\n\n const first = touches[0]\n const second = touches[1]\n if (!first || !second) {\n return 0\n }\n\n const deltaX = first.clientX - second.clientX\n const deltaY = first.clientY - second.clientY\n return Math.hypot(deltaX, deltaY)\n}\n\nexport function clampScrollPosition(element: HTMLElement): void {\n const maxScrollLeft = Math.max(0, element.scrollWidth - element.clientWidth)\n const maxScrollTop = Math.max(0, element.scrollHeight - element.clientHeight)\n element.scrollLeft = Math.min(Math.max(0, element.scrollLeft), maxScrollLeft)\n element.scrollTop = Math.min(Math.max(0, element.scrollTop), maxScrollTop)\n}\n\nexport function hasExceededPanThreshold(\n startX: number,\n startY: number,\n currentX: number,\n currentY: number,\n threshold = PAN_THRESHOLD_PX\n): boolean {\n return Math.abs(currentX - startX) > threshold || Math.abs(currentY - startY) > threshold\n}\n","// packages/resume/src/viewer/use-resume-viewport.ts\n//\n// Headless viewport engine for a paginated resume: zoom (buttons / Ctrl+wheel /\n// pinch), pan (mouse drag / touch), fit-to-width, focus-aware print + keyboard\n// shortcuts. The single source of truth for this behaviour — both the built-in\n// `ResumeViewer` and bespoke hosts (e.g. an OS window with its own toolbar\n// chrome) consume this hook so the logic is never duplicated.\n//\n// The host owns the markup and supplies the scroll + content refs; the hook\n// wires every listener to them and returns the derived scale, page count, and\n// the toolbar action handlers.\nimport { type RefObject, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport { getPageGeometry, type PageGeometry, type PageSize, WRAPPER_PADDING_X } from '../geometry'\nimport { registerPrintController } from './print'\nimport {\n clampScrollPosition,\n clampZoom,\n getTouchDistance,\n hasExceededPanThreshold,\n isInteractivePanTarget,\n} from './viewport'\n\nexport const SCALE_STEP = 0.1\nexport const MIN_SCALE = 0.25\nexport const MAX_SCALE = 2\nconst WHEEL_ZOOM_SENSITIVITY = 0.01\nconst WHEEL_ZOOM_CAP = 0.03\n\nconst clampResumeZoom = (value: number) => clampZoom(value, MIN_SCALE, MAX_SCALE)\n\nfunction isInputFocused(): boolean {\n const tag = document.activeElement?.tagName\n return tag === 'INPUT' || tag === 'TEXTAREA'\n}\n\nexport interface UseResumeViewportOptions {\n /** Scroll viewport element (the host attaches this to its scrollable region). */\n scrollRef: RefObject<HTMLDivElement | null>\n /** Element holding the rendered page frames (the print/copy target). */\n contentRef: RefObject<HTMLDivElement | null>\n /** Narrow-viewport flag — drives touch pan/pinch vs mouse drag. */\n isMobile: boolean\n /** Whether this viewport owns global print + keyboard shortcuts. */\n isActive: boolean\n /** Paper size driving the page dimensions + fit-to-width. Default 'letter'. */\n pageSize?: PageSize\n /** Suggested PDF filename (becomes document.title during print). */\n pdfFilename?: string\n /** Called after a successful copy-to-clipboard. */\n onCopy?: () => void\n /** Called when copy-to-clipboard fails. */\n onError?: (error: unknown) => void\n}\n\nexport interface UseResumeViewport {\n /** Current page count (reported by the pagination engine via setPageCount). */\n pageCount: number\n /** Reporter wired into PaginationReportContext. */\n setPageCount: (n: number) => void\n /** The scale actually applied to the page stage (fit-to-width or manual zoom). */\n effectiveScale: number\n /** Whether fit-to-width mode is active. */\n isFitToWidth: boolean\n /** Total stacked page height (unscaled px) — for sizing the page box. */\n totalHeight: number\n /** Resolved geometry for the active page size — for sizing the scaled box. */\n geo: PageGeometry\n handleZoomIn: () => void\n handleZoomOut: () => void\n handleActualSize: () => void\n handleFitToWidth: () => void\n requestPrint: () => void\n handleCopyText: () => Promise<void>\n /** Lower/upper zoom bounds (for disabling +/- controls). */\n minScale: number\n maxScale: number\n}\n\n/**\n * Wire the resume viewport behaviour to host-supplied refs and return the\n * derived state + action handlers. The host renders the scaled page stage; the\n * pagination engine inside it reports its page count through `setPageCount`.\n */\nexport function useResumeViewport({\n scrollRef,\n contentRef,\n isMobile,\n isActive,\n pageSize = 'letter',\n pdfFilename,\n onCopy,\n onError,\n}: UseResumeViewportOptions): UseResumeViewport {\n const geo = getPageGeometry(pageSize)\n\n // Keep the latest active state readable from global listeners (print/keyboard)\n // without re-registering them, so this viewport only hijacks printing when\n // it's the active view (avoids clashing with other apps' print handlers).\n const isActiveRef = useRef(isActive)\n isActiveRef.current = isActive\n\n // Set synchronously right before window.print() so the global beforeprint\n // listener knows THIS viewport initiated the print (active state can lag a click).\n const printRequestedRef = useRef(false)\n const requestPrint = useCallback(() => {\n printRequestedRef.current = true\n window.print()\n }, [])\n\n const zoomAnchorRef = useRef<{\n pointX: number\n pointY: number\n viewportX: number\n viewportY: number\n prevScale: number\n } | null>(null)\n const [pageCount, setPageCount] = useState(1)\n const [zoom, setZoom] = useState(1)\n const [fitScale, setFitScale] = useState(1)\n const [isFitToWidth, setIsFitToWidth] = useState(true)\n const [, forceRender] = useState(0)\n const dragRef = useRef<{\n startX: number\n startY: number\n scrollX: number\n scrollY: number\n } | null>(null)\n const touchPanRef = useRef<{\n scrollX: number\n scrollY: number\n startX: number\n startY: number\n } | null>(null)\n const pinchRef = useRef<{\n startDistance: number\n startScale: number\n } | null>(null)\n\n const effectiveScale = isFitToWidth ? fitScale : zoom\n\n // Refs for stable closures — avoids tearing down/re-registering listeners on every zoom tick\n const effectiveScaleRef = useRef(effectiveScale)\n const isFitToWidthRef = useRef(isFitToWidth)\n const fitScaleRef = useRef(fitScale)\n effectiveScaleRef.current = effectiveScale\n isFitToWidthRef.current = isFitToWidth\n fitScaleRef.current = fitScale\n\n // Keep zoom synced to fitScale while in fit-to-width mode\n useEffect(() => {\n if (isFitToWidth) setZoom(fitScale)\n }, [isFitToWidth, fitScale])\n\n // Responsive scaling — compute fit-to-width from the scroll viewport.\n useEffect(() => {\n const viewport = scrollRef.current\n if (!viewport) return\n\n const updateFitScale = () => {\n const available = Math.max(0, viewport.clientWidth - WRAPPER_PADDING_X)\n setFitScale(clampResumeZoom(Math.min(1, available / geo.widthPx)))\n }\n\n updateFitScale()\n const observer = new ResizeObserver(updateFitScale)\n observer.observe(viewport)\n window.visualViewport?.addEventListener('resize', updateFitScale)\n\n return () => {\n observer.disconnect()\n window.visualViewport?.removeEventListener('resize', updateFitScale)\n }\n }, [scrollRef, geo.widthPx])\n\n const captureViewportCenter = useCallback(() => {\n const el = scrollRef.current\n if (!el) return\n zoomAnchorRef.current = {\n pointX: el.scrollLeft + el.clientWidth / 2,\n pointY: el.scrollTop + el.clientHeight / 2,\n viewportX: el.clientWidth / 2,\n viewportY: el.clientHeight / 2,\n prevScale: effectiveScaleRef.current,\n }\n }, [scrollRef])\n\n const handleZoomIn = useCallback(() => {\n captureViewportCenter()\n setIsFitToWidth(false)\n setZoom(prev => clampResumeZoom(prev + SCALE_STEP))\n }, [captureViewportCenter])\n\n const handleZoomOut = useCallback(() => {\n captureViewportCenter()\n setIsFitToWidth(false)\n setZoom(prev => clampResumeZoom(prev - SCALE_STEP))\n }, [captureViewportCenter])\n\n const handleActualSize = useCallback(() => {\n captureViewportCenter()\n setIsFitToWidth(false)\n setZoom(1)\n }, [captureViewportCenter])\n\n const handleFitToWidth = useCallback(() => {\n setIsFitToWidth(true)\n }, [])\n\n // Ctrl+wheel zoom — rAF-batched for smooth trackpad pinch\n useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n\n let pendingDelta = 0\n let rafId = 0\n let lastCursorX = 0\n let lastCursorY = 0\n\n const flush = () => {\n rafId = 0\n const delta = pendingDelta\n pendingDelta = 0\n const cursorX = lastCursorX\n const cursorY = lastCursorY\n\n setZoom(prev => {\n const next = clampResumeZoom(prev + delta)\n if (next === prev) return prev\n zoomAnchorRef.current = {\n pointX: el.scrollLeft + cursorX,\n pointY: el.scrollTop + cursorY,\n viewportX: cursorX,\n viewportY: cursorY,\n prevScale: isFitToWidthRef.current ? fitScaleRef.current : prev,\n }\n return next\n })\n setIsFitToWidth(false)\n }\n\n const onWheel = (e: WheelEvent) => {\n if (!e.ctrlKey) return\n e.preventDefault()\n\n pendingDelta = Math.max(\n -WHEEL_ZOOM_CAP,\n Math.min(pendingDelta + -e.deltaY * WHEEL_ZOOM_SENSITIVITY, WHEEL_ZOOM_CAP)\n )\n const rect = el.getBoundingClientRect()\n lastCursorX = e.clientX - rect.left\n lastCursorY = e.clientY - rect.top\n\n if (rafId === 0) {\n rafId = requestAnimationFrame(flush)\n }\n }\n\n el.addEventListener('wheel', onWheel, { passive: false })\n return () => {\n el.removeEventListener('wheel', onWheel)\n if (rafId !== 0) cancelAnimationFrame(rafId)\n }\n }, [scrollRef])\n\n // Scroll anchoring — adjust scroll position after zoom so the focal point stays put\n useLayoutEffect(() => {\n const anchor = zoomAnchorRef.current\n if (!anchor) return\n const el = scrollRef.current\n if (!el) return\n\n const ratio = effectiveScale / anchor.prevScale\n el.scrollLeft = anchor.pointX * ratio - anchor.viewportX\n el.scrollTop = anchor.pointY * ratio - anchor.viewportY\n zoomAnchorRef.current = null\n }, [effectiveScale, scrollRef])\n\n // Print handling — clone-and-hide strategy, gated on this viewport owning the print.\n useEffect(\n () =>\n registerPrintController({\n getTarget: () => contentRef.current,\n getFilename: () => pdfFilename ?? 'resume',\n shouldOwnPrint: () => printRequestedRef.current || isActiveRef.current,\n onAfterPrint: () => {\n printRequestedRef.current = false\n forceRender(n => n + 1)\n },\n }),\n [pdfFilename, contentRef]\n )\n\n // Keyboard shortcuts\n useEffect(() => {\n const keyActions: Record<string, () => void> = {\n '+': handleZoomIn,\n '=': handleZoomIn,\n '-': handleZoomOut,\n _: handleZoomOut,\n }\n const modKeyActions: Record<string, () => void> = {\n '0': handleFitToWidth,\n '1': handleActualSize,\n p: requestPrint,\n }\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (!isActiveRef.current || isInputFocused()) return\n const mod = e.ctrlKey || e.metaKey\n const action = mod ? modKeyActions[e.key] : keyActions[e.key]\n if (action) {\n e.preventDefault()\n action()\n }\n }\n\n globalThis.addEventListener('keydown', handleKeyDown)\n return () => globalThis.removeEventListener('keydown', handleKeyDown)\n }, [handleZoomIn, handleZoomOut, handleFitToWidth, handleActualSize, requestPrint])\n\n // Click-and-drag panning (hand tool). Mobile uses touch pan + pinch-to-zoom.\n useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n\n const setPanActive = (active: boolean) => {\n if (isMobile) {\n el.dataset.panActive = active ? '' : undefined\n }\n }\n\n const moveDrag = (clientX: number, clientY: number) => {\n const drag = dragRef.current\n if (!drag) return\n el.scrollLeft = drag.scrollX - (clientX - drag.startX)\n el.scrollTop = drag.scrollY - (clientY - drag.startY)\n clampScrollPosition(el)\n }\n\n const beginDrag = (clientX: number, clientY: number) => {\n dragRef.current = {\n startX: clientX,\n startY: clientY,\n scrollX: el.scrollLeft,\n scrollY: el.scrollTop,\n }\n setPanActive(true)\n if (!isMobile) {\n el.classList.replace('resume-viewer-scroll-grab', 'resume-viewer-scroll-grabbing')\n el.classList.replace('cursor-grab', 'cursor-grabbing')\n }\n }\n\n const endDrag = () => {\n touchPanRef.current = null\n pinchRef.current = null\n if (!dragRef.current) return\n dragRef.current = null\n setPanActive(false)\n if (!isMobile) {\n el.classList.replace('resume-viewer-scroll-grabbing', 'resume-viewer-scroll-grab')\n el.classList.replace('cursor-grabbing', 'cursor-grab')\n }\n }\n\n const captureZoomAnchor = (clientX: number, clientY: number) => {\n const rect = el.getBoundingClientRect()\n zoomAnchorRef.current = {\n pointX: el.scrollLeft + clientX - rect.left,\n pointY: el.scrollTop + clientY - rect.top,\n viewportX: clientX - rect.left,\n viewportY: clientY - rect.top,\n prevScale: effectiveScaleRef.current,\n }\n }\n\n const onMouseDown = (e: MouseEvent) => {\n if (isMobile || e.button !== 0) return\n if (isInteractivePanTarget(e.target)) return\n\n beginDrag(e.clientX, e.clientY)\n e.preventDefault()\n }\n\n const onMouseMove = (e: MouseEvent) => {\n if (isMobile) return\n moveDrag(e.clientX, e.clientY)\n }\n\n const onTouchStart = (e: TouchEvent) => {\n if (!isMobile) return\n\n if (e.touches.length === 2) {\n touchPanRef.current = null\n dragRef.current = null\n const distance = getTouchDistance(e.touches)\n if (distance <= 0) return\n pinchRef.current = {\n startDistance: distance,\n startScale: effectiveScaleRef.current,\n }\n setIsFitToWidth(false)\n return\n }\n\n if (e.touches.length !== 1 || pinchRef.current) return\n if (isInteractivePanTarget(e.target)) return\n\n const touch = e.touches[0]\n if (!touch) return\n touchPanRef.current = {\n startX: touch.clientX,\n startY: touch.clientY,\n scrollX: el.scrollLeft,\n scrollY: el.scrollTop,\n }\n }\n\n const onTouchMove = (e: TouchEvent) => {\n if (!isMobile) return\n\n if (pinchRef.current && e.touches.length === 2) {\n const distance = getTouchDistance(e.touches)\n if (distance <= 0 || pinchRef.current.startDistance <= 0) return\n\n const first = e.touches[0]\n const second = e.touches[1]\n if (!first || !second) return\n\n const centerX = (first.clientX + second.clientX) / 2\n const centerY = (first.clientY + second.clientY) / 2\n const nextScale = clampResumeZoom(\n pinchRef.current.startScale * (distance / pinchRef.current.startDistance)\n )\n\n captureZoomAnchor(centerX, centerY)\n setZoom(nextScale)\n setIsFitToWidth(false)\n e.preventDefault()\n return\n }\n\n const pendingPan = touchPanRef.current\n const touch = e.touches[0]\n if (!pendingPan || !touch) return\n\n if (!dragRef.current) {\n if (\n !hasExceededPanThreshold(\n pendingPan.startX,\n pendingPan.startY,\n touch.clientX,\n touch.clientY\n )\n ) {\n return\n }\n\n dragRef.current = {\n startX: pendingPan.startX,\n startY: pendingPan.startY,\n scrollX: pendingPan.scrollX,\n scrollY: pendingPan.scrollY,\n }\n setPanActive(true)\n }\n\n moveDrag(touch.clientX, touch.clientY)\n e.preventDefault()\n }\n\n el.addEventListener('mousedown', onMouseDown)\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('mouseup', endDrag)\n\n if (isMobile) {\n el.addEventListener('touchstart', onTouchStart, { passive: true })\n el.addEventListener('touchmove', onTouchMove, { passive: false })\n el.addEventListener('touchend', endDrag)\n el.addEventListener('touchcancel', endDrag)\n }\n\n return () => {\n el.removeEventListener('mousedown', onMouseDown)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('mouseup', endDrag)\n delete el.dataset.panActive\n if (isMobile) {\n el.removeEventListener('touchstart', onTouchStart)\n el.removeEventListener('touchmove', onTouchMove)\n el.removeEventListener('touchend', endDrag)\n el.removeEventListener('touchcancel', endDrag)\n }\n }\n }, [isMobile, scrollRef])\n\n const handleCopyText = useCallback(async () => {\n const content = contentRef.current\n if (!content) return\n try {\n await navigator.clipboard.writeText(content.innerText)\n onCopy?.()\n } catch (error) {\n onError?.(error)\n }\n }, [contentRef, onCopy, onError])\n\n const totalHeight = pageCount * geo.heightPx + Math.max(0, pageCount - 1) * geo.gapPx\n\n return {\n pageCount,\n setPageCount,\n effectiveScale,\n isFitToWidth,\n totalHeight,\n geo,\n handleZoomIn,\n handleZoomOut,\n handleActualSize,\n handleFitToWidth,\n requestPrint,\n handleCopyText,\n minScale: MIN_SCALE,\n maxScale: MAX_SCALE,\n }\n}\n","import { MDXProvider } from '@mdx-js/react'\nimport clsx from 'clsx'\nimport { ClipboardCopy, Maximize2, Minus, Plus } from 'lucide-react'\nimport { useRef } from 'react'\nimport { resumeMdxComponents } from '../document/components'\nimport {\n PaginationReportContext,\n ResumeFontFamilyContext,\n ResumePageSizeContext,\n} from '../document/pagination-context'\n// Styles ship via the `@atom63/resume/styles` entry (import it once in your app)\n// — NOT self-imported here, so a consumer's token overrides reliably win the\n// cascade instead of racing a re-injected copy of the package CSS.\nimport type { ResumeViewerProps } from './resume-viewer.types'\nimport { useIsMobile } from './use-is-mobile'\nimport { useResumeViewport } from './use-resume-viewport'\n\nexport function ResumeViewer({\n Content,\n components,\n fontFamily,\n isActive = true,\n pageSize = 'letter',\n pdfFilename,\n onCopy,\n onError,\n className,\n toolbarStart,\n toolbarEnd,\n}: ResumeViewerProps) {\n const isMobile = useIsMobile()\n const scrollRef = useRef<HTMLDivElement>(null)\n const contentRef = useRef<HTMLDivElement>(null)\n\n const {\n pageCount,\n setPageCount,\n effectiveScale,\n isFitToWidth,\n totalHeight,\n geo,\n handleZoomIn,\n handleZoomOut,\n handleActualSize,\n handleFitToWidth,\n requestPrint,\n handleCopyText,\n minScale,\n maxScale,\n } = useResumeViewport({\n scrollRef,\n contentRef,\n isMobile,\n isActive,\n pageSize,\n pdfFilename,\n onCopy,\n onError,\n })\n\n return (\n <div className=\"resume-viewer\">\n {/* Drives the printed paper size; @page can't read a CSS custom property. */}\n <style>{`@page { size: ${geo.cssPageSize}; margin: 0; }`}</style>\n <div className=\"resume-viewer-toolbar\">\n {toolbarStart}\n <span className=\"resume-viewer-pagecount\">\n {pageCount} page{pageCount > 1 ? 's' : ''}\n </span>\n <div className=\"resume-viewer-controls\">\n <button\n aria-label=\"Fit resume to width\"\n aria-pressed={isFitToWidth}\n className={clsx('resume-viewer-btn', isFitToWidth && 'resume-viewer-btn-active')}\n onClick={handleFitToWidth}\n title=\"Fit to width\"\n type=\"button\"\n >\n <Maximize2 className=\"resume-viewer-icon\" />\n </button>\n <div className=\"resume-viewer-btn-group\">\n <button\n aria-label=\"Zoom out\"\n className=\"resume-viewer-btn\"\n disabled={!isFitToWidth && effectiveScale <= minScale}\n onClick={handleZoomOut}\n title=\"Zoom out\"\n type=\"button\"\n >\n <Minus className=\"resume-viewer-icon\" />\n </button>\n <button\n className=\"resume-viewer-zoom-label\"\n onClick={handleActualSize}\n title=\"Actual size (100%)\"\n type=\"button\"\n >\n {Math.round(effectiveScale * 100)}%\n </button>\n <button\n aria-label=\"Zoom in\"\n className=\"resume-viewer-btn\"\n disabled={effectiveScale >= maxScale}\n onClick={handleZoomIn}\n title=\"Zoom in\"\n type=\"button\"\n >\n <Plus className=\"resume-viewer-icon\" />\n </button>\n </div>\n <button\n aria-label=\"Copy resume text\"\n className=\"resume-viewer-btn\"\n onClick={handleCopyText}\n title=\"Copy resume text\"\n type=\"button\"\n >\n <ClipboardCopy className=\"resume-viewer-icon\" />\n </button>\n <button\n className=\"resume-viewer-btn resume-viewer-btn-primary\"\n onClick={requestPrint}\n type=\"button\"\n >\n Save to PDF\n </button>\n {toolbarEnd}\n </div>\n </div>\n\n <div\n className={clsx(\n 'resume-viewer-scroll',\n isMobile ? 'resume-viewer-scroll-mobile' : 'resume-viewer-scroll-grab',\n className\n )}\n ref={scrollRef}\n >\n <section\n aria-label={\n isMobile\n ? 'Resume preview. Drag with one finger to pan, pinch to zoom.'\n : 'Resume preview. Drag to pan, Ctrl+scroll to zoom.'\n }\n className=\"resume-viewer-stage\"\n >\n <div\n className=\"resume-viewer-page-box\"\n style={{\n width: geo.widthPx * effectiveScale,\n height: totalHeight * effectiveScale,\n }}\n >\n <div\n className=\"resume-viewer-scale-root\"\n data-resume-scale-root=\"\"\n style={{\n width: geo.widthPx,\n height: totalHeight,\n transform: effectiveScale === 1 ? undefined : `scale(${effectiveScale})`,\n }}\n >\n <ResumePageSizeContext.Provider value={pageSize}>\n <ResumeFontFamilyContext.Provider value={fontFamily}>\n <PaginationReportContext.Provider value={setPageCount}>\n <MDXProvider components={components ?? resumeMdxComponents}>\n <div\n className=\"resume-viewer-content\"\n id=\"resume-print-target\"\n ref={contentRef}\n style={{ zIndex: 1 }}\n >\n <Content />\n </div>\n </MDXProvider>\n </PaginationReportContext.Provider>\n </ResumeFontFamilyContext.Provider>\n </ResumePageSizeContext.Provider>\n </div>\n </div>\n </section>\n </div>\n </div>\n )\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atom63/resume",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Write resumes and CVs as MDX — a paginated, print-ready document engine + viewer for React, styled with a self-contained paper/document token system.",
|
|
@@ -62,7 +62,8 @@
|
|
|
62
62
|
],
|
|
63
63
|
"files": [
|
|
64
64
|
"dist",
|
|
65
|
-
"src/styles"
|
|
65
|
+
"src/styles",
|
|
66
|
+
"CHANGELOG.md"
|
|
66
67
|
],
|
|
67
68
|
"dependencies": {
|
|
68
69
|
"@mdx-js/mdx": "^3.1.1",
|
|
@@ -95,8 +96,8 @@
|
|
|
95
96
|
"tsup": "^8.3.5",
|
|
96
97
|
"typescript": "~5.8.3",
|
|
97
98
|
"vitest": "^4.1.8",
|
|
98
|
-
"@atom63/
|
|
99
|
-
"@atom63/
|
|
99
|
+
"@atom63/biome-config": "0.1.0",
|
|
100
|
+
"@atom63/tsconfig": "0.1.0"
|
|
100
101
|
},
|
|
101
102
|
"scripts": {
|
|
102
103
|
"build": "tsup",
|
package/src/styles/document.css
CHANGED
|
@@ -6,6 +6,31 @@
|
|
|
6
6
|
* or restyle without forking these classes.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
/*
|
|
10
|
+
* The page frames set width/height to the paper size and add the margins as
|
|
11
|
+
* padding, so they MUST be border-box. Scope a border-box reset to the document
|
|
12
|
+
* subtree — the package can't assume the host ships a global reset (without it a
|
|
13
|
+
* standalone consumer gets oversized, off-center pages and a wider content area).
|
|
14
|
+
*/
|
|
15
|
+
[data-resume-document],
|
|
16
|
+
[data-resume-document] *,
|
|
17
|
+
[data-resume-document] *::before,
|
|
18
|
+
[data-resume-document] *::after {
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
* Zero the user-agent margins on flow elements. The layout spaces blocks with
|
|
24
|
+
* flex `gap`; default heading/paragraph/list margins ADD on top of it and blow
|
|
25
|
+
* out the vertical rhythm (the package can't assume the host ships a reset like
|
|
26
|
+
* Tailwind preflight). `:where()` keeps specificity 0 so the .doc-* classes
|
|
27
|
+
* below — e.g. .doc-section's intentional margin-top — still win.
|
|
28
|
+
*/
|
|
29
|
+
[data-resume-document]
|
|
30
|
+
:where(h1, h2, h3, h4, h5, h6, p, ul, ol, li, figure, blockquote, dl, dd, pre) {
|
|
31
|
+
margin: 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
9
34
|
/* Layout primitives */
|
|
10
35
|
.doc-meta-grid {
|
|
11
36
|
display: grid;
|
|
@@ -164,7 +189,9 @@
|
|
|
164
189
|
flex-direction: column;
|
|
165
190
|
background: #fff;
|
|
166
191
|
color: #000;
|
|
192
|
+
/* Paper on a desk: a tight contact shadow + a soft ambient lift. */
|
|
167
193
|
box-shadow:
|
|
168
|
-
0
|
|
169
|
-
0
|
|
194
|
+
0 0 0 1px rgb(24 24 27 / 0.04),
|
|
195
|
+
0 1px 2px rgb(24 24 27 / 0.04),
|
|
196
|
+
0 12px 32px -12px rgb(24 24 27 / 0.18);
|
|
170
197
|
}
|
package/src/styles/tokens.css
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* @atom63/resume —
|
|
2
|
+
* @atom63/resume — document design tokens (the --doc-* vocabulary).
|
|
3
3
|
*
|
|
4
4
|
* The single source of truth for the resume/CV look. Override any token (e.g.
|
|
5
5
|
* in your own stylesheet, scoped to [data-resume-document]) to restyle the
|
|
6
6
|
* resume and CV together; the React primitives only compose these.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Page SIZE is not a token — it is fixed numeric geometry (see geometry.ts) and
|
|
9
|
+
* is selected with the ResumeViewer `pageSize` prop ('letter' | 'a4').
|
|
10
10
|
*
|
|
11
11
|
* Print-fixed by design: doc sizes are absolute px for PDF fidelity, independent
|
|
12
|
-
* of any host typography-scale setting. The font
|
|
13
|
-
*
|
|
14
|
-
* change.
|
|
12
|
+
* of any host typography-scale setting. The font family defaults to a clean sans
|
|
13
|
+
* stack via --doc-font; set --doc-font: inherit to follow the host font instead
|
|
14
|
+
* (pagination re-measures on font change).
|
|
15
15
|
*
|
|
16
|
-
* NOTE:
|
|
17
|
-
*
|
|
16
|
+
* NOTE: --doc-block-gap MUST stay in sync with BLOCK_GAP_PX in geometry.ts (the
|
|
17
|
+
* JS packer reads the same number to compute page breaks).
|
|
18
18
|
*/
|
|
19
19
|
[data-resume-document] {
|
|
20
|
-
/*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
--paper-pad-y: 0.6in;
|
|
25
|
-
--paper-gap: 32px; /* screen-only gap between page frames */
|
|
20
|
+
/* Font — follows the host app's font by default (so an embedded resume matches
|
|
21
|
+
its surroundings). Set --doc-font to a specific stack to override; the
|
|
22
|
+
create-resume scaffold sets a clean sans stack in its index.css. */
|
|
23
|
+
font-family: var(--doc-font, inherit);
|
|
26
24
|
|
|
27
25
|
/* Document layout */
|
|
28
26
|
--doc-meta-col: 11rem; /* left meta/label column (header name, section label, year|role) */
|
|
@@ -33,7 +31,8 @@
|
|
|
33
31
|
--doc-section-gap: 0.5rem; /* space above a section */
|
|
34
32
|
--doc-group-gap: 0.125rem; /* within a tight group (title + meta line) */
|
|
35
33
|
|
|
36
|
-
/* Type
|
|
34
|
+
/* Type — package default matches OS63 (10px). The scaffold bumps --doc-text to
|
|
35
|
+
11px in its index.css for a more readable starting point. */
|
|
37
36
|
--doc-text: 10px;
|
|
38
37
|
--doc-text-xs: 8px;
|
|
39
38
|
--doc-leading: 1.4;
|