This commit is contained in:
2026-07-27 00:56:18 +03:30
commit 1c75afe0fd
244 changed files with 27710 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
{
"name": "@ohmycv/vue-smart-pages",
"version": "0.1.1",
"description": "Automatically break your content into multiple pages, just like what Word does.",
"homepage": "https://github.com/Renovamen/oh-my-cv/tree/main/packages/vue-smart-pages",
"bugs": {
"url": "https://github.com/Renovamen/oh-my-cv"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Renovamen/oh-my-cv.git",
"directory": "packages/vue-smart-pages"
},
"license": "MIT",
"author": "Renovamen <renovamenzxh@gmail.com>",
"files": [
"dist"
],
"exports": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"scripts": {
"build-fast:pkg": "tsup src/index.ts --format cjs,esm",
"build:pkg": "pnpm run build-fast:pkg --dts"
},
"dependencies": {
"@vueuse/core": "^10.11.0",
"vue": "^3.4.31"
},
"publishConfig": {
"access": "public"
}
}
+62
View File
@@ -0,0 +1,62 @@
import type { PageSize, PageMargins } from "./types";
const NEW_PAGE_CLASS = "md-it-newpage";
const _elementHeight = (element: Element) => {
const style = window.getComputedStyle(element);
const marginTop = parseInt(style.marginTop) || 0;
const marginBottom = parseInt(style.marginBottom) || 0;
return element.clientHeight + marginTop + marginBottom;
};
const _createPage = (size: PageSize, margins: Required<PageMargins>) => {
const page = document.createElement("div");
page.dataset.scope = "vue-smart-pages";
page.dataset.part = "page";
page.style.height = `${size.height}px`;
setWidthAndMargins(page, size, margins);
return page;
};
export const setWidthAndMargins = (
element: HTMLElement,
size: PageSize,
margins: Required<PageMargins>
) => {
element.style.width = `${size.width}mm`;
element.style.padding = `${margins.top}px ${margins.right}px ${margins.bottom}px ${margins.left}px`;
};
export const breakPage = (
target: HTMLElement,
size: PageSize,
margins: Required<PageMargins>
) => {
const maxHeight = size.height - margins.top - margins.bottom;
const pages = document.createElement("div");
let accHeight = 0;
let page = _createPage(size, margins);
Array.from(target.children).forEach((child) => {
const childHeight = _elementHeight(child);
if (accHeight + childHeight > maxHeight || child.className === NEW_PAGE_CLASS) {
pages.appendChild(page);
accHeight = 0;
page = _createPage(size, margins);
}
page.appendChild(child);
accHeight += childHeight;
});
pages.appendChild(page);
target.innerHTML = pages.innerHTML;
};
+6
View File
@@ -0,0 +1,6 @@
import { useSmartPages } from "./useSmartPages";
export * from "./useSmartPages";
export type * from "./types";
export default useSmartPages;
+11
View File
@@ -0,0 +1,11 @@
export type PageSize = {
width: number;
height: number;
};
export type PageMargins = Partial<{
top: number;
bottom: number;
left: number;
right: number;
}>;
@@ -0,0 +1,89 @@
import { onMounted } from "vue";
import {
unrefElement,
toValue,
watchThrottled,
type WatchThrottledOptions,
type MaybeComputedElementRef
} from "@vueuse/core";
import type { MaybeRef } from "@vueuse/shared";
import { breakPage, setWidthAndMargins } from "./dom";
import type { PageSize, PageMargins } from "./types";
/**
* Break the content into pages based on the given size and margins.
*
* @param target HTML element
* @param html Content that will be rendered as innerHTML of the target element
* @param size Size and margins of each page
* @param options
*
* @returns A render function to manually trigger the page breaking
*
* @example
* ```vue
* <template>
* <div ref="target" />
* </template>
*
* <script setup>
* import { ref } from "vue";
* import { useSmartPages } from "vue-smart-pages";
*
* const target = ref();
*
* const { render } = useSmartPages(target, "<p>Content</p>", { width: 210, height: 297 }, { top: 10, bottom: 10, left: 10, right: 10 });
* </script>
* ```
*/
export const useSmartPages = (
target: MaybeComputedElementRef,
html: MaybeRef<string>,
size: MaybeRef<PageSize>,
margins: MaybeRef<PageMargins> = {},
options: {
beforeRender?: () => void | Promise<void>;
afterRender?: () => void | Promise<void>;
watchThrottledOptions?: WatchThrottledOptions<Readonly<boolean>>;
} = {}
) => {
const render = async () => {
const element = unrefElement(target) as HTMLElement | undefined | null;
if (!element) return;
const { width, height } = toValue(size);
const { top = 0, bottom = 0, left = 0, right = 0 } = toValue(margins);
const _size = { width, height };
const _margins = { top, bottom, left, right };
// To calculate the correct child heights for breaking the page, we first apply the content,
// size, and margins to a clone of the element, instead of the original element.
// This is to avoid flickering when the original element is modified.
const copy = element.cloneNode(true) as HTMLElement;
copy.innerHTML = toValue(html);
setWidthAndMargins(copy, _size, _margins);
// Attach it to the body temporarily to get the correct computed styles
document.body.appendChild(copy);
if (options.beforeRender) await options.beforeRender();
// Break the page based on the given size and margins
breakPage(copy, _size, _margins);
// Replace the original element with the modified copy
element.innerHTML = copy.innerHTML;
// Remove the copy from the body
document.body.removeChild(copy);
if (options.afterRender) await options.afterRender();
};
onMounted(render);
watchThrottled([size, margins, html], render, options.watchThrottledOptions);
return { render };
};
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"include": ["./src"]
}