From f2af499e693bcd2e2c3f134206eaedf124029b92 Mon Sep 17 00:00:00 2001 From: aria Date: Wed, 11 Mar 2026 18:55:16 +1100 Subject: [PATCH] rename package --- README.md | 46 +- examples/basic/index.html | 149 +-- examples/basic/package.json | 4 +- examples/basic/src/main.ts | 10 +- examples/basic/vite.config.ts | 6 +- examples/vanilla/build.js | 146 +-- examples/vanilla/index.html | 151 +-- examples/vanilla/package.json | 6 +- examples/vanilla/src/main.ts | 10 +- package.json | 2 +- packages/core/package.json | 2 +- packages/core/src/index.ts | 14 +- .../core/src/{docview.ts => polyrender.ts} | 46 +- packages/core/src/registry.ts | 2 +- packages/core/src/renderer.ts | 24 +- packages/core/src/renderers/browse-pages.ts | 8 +- packages/core/src/renderers/chunked-pdf.ts | 8 +- packages/core/src/renderers/code.ts | 10 +- packages/core/src/renderers/csv.ts | 8 +- packages/core/src/renderers/docx.ts | 8 +- packages/core/src/renderers/epub.ts | 6 +- packages/core/src/renderers/ods.ts | 8 +- packages/core/src/renderers/odt.ts | 8 +- packages/core/src/renderers/pdf.ts | 12 +- packages/core/src/renderers/text.ts | 10 +- packages/core/src/styles.css | 970 ++++++++++-------- packages/core/src/toolbar.ts | 8 +- packages/core/src/types.ts | 38 +- packages/core/src/utils.ts | 4 +- packages/react/package.json | 6 +- packages/react/src/DocumentViewer.tsx | 14 +- packages/react/src/index.ts | 16 +- packages/react/src/useDocumentRenderer.ts | 32 +- packages/react/tsup.config.ts | 2 +- pnpm-lock.yaml | 9 +- 35 files changed, 967 insertions(+), 836 deletions(-) rename packages/core/src/{docview.ts => polyrender.ts} (87%) diff --git a/README.md b/README.md index cee26ea..1988a7b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# DocView +# PolyRender A framework-agnostic, universal document renderer for the browser. Render PDFs, EPUBs, DOCX files, CSVs, source code, and plain text — with optional support for pre-rendered page images and chunked streaming for large documents. -**Core** (`@docview/core`) is a vanilla TypeScript library with zero framework dependencies. **React** (`@docview/react`) provides a thin wrapper component and hook. Both are designed for drop-in use in any web project. +**Core** (`@polyrender/core`) is a vanilla TypeScript library with zero framework dependencies. **React** (`@polyrender/react`) provides a thin wrapper component and hook. Both are designed for drop-in use in any web project. ## Features @@ -19,10 +19,10 @@ A framework-agnostic, universal document renderer for the browser. Render PDFs, ```bash # Core (vanilla JS) -npm install @docview/core +npm install @polyrender/core # React wrapper -npm install @docview/react +npm install @polyrender/react # Install peer dependencies for the formats you need: npm install pdfjs-dist # PDF @@ -39,10 +39,10 @@ You only need to install peer dependencies for the formats you plan to render. U ### Vanilla JS ```typescript -import { DocView } from '@docview/core' -import '@docview/core/styles.css' +import { PolyRender } from '@polyrender/core' +import '@polyrender/core/styles.css' -const viewer = new DocView(document.getElementById('viewer')!, { +const viewer = new PolyRender(document.getElementById('viewer')!, { source: { type: 'url', url: '/document.pdf' }, theme: 'dark', toolbar: true, @@ -65,8 +65,8 @@ viewer.destroy() ### React ```tsx -import { DocumentViewer } from '@docview/react' -import '@docview/core/styles.css' +import { DocumentViewer } from '@polyrender/react' +import '@polyrender/core/styles.css' function App() { return ( @@ -85,8 +85,8 @@ function App() { ```tsx import { useRef } from 'react' -import { DocumentViewer, type DocumentViewerRef } from '@docview/react' -import '@docview/core/styles.css' +import { DocumentViewer, type DocumentViewerRef } from '@polyrender/react' +import '@polyrender/core/styles.css' function App() { const viewerRef = useRef(null) @@ -109,8 +109,8 @@ function App() { ### React Hook (headless) ```tsx -import { useDocumentRenderer } from '@docview/react' -import '@docview/core/styles.css' +import { useDocumentRenderer } from '@polyrender/react' +import '@polyrender/core/styles.css' function CustomViewer({ url }: { url: string }) { const { containerRef, state, goToPage, setZoom } = useDocumentRenderer({ @@ -135,7 +135,7 @@ function CustomViewer({ url }: { url: string }) { ## Document Sources -DocView accepts four types of document sources: +PolyRender accepts four types of document sources: ### File (binary data) @@ -226,11 +226,11 @@ source = { ## Theming -DocView uses CSS custom properties for all visual styling. Override any `--dv-*` variable to customize: +PolyRender uses CSS custom properties for all visual styling. Override any `--dv-*` variable to customize: ```css /* Custom theme */ -.my-viewer .docview { +.my-viewer .polyrender { --dv-bg: #1e1e2e; --dv-surface: #2a2a3e; --dv-text: #cdd6f4; @@ -320,12 +320,12 @@ See `styles.css` for the complete list. Register a renderer for any format: ```typescript -import { DocView, BaseRenderer, type DocViewOptions, type DocumentFormat } from '@docview/core' +import { PolyRender, BaseRenderer, type PolyRenderOptions, type DocumentFormat } from '@polyrender/core' class MarkdownRenderer extends BaseRenderer { readonly format: DocumentFormat = 'custom-markdown' - protected async onMount(viewport: HTMLElement, options: DocViewOptions) { + protected async onMount(viewport: HTMLElement, options: PolyRenderOptions) { // Your rendering logic here const text = await this.loadText(options.source) const html = myMarkdownLib.render(text) @@ -337,10 +337,10 @@ class MarkdownRenderer extends BaseRenderer { } // Register globally -DocView.registerRenderer('custom-markdown', () => new MarkdownRenderer()) +PolyRender.registerRenderer('custom-markdown', () => new MarkdownRenderer()) // Use it -new DocView(container, { +new PolyRender(container, { source: { type: 'url', url: '/readme.md' }, format: 'custom-markdown', }) @@ -372,10 +372,10 @@ new DocView(container, { ``` packages/ -├── core/ @docview/core — Framework-agnostic TypeScript core +├── core/ @polyrender/core — Framework-agnostic TypeScript core │ ├── src/ │ │ ├── types.ts # All interfaces and types -│ │ ├── docview.ts # Main DocView class +│ │ ├── polyrender.ts # Main PolyRender class │ │ ├── renderer.ts # Abstract base renderer │ │ ├── registry.ts # Format → renderer factory mapping │ │ ├── toolbar.ts # Built-in toolbar DOM builder @@ -391,7 +391,7 @@ packages/ │ │ ├── code.ts # Code (highlight.js) │ │ └── text.ts # Plain text │ └── package.json -└── react/ @docview/react — React wrapper +└── react/ @polyrender/react — React wrapper ├── src/ │ ├── DocumentViewer.tsx # Drop-in component │ ├── useDocumentRenderer.ts # Headless hook diff --git a/examples/basic/index.html b/examples/basic/index.html index cef0472..9853f10 100644 --- a/examples/basic/index.html +++ b/examples/basic/index.html @@ -1,79 +1,88 @@ - + - - - - DocView — Basic Example - - - -

DocView — Basic Example

-

Drop-in document viewer. Pick a file below to view it.

-
- -
-
- - + .controls input[type="file"] { + font-size: 0.8rem; + color: #ccc; + } + + + +

PolyRender — Basic Example

+

+ Drop-in document viewer. Pick a file below to view it. +

+
+ +
+
+ + diff --git a/examples/basic/package.json b/examples/basic/package.json index c5bd204..613047e 100644 --- a/examples/basic/package.json +++ b/examples/basic/package.json @@ -1,5 +1,5 @@ { - "name": "@docview/example-basic", + "name": "@polyrender/example-basic", "private": true, "version": "0.0.0", "type": "module", @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "@docview/core": "workspace:*", + "@polyrender/core": "workspace:*", "pdfjs-dist": ">=4.0.0", "epubjs": ">=0.3.0", "docx-preview": ">=0.3.0", diff --git a/examples/basic/src/main.ts b/examples/basic/src/main.ts index f4dcbf6..0b90d12 100644 --- a/examples/basic/src/main.ts +++ b/examples/basic/src/main.ts @@ -1,11 +1,11 @@ -import { DocView } from '@docview/core' +import { PolyRender } from '@polyrender/core' import '../../../packages/core/src/styles.css' import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url' const viewerEl = document.getElementById('viewer')! const fileInput = document.getElementById('file-input') as HTMLInputElement -let viewer: DocView | null = null +let viewer: PolyRender | null = null fileInput.addEventListener('change', () => { const file = fileInput.files?.[0] @@ -17,8 +17,8 @@ fileInput.addEventListener('change', () => { viewer = null } - // Create a new DocView instance with the selected file - viewer = new DocView(viewerEl, { + // Create a new PolyRender instance with the selected file + viewer = new PolyRender(viewerEl, { source: { type: 'file', data: file, @@ -33,7 +33,7 @@ fileInput.addEventListener('change', () => { console.log(`Loaded "${file.name}" — ${info.pageCount} page(s), format: ${info.format}`) }, onError: (err) => { - console.error('DocView error:', err) + console.error('PolyRender error:', err) }, }) }) diff --git a/examples/basic/vite.config.ts b/examples/basic/vite.config.ts index f180c8e..c160a9a 100644 --- a/examples/basic/vite.config.ts +++ b/examples/basic/vite.config.ts @@ -33,11 +33,11 @@ function resolvePeerDeps(): Plugin { ].join('\n') return { - name: 'resolve-docview-peer-deps', + name: 'resolve-polyrender-peer-deps', enforce: 'pre', transform(code: string, id: string) { - // Only transform the @docview/core bundle - if (!id.includes('docview')) return + // Only transform the @polyrender/core bundle + if (!id.includes('polyrender')) return if (!code.includes('moduleName')) return // Replace `await import(moduleName)` or `await import(\n moduleName\n)` diff --git a/examples/vanilla/build.js b/examples/vanilla/build.js index 3fb07db..6c60673 100644 --- a/examples/vanilla/build.js +++ b/examples/vanilla/build.js @@ -2,98 +2,116 @@ * Minimal esbuild build script — replaces Vite entirely. * * The only "magic" is an esbuild plugin that rewrites the variable-based - * dynamic `import(moduleName)` in @docview/core's `requirePeerDep` into + * dynamic `import(moduleName)` in @polyrender/core's `requirePeerDep` into * static import() calls that esbuild can bundle. * * This is the same fundamental fix needed in Vite, webpack, or any other * bundler — browsers cannot resolve bare specifiers from `import(variable)`. */ -import { build, context } from 'esbuild' -import { cpSync, existsSync, mkdirSync } from 'fs' -import { resolve, dirname } from 'path' -import { fileURLToPath } from 'url' +import { build, context } from "esbuild"; +import { cpSync, existsSync, mkdirSync } from "fs"; +import { resolve, dirname } from "path"; +import { fileURLToPath } from "url"; -const __dirname = dirname(fileURLToPath(import.meta.url)) -const isWatch = process.argv.includes('--watch') +const __dirname = dirname(fileURLToPath(import.meta.url)); +const isWatch = process.argv.includes("--watch"); -/** Plugin to resolve @docview/core's dynamic peer-dep imports. */ +/** Plugin to resolve @polyrender/core's dynamic peer-dep imports. */ const resolvePeerDeps = { - name: 'resolve-peer-deps', - setup(build) { - build.onLoad({ filter: /docview.*\.(js|ts)$/ }, async (args) => { - const fs = await import('fs') - let contents = fs.readFileSync(args.path, 'utf8') + name: "resolve-peer-deps", + setup(build) { + build.onLoad({ filter: /polyrender.*\.(js|ts)$/ }, async (args) => { + const fs = await import("fs"); + let contents = fs.readFileSync(args.path, "utf8"); - if (contents.includes('moduleName') && contents.includes('import(')) { - // Map of peer dep names → static imports - const peerDeps = [ - 'pdfjs-dist', 'epubjs', 'docx-preview', - 'papaparse', 'highlight.js', 'jszip', 'xlsx', - ] - const cases = peerDeps - .map(d => ` case '${d}': return import('${d}').then(m => m.default || m);`) - .join('\n') + if ( + contents.includes("moduleName") && + contents.includes("import(") + ) { + // Map of peer dep names → static imports + const peerDeps = [ + "pdfjs-dist", + "epubjs", + "docx-preview", + "papaparse", + "highlight.js", + "jszip", + "xlsx", + ]; + const cases = peerDeps + .map( + (d) => + ` case '${d}': return import('${d}').then(m => m.default || m);`, + ) + .join("\n"); - const replacement = [ - '(async (name) => { switch(name) {', - cases, - " default: throw new Error(`Unknown peer dep: ${name}`);", - ' }})(moduleName)', - ].join('\n') + const replacement = [ + "(async (name) => { switch(name) {", + cases, + " default: throw new Error(`Unknown peer dep: ${name}`);", + " }})(moduleName)", + ].join("\n"); - contents = contents.replace( - /await\s+import\(\s*(?:\/\*.*?\*\/\s*)?moduleName\s*\)/g, - `await ${replacement}`, - ) - } + contents = contents.replace( + /await\s+import\(\s*(?:\/\*.*?\*\/\s*)?moduleName\s*\)/g, + `await ${replacement}`, + ); + } - return { contents, loader: args.path.endsWith('.ts') ? 'ts' : 'js' } - }) - }, -} + return { + contents, + loader: args.path.endsWith(".ts") ? "ts" : "js", + }; + }); + }, +}; // Ensure dist directory exists -const distDir = resolve(__dirname, 'dist') -if (!existsSync(distDir)) mkdirSync(distDir, { recursive: true }) +const distDir = resolve(__dirname, "dist"); +if (!existsSync(distDir)) mkdirSync(distDir, { recursive: true }); // Copy index.html to dist -cpSync(resolve(__dirname, 'index.html'), resolve(distDir, 'index.html')) +cpSync(resolve(__dirname, "index.html"), resolve(distDir, "index.html")); // Copy styles.css to dist -const stylesPath = resolve(__dirname, '../../packages/core/src/styles.css') +const stylesPath = resolve(__dirname, "../../packages/core/src/styles.css"); if (existsSync(stylesPath)) { - cpSync(stylesPath, resolve(distDir, 'styles.css')) + cpSync(stylesPath, resolve(distDir, "styles.css")); } // Copy pdfjs worker to dist -const workerGlob = resolve(__dirname, 'node_modules/pdfjs-dist/build') +const workerGlob = resolve(__dirname, "node_modules/pdfjs-dist/build"); if (existsSync(workerGlob)) { - const workerFiles = ['pdf.worker.min.mjs', 'pdf.worker.mjs', 'pdf.worker.min.js'] - for (const wf of workerFiles) { - const src = resolve(workerGlob, wf) - if (existsSync(src)) { - cpSync(src, resolve(distDir, wf)) - break + const workerFiles = [ + "pdf.worker.min.mjs", + "pdf.worker.mjs", + "pdf.worker.min.js", + ]; + for (const wf of workerFiles) { + const src = resolve(workerGlob, wf); + if (existsSync(src)) { + cpSync(src, resolve(distDir, wf)); + break; + } } - } } const buildOptions = { - entryPoints: [resolve(__dirname, 'src/main.ts')], - bundle: true, - format: 'esm', - outdir: distDir, - sourcemap: true, - target: 'es2022', - plugins: [resolvePeerDeps], - logLevel: 'info', -} + entryPoints: [resolve(__dirname, "src/main.ts")], + bundle: true, + format: "esm", + outdir: distDir, + sourcemap: true, + target: "es2022", + plugins: [resolvePeerDeps], + logLevel: "info", +}; if (isWatch) { - const ctx = await context(buildOptions) - await ctx.watch() - console.log('Watching for changes...') + const ctx = await context(buildOptions); + await ctx.watch(); + console.log("Watching for changes..."); } else { - await build(buildOptions) - console.log(`\nBuild complete! Serve with:\n npx serve dist\n`) + await build(buildOptions); + console.log(`\nBuild complete! Serve with:\n npx serve dist\n`); } diff --git a/examples/vanilla/index.html b/examples/vanilla/index.html index 0b7eca5..d62380f 100644 --- a/examples/vanilla/index.html +++ b/examples/vanilla/index.html @@ -1,80 +1,89 @@ - + - - - - DocView — Vanilla TS Example - - - - -

DocView — Vanilla TS Example

-

No Vite. Just TypeScript + esbuild. Pick a file below to view it.

-
- -
-
- - + .controls input[type="file"] { + font-size: 0.8rem; + color: #ccc; + } + + + +

PolyRender — Vanilla TS Example

+

+ No Vite. Just TypeScript + esbuild. Pick a file below to view it. +

+
+ +
+
+ + diff --git a/examples/vanilla/package.json b/examples/vanilla/package.json index 7f5de4e..d7e0955 100644 --- a/examples/vanilla/package.json +++ b/examples/vanilla/package.json @@ -1,5 +1,5 @@ { - "name": "@docview/example-vanilla", + "name": "@polyrender/example-vanilla", "private": true, "version": "0.0.0", "type": "module", @@ -9,7 +9,7 @@ "dev": "node build.js --watch" }, "dependencies": { - "@docview/core": "workspace:*", + "@polyrender/core": "workspace:*", "pdfjs-dist": ">=4.0.0", "epubjs": ">=0.3.0", "docx-preview": ">=0.3.0", @@ -22,4 +22,4 @@ "typescript": "^5.5.0", "esbuild": "^0.25.0" } -} +} \ No newline at end of file diff --git a/examples/vanilla/src/main.ts b/examples/vanilla/src/main.ts index 98a76c8..d183084 100644 --- a/examples/vanilla/src/main.ts +++ b/examples/vanilla/src/main.ts @@ -1,9 +1,9 @@ -import { DocView } from '@docview/core' +import { PolyRender } from '@polyrender/core' const viewerEl = document.getElementById('viewer')! const fileInput = document.getElementById('file-input') as HTMLInputElement -let viewer: DocView | null = null +let viewer: PolyRender | null = null fileInput.addEventListener('change', () => { const file = fileInput.files?.[0] @@ -15,8 +15,8 @@ fileInput.addEventListener('change', () => { viewer = null } - // Create a new DocView instance with the selected file - viewer = new DocView(viewerEl, { + // Create a new PolyRender instance with the selected file + viewer = new PolyRender(viewerEl, { source: { type: 'file', data: file, @@ -32,7 +32,7 @@ fileInput.addEventListener('change', () => { console.log(`Loaded "${file.name}" — ${info.pageCount} page(s), format: ${info.format}`) }, onError: (err) => { - console.error('DocView error:', err) + console.error('PolyRender error:', err) }, }) }) diff --git a/package.json b/package.json index 0a7146d..6f832b0 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "docview-monorepo", + "name": "polyrender-monorepo", "private": true, "version": "0.0.0", "description": "A framework-agnostic, universal document renderer with optional chunked loading", diff --git a/packages/core/package.json b/packages/core/package.json index b68367e..c683291 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,5 +1,5 @@ { - "name": "@docview/core", + "name": "@polyrender/core", "version": "0.1.0", "description": "Framework-agnostic universal document renderer — PDF, EPUB, DOCX, CSV, code, and plain text with optional chunked/paged loading", "license": "Zlib", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f93f80b..5c60741 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,5 +1,5 @@ // Main entry point -export { DocView } from './docview.js' +export { PolyRender } from './polyrender.js' // Types export type { @@ -22,7 +22,7 @@ export type { TextFetchAdapter, // Options - DocViewOptions, + PolyRenderOptions, PdfOptions, CodeOptions, CsvOptions, @@ -33,7 +33,7 @@ export type { // State & Info DocumentInfo, - DocViewState, + PolyRenderState, DocumentFormat, // Renderer interface (for custom renderers) @@ -41,14 +41,14 @@ export type { RendererFactory, // Events - DocViewEventMap, - DocViewEventType, + PolyRenderEventMap, + PolyRenderEventType, // Errors - DocViewErrorCode, + PolyRenderErrorCode, } from './types.js' -export { DocViewError } from './types.js' +export { PolyRenderError } from './types.js' // Registry (for custom renderer registration) export { registry } from './registry.js' diff --git a/packages/core/src/docview.ts b/packages/core/src/polyrender.ts similarity index 87% rename from packages/core/src/docview.ts rename to packages/core/src/polyrender.ts index c42f8fb..22cf93a 100644 --- a/packages/core/src/docview.ts +++ b/packages/core/src/polyrender.ts @@ -1,14 +1,14 @@ import type { - DocViewOptions, - DocViewState, + PolyRenderOptions, + PolyRenderState, DocumentFormat, Renderer, RendererFactory, - DocViewEventMap, - DocViewEventType, + PolyRenderEventMap, + PolyRenderEventType, ToolbarConfig, } from './types.js' -import { DocViewError } from './types.js' +import { PolyRenderError } from './types.js' import { registry } from './registry.js' import { detectFormat, getRendererFormat, clearElement } from './utils.js' import { createToolbar, type ToolbarHandle } from './toolbar.js' @@ -24,17 +24,17 @@ function ensureRegistered() { } /** - * DocView — Universal Document Viewer + * PolyRender — Universal Document Viewer * * Framework-agnostic entry point. Creates a document viewer inside a container * element, auto-detecting the format and loading the appropriate renderer. * * @example * ```ts - * import { DocView } from '@docview/core' - * import '@docview/core/styles.css' + * import { PolyRender } from '@polyrender/core' + * import '@polyrender/core/styles.css' * - * const viewer = new DocView(document.getElementById('viewer')!, { + * const viewer = new PolyRender(document.getElementById('viewer')!, { * source: { type: 'url', url: '/document.pdf' }, * theme: 'dark', * onReady: (info) => console.log('Loaded:', info.pageCount, 'pages'), @@ -47,16 +47,16 @@ function ensureRegistered() { * viewer.destroy() * ``` */ -export class DocView { +export class PolyRender { private container: HTMLElement - private options: DocViewOptions + private options: PolyRenderOptions private renderer: Renderer | null = null private toolbar: ToolbarHandle | null = null private root: HTMLElement private listeners = new Map void>>() private destroyed = false - constructor(container: HTMLElement, options: DocViewOptions) { + constructor(container: HTMLElement, options: PolyRenderOptions) { ensureRegistered() this.container = container @@ -64,15 +64,15 @@ export class DocView { // Create root element this.root = document.createElement('div') - this.root.className = `docview${options.className ? ` ${options.className}` : ''}` + this.root.className = `polyrender${options.className ? ` ${options.className}` : ''}` this.root.setAttribute('data-theme', this.resolveTheme(options.theme)) container.appendChild(this.root) // Initialize asynchronously this.init().catch((err) => { - const error = err instanceof DocViewError + const error = err instanceof PolyRenderError ? err - : new DocViewError('UNKNOWN', String(err), err) + : new PolyRenderError('UNKNOWN', String(err), err) options.onError?.(error) this.emit('error', error) }) @@ -110,7 +110,7 @@ export class DocView { } /** Get current viewer state. */ - getState(): DocViewState { + getState(): PolyRenderState { if (!this.renderer) { return { loading: true, @@ -132,23 +132,23 @@ export class DocView { } /** Update options (theme, zoom, etc.) without re-mounting. */ - async update(changed: Partial): Promise { + async update(changed: Partial): Promise { Object.assign(this.options, changed) if (changed.theme) { this.root.setAttribute('data-theme', this.resolveTheme(changed.theme)) } if (changed.className !== undefined) { - this.root.className = `docview${changed.className ? ` ${changed.className}` : ''}` + this.root.className = `polyrender${changed.className ? ` ${changed.className}` : ''}` } await this.renderer?.update(changed) } /** Subscribe to events. Returns an unsubscribe function. */ - on( + on( event: K, - callback: (data: DocViewEventMap[K]) => void, + callback: (data: PolyRenderEventMap[K]) => void, ): () => void { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()) @@ -193,7 +193,7 @@ export class DocView { const format = explicitFormat ?? detectedFormat if (!format) { - throw new DocViewError( + throw new PolyRenderError( 'FORMAT_DETECTION_FAILED', 'Could not detect the document format. Provide a `format` option or ensure ' + 'the source has a recognizable filename, URL extension, or MIME type.', @@ -206,7 +206,7 @@ export class DocView { // Create renderer const renderer = registry.create(rendererFormat) if (!renderer) { - throw new DocViewError( + throw new PolyRenderError( 'FORMAT_UNSUPPORTED', `No renderer registered for format "${rendererFormat}". ` + `Available formats: ${registry.formats().join(', ')}`, @@ -297,7 +297,7 @@ export class DocView { } } - private emit(event: K, data: DocViewEventMap[K]): void { + private emit(event: K, data: PolyRenderEventMap[K]): void { const callbacks = this.listeners.get(event) if (callbacks) { for (const cb of callbacks) { diff --git a/packages/core/src/registry.ts b/packages/core/src/registry.ts index d664cbf..a39e31f 100644 --- a/packages/core/src/registry.ts +++ b/packages/core/src/registry.ts @@ -3,7 +3,7 @@ import type { DocumentFormat, Renderer, RendererFactory } from './types.js' /** * Registry mapping document formats to their renderer factories. * Built-in renderers are registered by default. Consumers can register - * custom renderers for new formats via `DocView.registerRenderer()`. + * custom renderers for new formats via `PolyRender.registerRenderer()`. */ class FormatRegistry { private factories = new Map() diff --git a/packages/core/src/renderer.ts b/packages/core/src/renderer.ts index e09c35d..5872f7b 100644 --- a/packages/core/src/renderer.ts +++ b/packages/core/src/renderer.ts @@ -1,11 +1,11 @@ import type { Renderer, - DocViewOptions, + PolyRenderOptions, DocumentFormat, DocumentInfo, - DocViewState, + PolyRenderState, } from './types.js' -import { DocViewError } from './types.js' +import { PolyRenderError } from './types.js' import { el, clearElement } from './utils.js' /** @@ -23,8 +23,8 @@ export abstract class BaseRenderer implements Renderer { protected container!: HTMLElement protected viewport!: HTMLElement - protected options!: DocViewOptions - protected state: DocViewState = { + protected options!: PolyRenderOptions + protected state: PolyRenderState = { loading: true, error: null, currentPage: 1, @@ -33,7 +33,7 @@ export abstract class BaseRenderer implements Renderer { documentInfo: null, } - async mount(container: HTMLElement, options: DocViewOptions): Promise { + async mount(container: HTMLElement, options: PolyRenderOptions): Promise { this.container = container this.options = options this.state.currentPage = options.initialPage ?? 1 @@ -47,9 +47,9 @@ export abstract class BaseRenderer implements Renderer { try { await this.onMount(this.viewport, options) } catch (err) { - const error = err instanceof DocViewError + const error = err instanceof PolyRenderError ? err - : new DocViewError('RENDER_FAILED', String(err), err) + : new PolyRenderError('RENDER_FAILED', String(err), err) this.state.error = error this.state.loading = false this.showError(error) @@ -57,7 +57,7 @@ export abstract class BaseRenderer implements Renderer { } } - async update(changed: Partial): Promise { + async update(changed: Partial): Promise { Object.assign(this.options, changed) await this.onUpdate(changed) } @@ -99,13 +99,13 @@ export abstract class BaseRenderer implements Renderer { // --- Subclass hooks --- /** Render the document into the viewport. */ - protected abstract onMount(viewport: HTMLElement, options: DocViewOptions): Promise + protected abstract onMount(viewport: HTMLElement, options: PolyRenderOptions): Promise /** Clean up format-specific resources. */ protected abstract onDestroy(): void /** React to option changes. Default: no-op. */ - protected async onUpdate(_changed: Partial): Promise {} + protected async onUpdate(_changed: Partial): Promise {} /** Navigate to a page in the rendered content. Default: no-op. */ protected onPageChange(_page: number): void {} @@ -140,7 +140,7 @@ export abstract class BaseRenderer implements Renderer { } /** Show an error message in the viewport. */ - protected showError(error: DocViewError): void { + protected showError(error: PolyRenderError): void { clearElement(this.viewport) const errorEl = el('div', 'dv-error') errorEl.innerHTML = ` diff --git a/packages/core/src/renderers/browse-pages.ts b/packages/core/src/renderers/browse-pages.ts index d18dc33..ba60e62 100644 --- a/packages/core/src/renderers/browse-pages.ts +++ b/packages/core/src/renderers/browse-pages.ts @@ -1,5 +1,5 @@ import type { - DocViewOptions, + PolyRenderOptions, DocumentFormat, PageData, PageFetchAdapter, @@ -7,7 +7,7 @@ import type { TextLayerData, TextFetchAdapter, } from '../types.js' -import { DocViewError } from '../types.js' +import { PolyRenderError } from '../types.js' import { BaseRenderer } from '../renderer.js' import { el, clamp, debounce } from '../utils.js' @@ -31,7 +31,7 @@ export class BrowsePagesRenderer extends BaseRenderer { private observer: IntersectionObserver | null = null private debouncedScroll: ReturnType | null = null - protected async onMount(viewport: HTMLElement, options: DocViewOptions): Promise { + protected async onMount(viewport: HTMLElement, options: PolyRenderOptions): Promise { const loadingEl = this.showLoading('Loading pages…') const source = options.source as PagesSource @@ -53,7 +53,7 @@ export class BrowsePagesRenderer extends BaseRenderer { const totalPages = this.fetchAdapter?.totalPages ?? this.pages.length if (totalPages === 0) { - throw new DocViewError('RENDER_FAILED', 'No pages provided.') + throw new PolyRenderError('RENDER_FAILED', 'No pages provided.') } // Create pages container diff --git a/packages/core/src/renderers/chunked-pdf.ts b/packages/core/src/renderers/chunked-pdf.ts index 828cd25..1ae4fa0 100644 --- a/packages/core/src/renderers/chunked-pdf.ts +++ b/packages/core/src/renderers/chunked-pdf.ts @@ -1,12 +1,12 @@ import type { - DocViewOptions, + PolyRenderOptions, DocumentFormat, ChunkedSource, ChunkData, ChunkFetchAdapter, PageFetchAdapter, } from '../types.js' -import { DocViewError } from '../types.js' +import { PolyRenderError } from '../types.js' import { BaseRenderer } from '../renderer.js' import { el, clamp, debounce, requirePeerDep, toArrayBuffer } from '../utils.js' @@ -42,7 +42,7 @@ export class ChunkedPdfRenderer extends BaseRenderer { private debouncedScroll: ReturnType | null = null private pdfjsLib: unknown = null - protected async onMount(viewport: HTMLElement, options: DocViewOptions): Promise { + protected async onMount(viewport: HTMLElement, options: PolyRenderOptions): Promise { const loadingEl = this.showLoading('Loading document…') const source = options.source as ChunkedSource @@ -66,7 +66,7 @@ export class ChunkedPdfRenderer extends BaseRenderer { } catch { // If pdfjs not available, we can still show browse pages if (!this.browseAdapter && !source.browsePages) { - throw new DocViewError( + throw new PolyRenderError( 'PEER_DEPENDENCY_MISSING', 'Chunked PDF rendering requires pdfjs-dist. Install it with: npm install pdfjs-dist', ) diff --git a/packages/core/src/renderers/code.ts b/packages/core/src/renderers/code.ts index cd52510..849c874 100644 --- a/packages/core/src/renderers/code.ts +++ b/packages/core/src/renderers/code.ts @@ -1,4 +1,4 @@ -import type { DocViewOptions, DocumentFormat } from '../types.js' +import type { PolyRenderOptions, DocumentFormat } from '../types.js' import { BaseRenderer } from '../renderer.js' import { el, toText, fetchAsBuffer, requirePeerDep, getLanguageFromExtension } from '../utils.js' @@ -20,7 +20,7 @@ export class CodeRenderer extends BaseRenderer { private codeContainer!: HTMLElement private hljs: HighlightJS | null = null - protected async onMount(viewport: HTMLElement, options: DocViewOptions): Promise { + protected async onMount(viewport: HTMLElement, options: PolyRenderOptions): Promise { this.showLoading('Loading file…') // Try to load highlight.js (optional peer dep) @@ -89,7 +89,7 @@ export class CodeRenderer extends BaseRenderer { }) } - private async loadText(options: DocViewOptions): Promise { + private async loadText(options: PolyRenderOptions): Promise { const source = options.source if (source.type === 'file') return toText(source.data) if (source.type === 'url') { @@ -99,7 +99,7 @@ export class CodeRenderer extends BaseRenderer { return '' } - private detectLanguage(options: DocViewOptions): string | null { + private detectLanguage(options: PolyRenderOptions): string | null { // From explicit format const format = options.format if (format && format !== 'code') { @@ -133,7 +133,7 @@ export class CodeRenderer extends BaseRenderer { .replace(/"/g, '"') } - private getFilename(options: DocViewOptions): string | undefined { + private getFilename(options: PolyRenderOptions): string | undefined { const source = options.source if ('filename' in source && source.filename) return source.filename if (source.type === 'url') return source.url.split('/').pop()?.split('?')[0] diff --git a/packages/core/src/renderers/csv.ts b/packages/core/src/renderers/csv.ts index 57782ee..f4eb240 100644 --- a/packages/core/src/renderers/csv.ts +++ b/packages/core/src/renderers/csv.ts @@ -1,4 +1,4 @@ -import type { DocViewOptions, DocumentFormat } from '../types.js' +import type { PolyRenderOptions, DocumentFormat } from '../types.js' import { BaseRenderer } from '../renderer.js' import { el, toText, fetchAsBuffer, requirePeerDep } from '../utils.js' @@ -30,7 +30,7 @@ export class CsvRenderer extends BaseRenderer { private sortCol = -1 private sortAsc = true - protected async onMount(viewport: HTMLElement, options: DocViewOptions): Promise { + protected async onMount(viewport: HTMLElement, options: PolyRenderOptions): Promise { this.showLoading('Parsing data…') const Papa = await requirePeerDep('papaparse', 'CSV') @@ -156,7 +156,7 @@ export class CsvRenderer extends BaseRenderer { this.renderTable(true) } - private async loadText(options: DocViewOptions): Promise { + private async loadText(options: PolyRenderOptions): Promise { const source = options.source if (source.type === 'file') return toText(source.data) if (source.type === 'url') { @@ -166,7 +166,7 @@ export class CsvRenderer extends BaseRenderer { return '' } - private getFilename(options: DocViewOptions): string | undefined { + private getFilename(options: PolyRenderOptions): string | undefined { const source = options.source if ('filename' in source && source.filename) return source.filename if (source.type === 'url') return source.url.split('/').pop()?.split('?')[0] diff --git a/packages/core/src/renderers/docx.ts b/packages/core/src/renderers/docx.ts index a4ed857..49bbc5e 100644 --- a/packages/core/src/renderers/docx.ts +++ b/packages/core/src/renderers/docx.ts @@ -1,4 +1,4 @@ -import type { DocViewOptions, DocumentFormat } from '../types.js' +import type { PolyRenderOptions, DocumentFormat } from '../types.js' import { BaseRenderer } from '../renderer.js' import { el, toArrayBuffer, fetchAsBuffer, requirePeerDep } from '../utils.js' @@ -35,7 +35,7 @@ export class DocxRenderer extends BaseRenderer { private docxContainer!: HTMLElement - protected async onMount(viewport: HTMLElement, options: DocViewOptions): Promise { + protected async onMount(viewport: HTMLElement, options: PolyRenderOptions): Promise { this.showLoading('Rendering document…') const docxPreview = await requirePeerDep('docx-preview', 'DOCX') @@ -71,14 +71,14 @@ export class DocxRenderer extends BaseRenderer { }) } - private async loadData(options: DocViewOptions): Promise { + private async loadData(options: PolyRenderOptions): Promise { const source = options.source if (source.type === 'file') return toArrayBuffer(source.data) if (source.type === 'url') return fetchAsBuffer(source.url, source.fetchOptions) throw new Error('DOCX renderer requires a file or url source.') } - private getFilename(options: DocViewOptions): string | undefined { + private getFilename(options: PolyRenderOptions): string | undefined { const source = options.source if ('filename' in source && source.filename) return source.filename if (source.type === 'url') return source.url.split('/').pop()?.split('?')[0] diff --git a/packages/core/src/renderers/epub.ts b/packages/core/src/renderers/epub.ts index b539c2a..864b8c7 100644 --- a/packages/core/src/renderers/epub.ts +++ b/packages/core/src/renderers/epub.ts @@ -1,4 +1,4 @@ -import type { DocViewOptions, DocumentFormat } from '../types.js' +import type { PolyRenderOptions, DocumentFormat } from '../types.js' import { BaseRenderer } from '../renderer.js' import { el, toArrayBuffer, fetchAsBuffer, requirePeerDep } from '../utils.js' @@ -52,7 +52,7 @@ export class EpubRenderer extends BaseRenderer { private epubContainer!: HTMLElement private keyHandler: ((e: KeyboardEvent) => void) | null = null - protected async onMount(viewport: HTMLElement, options: DocViewOptions): Promise { + protected async onMount(viewport: HTMLElement, options: PolyRenderOptions): Promise { this.showLoading('Loading book…') const epubjs = await requirePeerDep('epubjs', 'EPUB') @@ -163,7 +163,7 @@ export class EpubRenderer extends BaseRenderer { this.rendition?.display(String(page - 1)) } - private getFilename(options: DocViewOptions): string | undefined { + private getFilename(options: PolyRenderOptions): string | undefined { const source = options.source if ('filename' in source && source.filename) return source.filename if (source.type === 'url') return source.url.split('/').pop()?.split('?')[0] diff --git a/packages/core/src/renderers/ods.ts b/packages/core/src/renderers/ods.ts index 8aaffc2..5ace2eb 100644 --- a/packages/core/src/renderers/ods.ts +++ b/packages/core/src/renderers/ods.ts @@ -1,4 +1,4 @@ -import type { DocViewOptions, DocumentFormat } from '../types.js' +import type { PolyRenderOptions, DocumentFormat } from '../types.js' import { BaseRenderer } from '../renderer.js' import { el, toArrayBuffer, fetchAsBuffer, requirePeerDep } from '../utils.js' @@ -35,7 +35,7 @@ export class OdsRenderer extends BaseRenderer { private sortCol = -1 private sortAsc = true - protected async onMount(viewport: HTMLElement, options: DocViewOptions): Promise { + protected async onMount(viewport: HTMLElement, options: PolyRenderOptions): Promise { this.showLoading('Parsing spreadsheet…') const XLSX = await requirePeerDep('xlsx', 'ODS') @@ -213,14 +213,14 @@ export class OdsRenderer extends BaseRenderer { } } - private async loadData(options: DocViewOptions): Promise { + private async loadData(options: PolyRenderOptions): Promise { const source = options.source if (source.type === 'file') return toArrayBuffer(source.data) if (source.type === 'url') return fetchAsBuffer(source.url, source.fetchOptions) throw new Error('ODS renderer requires a file or url source.') } - private getFilename(options: DocViewOptions): string | undefined { + private getFilename(options: PolyRenderOptions): string | undefined { const source = options.source if ('filename' in source && source.filename) return source.filename if (source.type === 'url') return source.url.split('/').pop()?.split('?')[0] diff --git a/packages/core/src/renderers/odt.ts b/packages/core/src/renderers/odt.ts index a731a2f..606e3fc 100644 --- a/packages/core/src/renderers/odt.ts +++ b/packages/core/src/renderers/odt.ts @@ -1,4 +1,4 @@ -import type { DocViewOptions, DocumentFormat } from '../types.js' +import type { PolyRenderOptions, DocumentFormat } from '../types.js' import { BaseRenderer } from '../renderer.js' import { el, toArrayBuffer, fetchAsBuffer, requirePeerDep } from '../utils.js' @@ -27,7 +27,7 @@ export class OdtRenderer extends BaseRenderer { private odtContainer!: HTMLElement - protected async onMount(viewport: HTMLElement, options: DocViewOptions): Promise { + protected async onMount(viewport: HTMLElement, options: PolyRenderOptions): Promise { this.showLoading('Rendering ODT document…') const JSZipLib = await requirePeerDep('jszip', 'ODT') @@ -265,14 +265,14 @@ export class OdtRenderer extends BaseRenderer { .replace(/"/g, '"') } - private async loadData(options: DocViewOptions): Promise { + private async loadData(options: PolyRenderOptions): Promise { const source = options.source if (source.type === 'file') return toArrayBuffer(source.data) if (source.type === 'url') return fetchAsBuffer(source.url, source.fetchOptions) throw new Error('ODT renderer requires a file or url source.') } - private getFilename(options: DocViewOptions): string | undefined { + private getFilename(options: PolyRenderOptions): string | undefined { const source = options.source if ('filename' in source && source.filename) return source.filename if (source.type === 'url') return source.url.split('/').pop()?.split('?')[0] diff --git a/packages/core/src/renderers/pdf.ts b/packages/core/src/renderers/pdf.ts index 895b50e..790030d 100644 --- a/packages/core/src/renderers/pdf.ts +++ b/packages/core/src/renderers/pdf.ts @@ -1,5 +1,5 @@ -import type { DocViewOptions, DocumentFormat, DocumentInfo } from '../types.js' -import { DocViewError } from '../types.js' +import type { PolyRenderOptions, DocumentFormat, DocumentInfo } from '../types.js' +import { PolyRenderError } from '../types.js' import { BaseRenderer } from '../renderer.js' import { el, @@ -58,7 +58,7 @@ export class PdfRenderer extends BaseRenderer { private resizeObserver: ResizeObserver | null = null private debouncedRender: ReturnType | null = null - protected async onMount(viewport: HTMLElement, options: DocViewOptions): Promise { + protected async onMount(viewport: HTMLElement, options: PolyRenderOptions): Promise { const loadingEl = this.showLoading('Loading PDF…') // Load pdfjs-dist @@ -182,7 +182,7 @@ export class PdfRenderer extends BaseRenderer { } private async loadSource( - options: DocViewOptions, + options: PolyRenderOptions, ): Promise { const source = options.source if (source.type === 'url') { @@ -192,7 +192,7 @@ export class PdfRenderer extends BaseRenderer { if (source.type === 'file') { return toArrayBuffer(source.data) } - throw new DocViewError( + throw new PolyRenderError( 'SOURCE_LOAD_FAILED', 'PDF renderer requires a file or url source.', ) @@ -334,7 +334,7 @@ export class PdfRenderer extends BaseRenderer { return this.baseScale * (mode === 'fit-page' ? 0.95 : 1) } - private getFilename(options: DocViewOptions): string | undefined { + private getFilename(options: PolyRenderOptions): string | undefined { const src = options.source if ('filename' in src && src.filename) return src.filename if (src.type === 'url') { diff --git a/packages/core/src/renderers/text.ts b/packages/core/src/renderers/text.ts index ae3b79d..80cb7dc 100644 --- a/packages/core/src/renderers/text.ts +++ b/packages/core/src/renderers/text.ts @@ -1,4 +1,4 @@ -import type { DocViewOptions, DocumentFormat } from '../types.js' +import type { PolyRenderOptions, DocumentFormat } from '../types.js' import { BaseRenderer } from '../renderer.js' import { el, toText, fetchAsBuffer } from '../utils.js' @@ -11,7 +11,7 @@ export class TextRenderer extends BaseRenderer { private textContainer!: HTMLElement - protected async onMount(viewport: HTMLElement, options: DocViewOptions): Promise { + protected async onMount(viewport: HTMLElement, options: PolyRenderOptions): Promise { this.showLoading('Loading text…') const text = await this.loadText(options) @@ -34,7 +34,7 @@ export class TextRenderer extends BaseRenderer { }) } - private async loadText(options: DocViewOptions): Promise { + private async loadText(options: PolyRenderOptions): Promise { const source = options.source if (source.type === 'file') { return toText(source.data) @@ -46,7 +46,7 @@ export class TextRenderer extends BaseRenderer { return '' } - private getExtension(options: DocViewOptions): string { + private getExtension(options: PolyRenderOptions): string { const source = options.source const name = ('filename' in source ? source.filename : undefined) ?? (source.type === 'url' ? source.url : '') @@ -54,7 +54,7 @@ export class TextRenderer extends BaseRenderer { return dot >= 0 ? name.slice(dot + 1).toLowerCase() : '' } - private getFilename(options: DocViewOptions): string | undefined { + private getFilename(options: PolyRenderOptions): string | undefined { const source = options.source if ('filename' in source && source.filename) return source.filename if (source.type === 'url') { diff --git a/packages/core/src/styles.css b/packages/core/src/styles.css index 3b7a87d..b5a877a 100644 --- a/packages/core/src/styles.css +++ b/packages/core/src/styles.css @@ -1,699 +1,797 @@ /* ========================================================================== - DocView — CSS Variables Theme System + PolyRender — CSS Variables Theme System Override any --dv-* variable to customize the viewer appearance. ========================================================================== */ /* --- Base / Dark Theme (default) --- */ -.docview { - /* Typography */ - --dv-font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - --dv-font-mono: 'SF Mono', SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace; - --dv-font-size-sm: 12px; - --dv-font-size: 14px; - --dv-font-size-lg: 16px; - --dv-line-height: 1.5; - - /* Spacing & Radii */ - --dv-radius-sm: 4px; - --dv-radius: 6px; - --dv-radius-lg: 10px; - --dv-spacing-xs: 4px; - --dv-spacing-sm: 8px; - --dv-spacing: 12px; - --dv-spacing-lg: 16px; - --dv-spacing-xl: 24px; - - /* Transitions */ - --dv-transition-fast: 100ms ease; - --dv-transition: 150ms ease; - --dv-transition-slow: 250ms ease; - - /* Colors — Dark */ - --dv-bg: #0d1117; - --dv-surface: #161b22; - --dv-surface-raised: #1c2129; - --dv-border: #30363d; - --dv-border-subtle: #21262d; - --dv-text: #e6edf3; - --dv-text-secondary: #8b949e; - --dv-text-muted: #484f58; - --dv-accent: #58a6ff; - --dv-accent-hover: #79b8ff; - --dv-accent-subtle: rgba(88, 166, 255, 0.15); - --dv-error: #f85149; - --dv-warning: #d29922; - - /* Document Page */ - --dv-page-bg: #ffffff; - --dv-page-shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 4px 12px rgba(0, 0, 0, 0.2); - --dv-page-gap: 12px; - - /* Toolbar */ - --dv-toolbar-bg: #161b22; - --dv-toolbar-border: #30363d; - --dv-toolbar-height: 44px; - - /* Scrollbar */ - --dv-scrollbar-size: 8px; - --dv-scrollbar-thumb: #484f58; - --dv-scrollbar-thumb-hover: #6e7681; - --dv-scrollbar-track: transparent; - - /* Table (CSV) */ - --dv-table-header-bg: #161b22; - --dv-table-row-hover: rgba(136, 146, 158, 0.08); - --dv-table-stripe: rgba(136, 146, 158, 0.04); - - /* Code */ - --dv-code-bg: #0d1117; - --dv-code-gutter: #484f58; - --dv-code-gutter-bg: #0d1117; - --dv-code-highlight-line: rgba(88, 166, 255, 0.1); +.polyrender { + /* Typography */ + --dv-font-sans: + system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + sans-serif; + --dv-font-mono: + "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; + --dv-font-size-sm: 12px; + --dv-font-size: 14px; + --dv-font-size-lg: 16px; + --dv-line-height: 1.5; + + /* Spacing & Radii */ + --dv-radius-sm: 4px; + --dv-radius: 6px; + --dv-radius-lg: 10px; + --dv-spacing-xs: 4px; + --dv-spacing-sm: 8px; + --dv-spacing: 12px; + --dv-spacing-lg: 16px; + --dv-spacing-xl: 24px; + + /* Transitions */ + --dv-transition-fast: 100ms ease; + --dv-transition: 150ms ease; + --dv-transition-slow: 250ms ease; + + /* Colors — Dark */ + --dv-bg: #0d1117; + --dv-surface: #161b22; + --dv-surface-raised: #1c2129; + --dv-border: #30363d; + --dv-border-subtle: #21262d; + --dv-text: #e6edf3; + --dv-text-secondary: #8b949e; + --dv-text-muted: #484f58; + --dv-accent: #58a6ff; + --dv-accent-hover: #79b8ff; + --dv-accent-subtle: rgba(88, 166, 255, 0.15); + --dv-error: #f85149; + --dv-warning: #d29922; + + /* Document Page */ + --dv-page-bg: #ffffff; + --dv-page-shadow: + 0 1px 3px rgba(0, 0, 0, 0.3), 0 4px 12px rgba(0, 0, 0, 0.2); + --dv-page-gap: 12px; + + /* Toolbar */ + --dv-toolbar-bg: #161b22; + --dv-toolbar-border: #30363d; + --dv-toolbar-height: 44px; + + /* Scrollbar */ + --dv-scrollbar-size: 8px; + --dv-scrollbar-thumb: #484f58; + --dv-scrollbar-thumb-hover: #6e7681; + --dv-scrollbar-track: transparent; + + /* Table (CSV) */ + --dv-table-header-bg: #161b22; + --dv-table-row-hover: rgba(136, 146, 158, 0.08); + --dv-table-stripe: rgba(136, 146, 158, 0.04); + + /* Code */ + --dv-code-bg: #0d1117; + --dv-code-gutter: #484f58; + --dv-code-gutter-bg: #0d1117; + --dv-code-highlight-line: rgba(88, 166, 255, 0.1); } /* --- Light Theme --- */ -.docview[data-theme='light'] { - --dv-bg: #ffffff; - --dv-surface: #f6f8fa; - --dv-surface-raised: #ffffff; - --dv-border: #d0d7de; - --dv-border-subtle: #e1e4e8; - --dv-text: #1f2328; - --dv-text-secondary: #656d76; - --dv-text-muted: #8b949e; - --dv-accent: #0969da; - --dv-accent-hover: #0550ae; - --dv-accent-subtle: rgba(9, 105, 218, 0.08); - --dv-error: #cf222e; - --dv-warning: #9a6700; - --dv-page-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 4px 12px rgba(0, 0, 0, 0.04); - --dv-toolbar-bg: #f6f8fa; - --dv-toolbar-border: #d0d7de; - --dv-scrollbar-thumb: #c1c8cf; - --dv-scrollbar-thumb-hover: #afb8c1; - --dv-table-header-bg: #f6f8fa; - --dv-table-row-hover: rgba(31, 35, 40, 0.04); - --dv-table-stripe: rgba(31, 35, 40, 0.02); - --dv-code-bg: #f6f8fa; - --dv-code-gutter: #8b949e; - --dv-code-gutter-bg: #f6f8fa; - --dv-code-highlight-line: rgba(9, 105, 218, 0.08); +.polyrender[data-theme="light"] { + --dv-bg: #ffffff; + --dv-surface: #f6f8fa; + --dv-surface-raised: #ffffff; + --dv-border: #d0d7de; + --dv-border-subtle: #e1e4e8; + --dv-text: #1f2328; + --dv-text-secondary: #656d76; + --dv-text-muted: #8b949e; + --dv-accent: #0969da; + --dv-accent-hover: #0550ae; + --dv-accent-subtle: rgba(9, 105, 218, 0.08); + --dv-error: #cf222e; + --dv-warning: #9a6700; + --dv-page-shadow: + 0 1px 3px rgba(0, 0, 0, 0.08), 0 4px 12px rgba(0, 0, 0, 0.04); + --dv-toolbar-bg: #f6f8fa; + --dv-toolbar-border: #d0d7de; + --dv-scrollbar-thumb: #c1c8cf; + --dv-scrollbar-thumb-hover: #afb8c1; + --dv-table-header-bg: #f6f8fa; + --dv-table-row-hover: rgba(31, 35, 40, 0.04); + --dv-table-stripe: rgba(31, 35, 40, 0.02); + --dv-code-bg: #f6f8fa; + --dv-code-gutter: #8b949e; + --dv-code-gutter-bg: #f6f8fa; + --dv-code-highlight-line: rgba(9, 105, 218, 0.08); } - /* ========================================================================== Root Container ========================================================================== */ -.docview { - position: relative; - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - min-height: 200px; - overflow: hidden; - background: var(--dv-bg); - color: var(--dv-text); - font-family: var(--dv-font-sans); - font-size: var(--dv-font-size); - line-height: var(--dv-line-height); - border-radius: var(--dv-radius); - border: 1px solid var(--dv-border); +.polyrender { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + min-height: 200px; + overflow: hidden; + background: var(--dv-bg); + color: var(--dv-text); + font-family: var(--dv-font-sans); + font-size: var(--dv-font-size); + line-height: var(--dv-line-height); + border-radius: var(--dv-radius); + border: 1px solid var(--dv-border); } - /* ========================================================================== Toolbar ========================================================================== */ .dv-toolbar { - display: flex; - align-items: center; - gap: var(--dv-spacing-sm); - height: var(--dv-toolbar-height); - min-height: var(--dv-toolbar-height); - padding: 0 var(--dv-spacing); - background: var(--dv-toolbar-bg); - border-bottom: 1px solid var(--dv-toolbar-border); - user-select: none; - flex-shrink: 0; - z-index: 10; + display: flex; + align-items: center; + gap: var(--dv-spacing-sm); + height: var(--dv-toolbar-height); + min-height: var(--dv-toolbar-height); + padding: 0 var(--dv-spacing); + background: var(--dv-toolbar-bg); + border-bottom: 1px solid var(--dv-toolbar-border); + user-select: none; + flex-shrink: 0; + z-index: 10; } -.dv-toolbar[data-position='bottom'] { - border-bottom: none; - border-top: 1px solid var(--dv-toolbar-border); - order: 1; +.dv-toolbar[data-position="bottom"] { + border-bottom: none; + border-top: 1px solid var(--dv-toolbar-border); + order: 1; } .dv-toolbar-group { - display: flex; - align-items: center; - gap: var(--dv-spacing-xs); + display: flex; + align-items: center; + gap: var(--dv-spacing-xs); } .dv-toolbar-spacer { - flex: 1; + flex: 1; } .dv-toolbar-separator { - width: 1px; - height: 20px; - background: var(--dv-border); - margin: 0 var(--dv-spacing-xs); + width: 1px; + height: 20px; + background: var(--dv-border); + margin: 0 var(--dv-spacing-xs); } .dv-toolbar-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - padding: 0; - border: none; - border-radius: var(--dv-radius-sm); - background: transparent; - color: var(--dv-text-secondary); - cursor: pointer; - transition: background var(--dv-transition-fast), color var(--dv-transition-fast); - flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 0; + border: none; + border-radius: var(--dv-radius-sm); + background: transparent; + color: var(--dv-text-secondary); + cursor: pointer; + transition: + background var(--dv-transition-fast), + color var(--dv-transition-fast); + flex-shrink: 0; } .dv-toolbar-btn:hover { - background: var(--dv-accent-subtle); - color: var(--dv-text); + background: var(--dv-accent-subtle); + color: var(--dv-text); } .dv-toolbar-btn:active { - background: var(--dv-border); + background: var(--dv-border); } .dv-toolbar-btn[disabled] { - opacity: 0.35; - cursor: default; - pointer-events: none; + opacity: 0.35; + cursor: default; + pointer-events: none; } .dv-toolbar-btn svg { - width: 16px; - height: 16px; + width: 16px; + height: 16px; } .dv-toolbar-label { - font-size: var(--dv-font-size-sm); - color: var(--dv-text-secondary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 200px; + font-size: var(--dv-font-size-sm); + color: var(--dv-text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; } .dv-page-input { - width: 48px; - height: 28px; - padding: 0 var(--dv-spacing-xs); - border: 1px solid var(--dv-border); - border-radius: var(--dv-radius-sm); - background: var(--dv-bg); - color: var(--dv-text); - font-family: var(--dv-font-mono); - font-size: var(--dv-font-size-sm); - text-align: center; - outline: none; + width: 48px; + height: 28px; + padding: 0 var(--dv-spacing-xs); + border: 1px solid var(--dv-border); + border-radius: var(--dv-radius-sm); + background: var(--dv-bg); + color: var(--dv-text); + font-family: var(--dv-font-mono); + font-size: var(--dv-font-size-sm); + text-align: center; + outline: none; } .dv-page-input:focus { - border-color: var(--dv-accent); - box-shadow: 0 0 0 2px var(--dv-accent-subtle); + border-color: var(--dv-accent); + box-shadow: 0 0 0 2px var(--dv-accent-subtle); } - /* ========================================================================== Viewport (Scrollable Document Area) ========================================================================== */ .dv-viewport { - flex: 1; - overflow: auto; - position: relative; - background: var(--dv-bg); + flex: 1; + overflow: auto; + position: relative; + background: var(--dv-bg); } /* Custom scrollbar */ .dv-viewport::-webkit-scrollbar { - width: var(--dv-scrollbar-size); - height: var(--dv-scrollbar-size); + width: var(--dv-scrollbar-size); + height: var(--dv-scrollbar-size); } .dv-viewport::-webkit-scrollbar-thumb { - background: var(--dv-scrollbar-thumb); - border-radius: 999px; + background: var(--dv-scrollbar-thumb); + border-radius: 999px; } .dv-viewport::-webkit-scrollbar-thumb:hover { - background: var(--dv-scrollbar-thumb-hover); + background: var(--dv-scrollbar-thumb-hover); } .dv-viewport::-webkit-scrollbar-track { - background: var(--dv-scrollbar-track); + background: var(--dv-scrollbar-track); } .dv-viewport { - scrollbar-width: thin; - scrollbar-color: var(--dv-scrollbar-thumb) var(--dv-scrollbar-track); + scrollbar-width: thin; + scrollbar-color: var(--dv-scrollbar-thumb) var(--dv-scrollbar-track); } - /* ========================================================================== Page Container (for paginated renderers: PDF, images, etc.) ========================================================================== */ .dv-pages { - display: flex; - flex-direction: column; - align-items: center; - padding: var(--dv-spacing-lg); - gap: var(--dv-page-gap); - min-height: 100%; + display: flex; + flex-direction: column; + align-items: center; + padding: var(--dv-spacing-lg); + gap: var(--dv-page-gap); + min-height: 100%; } .dv-page { - position: relative; - background: var(--dv-page-bg); - box-shadow: var(--dv-page-shadow); - border-radius: 2px; - overflow: hidden; - flex-shrink: 0; + position: relative; + background: var(--dv-page-bg); + box-shadow: var(--dv-page-shadow); + border-radius: 2px; + overflow: hidden; + flex-shrink: 0; } .dv-page canvas { - display: block; + display: block; } .dv-page img { - display: block; - width: 100%; - height: auto; + display: block; + width: 100%; + height: auto; } /* Text layer overlay for copy/paste over rendered pages */ .dv-text-layer { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - overflow: hidden; - opacity: 0.25; - line-height: 1; - pointer-events: all; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + opacity: 0.25; + line-height: 1; + pointer-events: all; } .dv-text-layer span { - position: absolute; - white-space: pre; - color: transparent; - cursor: text; + position: absolute; + white-space: pre; + color: transparent; + cursor: text; } .dv-text-layer span::selection { - background: var(--dv-accent); - color: transparent; - opacity: 0.4; + background: var(--dv-accent); + color: transparent; + opacity: 0.4; } - /* ========================================================================== Loading & Error States ========================================================================== */ .dv-loading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: var(--dv-spacing); - padding: var(--dv-spacing-xl); - min-height: 200px; - color: var(--dv-text-secondary); - font-size: var(--dv-font-size); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--dv-spacing); + padding: var(--dv-spacing-xl); + min-height: 200px; + color: var(--dv-text-secondary); + font-size: var(--dv-font-size); } .dv-spinner { - width: 24px; - height: 24px; - border: 2px solid var(--dv-border); - border-top-color: var(--dv-accent); - border-radius: 50%; - animation: dv-spin 0.6s linear infinite; + width: 24px; + height: 24px; + border: 2px solid var(--dv-border); + border-top-color: var(--dv-accent); + border-radius: 50%; + animation: dv-spin 0.6s linear infinite; } @keyframes dv-spin { - to { transform: rotate(360deg); } + to { + transform: rotate(360deg); + } } .dv-error { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: var(--dv-spacing-sm); - padding: var(--dv-spacing-xl); - min-height: 200px; - color: var(--dv-error); - text-align: center; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--dv-spacing-sm); + padding: var(--dv-spacing-xl); + min-height: 200px; + color: var(--dv-error); + text-align: center; } .dv-error-code { - font-family: var(--dv-font-mono); - font-size: var(--dv-font-size-sm); - color: var(--dv-text-muted); + font-family: var(--dv-font-mono); + font-size: var(--dv-font-size-sm); + color: var(--dv-text-muted); } .dv-error-message { - font-size: var(--dv-font-size); - max-width: 400px; + font-size: var(--dv-font-size); + max-width: 400px; } - /* ========================================================================== Table Renderer (CSV/TSV) ========================================================================== */ .dv-table-container { - overflow: auto; - flex: 1; - font-size: var(--dv-font-size-sm); + overflow: auto; + flex: 1; + font-size: var(--dv-font-size-sm); } .dv-table { - width: 100%; - border-collapse: collapse; - border-spacing: 0; - font-family: var(--dv-font-mono); - font-size: var(--dv-font-size-sm); + width: 100%; + border-collapse: collapse; + border-spacing: 0; + font-family: var(--dv-font-mono); + font-size: var(--dv-font-size-sm); } .dv-table thead { - position: sticky; - top: 0; - z-index: 5; + position: sticky; + top: 0; + z-index: 5; } .dv-table th { - background: var(--dv-table-header-bg); - border-bottom: 2px solid var(--dv-border); - padding: var(--dv-spacing-sm) var(--dv-spacing); - text-align: left; - font-weight: 600; - color: var(--dv-text); - white-space: nowrap; - cursor: default; - user-select: none; + background: var(--dv-table-header-bg); + border-bottom: 2px solid var(--dv-border); + padding: var(--dv-spacing-sm) var(--dv-spacing); + text-align: left; + font-weight: 600; + color: var(--dv-text); + white-space: nowrap; + cursor: default; + user-select: none; } -.dv-table th[data-sortable='true'] { - cursor: pointer; +.dv-table th[data-sortable="true"] { + cursor: pointer; } -.dv-table th[data-sortable='true']:hover { - background: var(--dv-surface-raised); +.dv-table th[data-sortable="true"]:hover { + background: var(--dv-surface-raised); } .dv-table td { - padding: var(--dv-spacing-xs) var(--dv-spacing); - border-bottom: 1px solid var(--dv-border-subtle); - color: var(--dv-text); - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + padding: var(--dv-spacing-xs) var(--dv-spacing); + border-bottom: 1px solid var(--dv-border-subtle); + color: var(--dv-text); + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .dv-table tbody tr:nth-child(even) { - background: var(--dv-table-stripe); + background: var(--dv-table-stripe); } .dv-table tbody tr:hover { - background: var(--dv-table-row-hover); + background: var(--dv-table-row-hover); } .dv-table-row-number { - color: var(--dv-text-muted); - min-width: 48px; - text-align: right; - padding-right: var(--dv-spacing) !important; - user-select: none; + color: var(--dv-text-muted); + min-width: 48px; + text-align: right; + padding-right: var(--dv-spacing) !important; + user-select: none; } - /* ========================================================================== Code Renderer ========================================================================== */ .dv-code-container { - display: flex; - flex: 1; - overflow: auto; - font-family: var(--dv-font-mono); - font-size: var(--dv-font-size); - line-height: 1.6; - background: var(--dv-code-bg); - tab-size: 2; + display: flex; + flex: 1; + overflow: auto; + font-family: var(--dv-font-mono); + font-size: var(--dv-font-size); + line-height: 1.6; + background: var(--dv-code-bg); + tab-size: 2; } .dv-code-gutter { - position: sticky; - left: 0; - display: flex; - flex-direction: column; - padding: var(--dv-spacing) 0; - text-align: right; - color: var(--dv-code-gutter); - background: var(--dv-code-gutter-bg); - user-select: none; - flex-shrink: 0; - border-right: 1px solid var(--dv-border-subtle); - z-index: 1; + position: sticky; + left: 0; + display: flex; + flex-direction: column; + padding: var(--dv-spacing) 0; + text-align: right; + color: var(--dv-code-gutter); + background: var(--dv-code-gutter-bg); + user-select: none; + flex-shrink: 0; + border-right: 1px solid var(--dv-border-subtle); + z-index: 1; } .dv-code-gutter-line { - padding: 0 var(--dv-spacing) 0 var(--dv-spacing-lg); - min-width: 48px; + padding: 0 var(--dv-spacing) 0 var(--dv-spacing-lg); + min-width: 48px; } .dv-code-body { - flex: 1; - padding: var(--dv-spacing); - overflow-x: auto; - white-space: pre; + flex: 1; + padding: var(--dv-spacing); + overflow-x: auto; + white-space: pre; } .dv-code-body.dv-word-wrap { - white-space: pre-wrap; - word-break: break-all; + white-space: pre-wrap; + word-break: break-all; } /* --- Built-in Syntax Highlighting (Dark) --- */ -.docview .dv-code-body .hljs-keyword, -.docview .dv-code-body .hljs-selector-tag, -.docview .dv-code-body .hljs-built_in, -.docview .dv-code-body .hljs-type { color: #ff7b72; } +.polyrender .dv-code-body .hljs-keyword, +.polyrender .dv-code-body .hljs-selector-tag, +.polyrender .dv-code-body .hljs-built_in, +.polyrender .dv-code-body .hljs-type { + color: #ff7b72; +} -.docview .dv-code-body .hljs-string, -.docview .dv-code-body .hljs-addition { color: #a5d6ff; } +.polyrender .dv-code-body .hljs-string, +.polyrender .dv-code-body .hljs-addition { + color: #a5d6ff; +} -.docview .dv-code-body .hljs-number, -.docview .dv-code-body .hljs-literal { color: #79c0ff; } +.polyrender .dv-code-body .hljs-number, +.polyrender .dv-code-body .hljs-literal { + color: #79c0ff; +} -.docview .dv-code-body .hljs-comment, -.docview .dv-code-body .hljs-quote { color: #8b949e; font-style: italic; } +.polyrender .dv-code-body .hljs-comment, +.polyrender .dv-code-body .hljs-quote { + color: #8b949e; + font-style: italic; +} -.docview .dv-code-body .hljs-function, -.docview .dv-code-body .hljs-title { color: #d2a8ff; } +.polyrender .dv-code-body .hljs-function, +.polyrender .dv-code-body .hljs-title { + color: #d2a8ff; +} -.docview .dv-code-body .hljs-title.function_ { color: #d2a8ff; } -.docview .dv-code-body .hljs-title.class_ { color: #f0883e; } +.polyrender .dv-code-body .hljs-title.function_ { + color: #d2a8ff; +} +.polyrender .dv-code-body .hljs-title.class_ { + color: #f0883e; +} -.docview .dv-code-body .hljs-variable, -.docview .dv-code-body .hljs-template-variable { color: #ffa657; } +.polyrender .dv-code-body .hljs-variable, +.polyrender .dv-code-body .hljs-template-variable { + color: #ffa657; +} -.docview .dv-code-body .hljs-attr, -.docview .dv-code-body .hljs-attribute { color: #79c0ff; } +.polyrender .dv-code-body .hljs-attr, +.polyrender .dv-code-body .hljs-attribute { + color: #79c0ff; +} -.docview .dv-code-body .hljs-tag { color: #7ee787; } +.polyrender .dv-code-body .hljs-tag { + color: #7ee787; +} -.docview .dv-code-body .hljs-name { color: #7ee787; } +.polyrender .dv-code-body .hljs-name { + color: #7ee787; +} -.docview .dv-code-body .hljs-regexp { color: #a5d6ff; } +.polyrender .dv-code-body .hljs-regexp { + color: #a5d6ff; +} -.docview .dv-code-body .hljs-symbol, -.docview .dv-code-body .hljs-bullet { color: #ffa657; } +.polyrender .dv-code-body .hljs-symbol, +.polyrender .dv-code-body .hljs-bullet { + color: #ffa657; +} -.docview .dv-code-body .hljs-meta, -.docview .dv-code-body .hljs-meta .hljs-keyword { color: #79c0ff; } +.polyrender .dv-code-body .hljs-meta, +.polyrender .dv-code-body .hljs-meta .hljs-keyword { + color: #79c0ff; +} -.docview .dv-code-body .hljs-deletion { color: #ffa198; background: rgba(255, 129, 130, 0.1); } -.docview .dv-code-body .hljs-addition { background: rgba(63, 185, 80, 0.1); } +.polyrender .dv-code-body .hljs-deletion { + color: #ffa198; + background: rgba(255, 129, 130, 0.1); +} +.polyrender .dv-code-body .hljs-addition { + background: rgba(63, 185, 80, 0.1); +} -.docview .dv-code-body .hljs-params { color: #e6edf3; } +.polyrender .dv-code-body .hljs-params { + color: #e6edf3; +} -.docview .dv-code-body .hljs-operator { color: #ff7b72; } +.polyrender .dv-code-body .hljs-operator { + color: #ff7b72; +} -.docview .dv-code-body .hljs-property { color: #79c0ff; } +.polyrender .dv-code-body .hljs-property { + color: #79c0ff; +} -.docview .dv-code-body .hljs-punctuation { color: #8b949e; } +.polyrender .dv-code-body .hljs-punctuation { + color: #8b949e; +} -.docview .dv-code-body .hljs-emphasis { font-style: italic; } -.docview .dv-code-body .hljs-strong { font-weight: bold; } +.polyrender .dv-code-body .hljs-emphasis { + font-style: italic; +} +.polyrender .dv-code-body .hljs-strong { + font-weight: bold; +} -.docview .dv-code-body .hljs-section { color: #79c0ff; font-weight: bold; } +.polyrender .dv-code-body .hljs-section { + color: #79c0ff; + font-weight: bold; +} -.docview .dv-code-body .hljs-link { color: #58a6ff; text-decoration: underline; } +.polyrender .dv-code-body .hljs-link { + color: #58a6ff; + text-decoration: underline; +} /* --- Syntax Highlighting (Light Override) --- */ -.docview[data-theme='light'] .dv-code-body .hljs-keyword, -.docview[data-theme='light'] .dv-code-body .hljs-selector-tag, -.docview[data-theme='light'] .dv-code-body .hljs-built_in, -.docview[data-theme='light'] .dv-code-body .hljs-type { color: #cf222e; } - -.docview[data-theme='light'] .dv-code-body .hljs-string, -.docview[data-theme='light'] .dv-code-body .hljs-addition { color: #0a3069; } +.polyrender[data-theme="light"] .dv-code-body .hljs-keyword, +.polyrender[data-theme="light"] .dv-code-body .hljs-selector-tag, +.polyrender[data-theme="light"] .dv-code-body .hljs-built_in, +.polyrender[data-theme="light"] .dv-code-body .hljs-type { + color: #cf222e; +} -.docview[data-theme='light'] .dv-code-body .hljs-number, -.docview[data-theme='light'] .dv-code-body .hljs-literal { color: #0550ae; } +.polyrender[data-theme="light"] .dv-code-body .hljs-string, +.polyrender[data-theme="light"] .dv-code-body .hljs-addition { + color: #0a3069; +} -.docview[data-theme='light'] .dv-code-body .hljs-comment, -.docview[data-theme='light'] .dv-code-body .hljs-quote { color: #6e7781; font-style: italic; } +.polyrender[data-theme="light"] .dv-code-body .hljs-number, +.polyrender[data-theme="light"] .dv-code-body .hljs-literal { + color: #0550ae; +} -.docview[data-theme='light'] .dv-code-body .hljs-function, -.docview[data-theme='light'] .dv-code-body .hljs-title { color: #8250df; } +.polyrender[data-theme="light"] .dv-code-body .hljs-comment, +.polyrender[data-theme="light"] .dv-code-body .hljs-quote { + color: #6e7781; + font-style: italic; +} -.docview[data-theme='light'] .dv-code-body .hljs-title.function_ { color: #8250df; } -.docview[data-theme='light'] .dv-code-body .hljs-title.class_ { color: #953800; } +.polyrender[data-theme="light"] .dv-code-body .hljs-function, +.polyrender[data-theme="light"] .dv-code-body .hljs-title { + color: #8250df; +} -.docview[data-theme='light'] .dv-code-body .hljs-variable, -.docview[data-theme='light'] .dv-code-body .hljs-template-variable { color: #953800; } +.polyrender[data-theme="light"] .dv-code-body .hljs-title.function_ { + color: #8250df; +} +.polyrender[data-theme="light"] .dv-code-body .hljs-title.class_ { + color: #953800; +} -.docview[data-theme='light'] .dv-code-body .hljs-attr, -.docview[data-theme='light'] .dv-code-body .hljs-attribute { color: #0550ae; } +.polyrender[data-theme="light"] .dv-code-body .hljs-variable, +.polyrender[data-theme="light"] .dv-code-body .hljs-template-variable { + color: #953800; +} -.docview[data-theme='light'] .dv-code-body .hljs-tag { color: #116329; } -.docview[data-theme='light'] .dv-code-body .hljs-name { color: #116329; } +.polyrender[data-theme="light"] .dv-code-body .hljs-attr, +.polyrender[data-theme="light"] .dv-code-body .hljs-attribute { + color: #0550ae; +} -.docview[data-theme='light'] .dv-code-body .hljs-regexp { color: #0a3069; } +.polyrender[data-theme="light"] .dv-code-body .hljs-tag { + color: #116329; +} +.polyrender[data-theme="light"] .dv-code-body .hljs-name { + color: #116329; +} -.docview[data-theme='light'] .dv-code-body .hljs-symbol, -.docview[data-theme='light'] .dv-code-body .hljs-bullet { color: #953800; } +.polyrender[data-theme="light"] .dv-code-body .hljs-regexp { + color: #0a3069; +} -.docview[data-theme='light'] .dv-code-body .hljs-meta, -.docview[data-theme='light'] .dv-code-body .hljs-meta .hljs-keyword { color: #0550ae; } +.polyrender[data-theme="light"] .dv-code-body .hljs-symbol, +.polyrender[data-theme="light"] .dv-code-body .hljs-bullet { + color: #953800; +} -.docview[data-theme='light'] .dv-code-body .hljs-deletion { color: #82071e; background: rgba(255, 129, 130, 0.1); } -.docview[data-theme='light'] .dv-code-body .hljs-addition { background: rgba(63, 185, 80, 0.1); } +.polyrender[data-theme="light"] .dv-code-body .hljs-meta, +.polyrender[data-theme="light"] .dv-code-body .hljs-meta .hljs-keyword { + color: #0550ae; +} -.docview[data-theme='light'] .dv-code-body .hljs-params { color: #1f2328; } -.docview[data-theme='light'] .dv-code-body .hljs-operator { color: #cf222e; } -.docview[data-theme='light'] .dv-code-body .hljs-property { color: #0550ae; } -.docview[data-theme='light'] .dv-code-body .hljs-punctuation { color: #6e7781; } -.docview[data-theme='light'] .dv-code-body .hljs-section { color: #0550ae; font-weight: bold; } -.docview[data-theme='light'] .dv-code-body .hljs-link { color: #0969da; text-decoration: underline; } +.polyrender[data-theme="light"] .dv-code-body .hljs-deletion { + color: #82071e; + background: rgba(255, 129, 130, 0.1); +} +.polyrender[data-theme="light"] .dv-code-body .hljs-addition { + background: rgba(63, 185, 80, 0.1); +} +.polyrender[data-theme="light"] .dv-code-body .hljs-params { + color: #1f2328; +} +.polyrender[data-theme="light"] .dv-code-body .hljs-operator { + color: #cf222e; +} +.polyrender[data-theme="light"] .dv-code-body .hljs-property { + color: #0550ae; +} +.polyrender[data-theme="light"] .dv-code-body .hljs-punctuation { + color: #6e7781; +} +.polyrender[data-theme="light"] .dv-code-body .hljs-section { + color: #0550ae; + font-weight: bold; +} +.polyrender[data-theme="light"] .dv-code-body .hljs-link { + color: #0969da; + text-decoration: underline; +} /* ========================================================================== Text Renderer ========================================================================== */ .dv-text-container { - flex: 1; - overflow: auto; - padding: var(--dv-spacing-lg); - font-family: var(--dv-font-sans); - font-size: var(--dv-font-size-lg); - line-height: 1.7; - color: var(--dv-text); - white-space: pre-wrap; - word-wrap: break-word; - max-width: 72ch; - margin: 0 auto; + flex: 1; + overflow: auto; + padding: var(--dv-spacing-lg); + font-family: var(--dv-font-sans); + font-size: var(--dv-font-size-lg); + line-height: 1.7; + color: var(--dv-text); + white-space: pre-wrap; + word-wrap: break-word; + max-width: 72ch; + margin: 0 auto; } .dv-text-container.dv-monospace { - font-family: var(--dv-font-mono); - font-size: var(--dv-font-size); - max-width: none; + font-family: var(--dv-font-mono); + font-size: var(--dv-font-size); + max-width: none; } - /* ========================================================================== EPUB Renderer ========================================================================== */ .dv-epub-container { - flex: 1; - overflow: hidden; - position: relative; + flex: 1; + overflow: hidden; + position: relative; } .dv-epub-container iframe { - border: none; - width: 100%; - height: 100%; + border: none; + width: 100%; + height: 100%; } - /* ========================================================================== DOCX Renderer ========================================================================== */ .dv-docx-container { - flex: 1; - overflow: auto; - padding: var(--dv-spacing-lg); - background: var(--dv-bg); + flex: 1; + overflow: auto; + padding: var(--dv-spacing-lg); + background: var(--dv-bg); } /* docx-preview renders into a wrapper div — scope some resets */ .dv-docx-container .docx-wrapper { - background: transparent !important; - padding: 0 !important; + background: transparent !important; + padding: 0 !important; } .dv-docx-container .docx-wrapper > section.docx { - background: var(--dv-page-bg) !important; - box-shadow: var(--dv-page-shadow) !important; - margin: 0 auto var(--dv-page-gap) !important; - border-radius: 2px; + background: var(--dv-page-bg) !important; + box-shadow: var(--dv-page-shadow) !important; + margin: 0 auto var(--dv-page-gap) !important; + border-radius: 2px; } - /* ========================================================================== Page Image Renderer (browse pages / pre-rendered) ========================================================================== */ .dv-browse-page { - position: relative; - display: flex; - align-items: center; - justify-content: center; + position: relative; + display: flex; + align-items: center; + justify-content: center; } .dv-browse-page img { - display: block; - max-width: 100%; - height: auto; - background: var(--dv-page-bg); - box-shadow: var(--dv-page-shadow); - border-radius: 2px; + display: block; + max-width: 100%; + height: auto; + background: var(--dv-page-bg); + box-shadow: var(--dv-page-shadow); + border-radius: 2px; } .dv-browse-page-placeholder { - display: flex; - align-items: center; - justify-content: center; - background: var(--dv-surface); - border-radius: 2px; - color: var(--dv-text-muted); - font-size: var(--dv-font-size-sm); + display: flex; + align-items: center; + justify-content: center; + background: var(--dv-surface); + border-radius: 2px; + color: var(--dv-text-muted); + font-size: var(--dv-font-size-sm); } diff --git a/packages/core/src/toolbar.ts b/packages/core/src/toolbar.ts index 7201d34..e16e799 100644 --- a/packages/core/src/toolbar.ts +++ b/packages/core/src/toolbar.ts @@ -1,4 +1,4 @@ -import type { ToolbarConfig, DocViewState } from './types.js' +import type { ToolbarConfig, PolyRenderState } from './types.js' import { el, svgIcon, icons } from './utils.js' export interface ToolbarActions { @@ -16,7 +16,7 @@ export interface ToolbarHandle { /** Root toolbar element. */ element: HTMLElement /** Update displayed state (page, total, zoom). */ - updateState(state: DocViewState): void + updateState(state: PolyRenderState): void /** Destroy and clean up listeners. */ destroy(): void } @@ -28,7 +28,7 @@ export interface ToolbarHandle { export function createToolbar( config: ToolbarConfig, actions: ToolbarActions, - initialState: DocViewState, + initialState: PolyRenderState, ): ToolbarHandle { const toolbar = el('div', 'dv-toolbar') if (config.position === 'bottom') { @@ -136,7 +136,7 @@ export function createToolbar( return { element: toolbar, - updateState(state: DocViewState) { + updateState(state: PolyRenderState) { if (pageInput) pageInput.value = String(state.currentPage) if (pageLabel) pageLabel.textContent = `/ ${state.totalPages}` if (zoomLabel) zoomLabel.textContent = `${Math.round(state.zoom * 100)}%` diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 239f8e6..30d0c1b 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,5 +1,5 @@ // --------------------------------------------------------------------------- -// Document Sources — what consumers pass in to tell DocView what to render +// Document Sources — what consumers pass in to tell PolyRender what to render // --------------------------------------------------------------------------- /** A direct file provided as binary data. */ @@ -151,10 +151,10 @@ export interface TextFetchAdapter { // --------------------------------------------------------------------------- -// Options — configuration for the DocView instance +// Options — configuration for the PolyRender instance // --------------------------------------------------------------------------- -export interface DocViewOptions { +export interface PolyRenderOptions { /** The document to render. */ source: DocumentSource /** Explicit format override. If omitted, auto-detected from source. */ @@ -180,7 +180,7 @@ export interface DocViewOptions { /** Called when the visible page changes. */ onPageChange?: (page: number, totalPages: number) => void /** Called on unrecoverable errors. */ - onError?: (error: DocViewError) => void + onError?: (error: PolyRenderError) => void /** Called when zoom level changes. */ onZoomChange?: (zoom: number) => void /** Called when loading state changes. */ @@ -290,12 +290,12 @@ export interface DocumentInfo { fileSize?: number } -/** Current viewer state, queryable from a DocView instance. */ -export interface DocViewState { +/** Current viewer state, queryable from a PolyRender instance. */ +export interface PolyRenderState { /** Whether the document is currently loading. */ loading: boolean /** Current error, if any. */ - error: DocViewError | null + error: PolyRenderError | null /** Current 1-indexed page number. */ currentPage: number /** Total page count. */ @@ -311,7 +311,7 @@ export interface DocViewState { // Errors // --------------------------------------------------------------------------- -export type DocViewErrorCode = +export type PolyRenderErrorCode = | 'FORMAT_UNSUPPORTED' | 'FORMAT_DETECTION_FAILED' | 'PEER_DEPENDENCY_MISSING' @@ -321,13 +321,13 @@ export type DocViewErrorCode = | 'PAGE_OUT_OF_RANGE' | 'UNKNOWN' -export class DocViewError extends Error { - code: DocViewErrorCode +export class PolyRenderError extends Error { + code: PolyRenderErrorCode detail?: unknown - constructor(code: DocViewErrorCode, message: string, detail?: unknown) { + constructor(code: PolyRenderErrorCode, message: string, detail?: unknown) { super(message) - this.name = 'DocViewError' + this.name = 'PolyRenderError' this.code = code this.detail = detail } @@ -363,7 +363,7 @@ export type DocumentFormat = /** * The contract every format renderer must fulfill. Each renderer manages - * its own DOM subtree within the provided container element. The DocView + * its own DOM subtree within the provided container element. The PolyRender * orchestrator calls these methods in response to user actions and source * changes. */ @@ -376,13 +376,13 @@ export interface Renderer { * Called once after the renderer is created. The container is an empty div * scoped to this renderer — the renderer owns its entire DOM subtree. */ - mount(container: HTMLElement, options: DocViewOptions): Promise + mount(container: HTMLElement, options: PolyRenderOptions): Promise /** * React to changed options (theme, zoom, etc.) without full re-mount. * Only the changed fields will be present. */ - update(changed: Partial): Promise + update(changed: Partial): Promise /** Navigate to a specific page (1-indexed). */ goToPage(page: number): void @@ -411,7 +411,7 @@ export interface Renderer { /** * Factory function that creates a renderer instance. Registered in the - * format registry so DocView can instantiate the right renderer for each + * format registry so PolyRender can instantiate the right renderer for each * document format. */ export type RendererFactory = () => Renderer @@ -421,13 +421,13 @@ export type RendererFactory = () => Renderer // Events (for vanilla JS event-driven usage) // --------------------------------------------------------------------------- -export interface DocViewEventMap { +export interface PolyRenderEventMap { ready: DocumentInfo pagechange: { page: number; totalPages: number } zoomchange: { zoom: number } - error: DocViewError + error: PolyRenderError loadingchange: { loading: boolean } destroy: void } -export type DocViewEventType = keyof DocViewEventMap +export type PolyRenderEventType = keyof PolyRenderEventMap diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index c546b68..0471a96 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -1,5 +1,5 @@ -import type { DocumentSource, DocumentFormat, DocViewError } from './types.js' -import { DocViewError as DVError } from './types.js' +import type { DocumentSource, DocumentFormat, PolyRenderError } from './types.js' +import { PolyRenderError as DVError } from './types.js' // --------------------------------------------------------------------------- // DOM Helpers diff --git a/packages/react/package.json b/packages/react/package.json index 7ee4e98..beb92ce 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,7 +1,7 @@ { - "name": "@docview/react", + "name": "@polyrender/react", "version": "0.1.0", - "description": "React components for the DocView universal document renderer", + "description": "React components for the PolyRender universal document renderer", "license": "Zlib", "type": "module", "main": "./dist/index.cjs", @@ -26,7 +26,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@docview/core": "workspace:*" + "@polyrender/core": "workspace:*" }, "peerDependencies": { "react": ">=18.0.0", diff --git a/packages/react/src/DocumentViewer.tsx b/packages/react/src/DocumentViewer.tsx index ba10b50..ba80eed 100644 --- a/packages/react/src/DocumentViewer.tsx +++ b/packages/react/src/DocumentViewer.tsx @@ -3,13 +3,13 @@ import type { DocumentSource, DocumentFormat, DocumentInfo, - DocViewError, + PolyRenderError, ToolbarConfig, PdfOptions, CodeOptions, CsvOptions, EpubOptions, -} from '@docview/core' +} from '@polyrender/core' import { useDocumentRenderer } from './useDocumentRenderer.js' export interface DocumentViewerProps { @@ -44,7 +44,7 @@ export interface DocumentViewerProps { onReady?: (info: DocumentInfo) => void onPageChange?: (page: number, totalPages: number) => void onZoomChange?: (zoom: number) => void - onError?: (error: DocViewError) => void + onError?: (error: PolyRenderError) => void onLoadingChange?: (loading: boolean) => void // --- Format-specific options --- @@ -70,13 +70,13 @@ export interface DocumentViewerRef { /** * React component for rendering documents of any supported format. * - * Wraps the framework-agnostic `@docview/core` library with React lifecycle + * Wraps the framework-agnostic `@polyrender/core` library with React lifecycle * management, ref-based imperative API, and automatic cleanup. * * @example * ```tsx - * import { DocumentViewer } from '@docview/react' - * import '@docview/core/styles.css' + * import { DocumentViewer } from '@polyrender/react' + * import '@polyrender/core/styles.css' * * function App() { * return ( @@ -184,7 +184,7 @@ export const DocumentViewer = forwardRef
) }, diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index ccb7388..9cafac9 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -7,7 +7,7 @@ export { useDocumentRenderer } from './useDocumentRenderer.js' export type { UseDocumentRendererOptions, UseDocumentRendererReturn } from './useDocumentRenderer.js' // Re-export core types for convenience (so consumers don't need to -// install @docview/core separately just for types) +// install @polyrender/core separately just for types) export type { DocumentSource, FileSource, @@ -21,18 +21,18 @@ export type { PageFetchAdapter, ChunkFetchAdapter, TextFetchAdapter, - DocViewOptions, + PolyRenderOptions, PdfOptions, CodeOptions, CsvOptions, EpubOptions, ToolbarConfig, DocumentInfo, - DocViewState, + PolyRenderState, DocumentFormat, - DocViewEventMap, - DocViewEventType, - DocViewErrorCode, -} from '@docview/core' + PolyRenderEventMap, + PolyRenderEventType, + PolyRenderErrorCode, +} from '@polyrender/core' -export { DocViewError, DocView } from '@docview/core' +export { PolyRenderError, PolyRender } from '@polyrender/core' diff --git a/packages/react/src/useDocumentRenderer.ts b/packages/react/src/useDocumentRenderer.ts index d3f0e26..3af5d24 100644 --- a/packages/react/src/useDocumentRenderer.ts +++ b/packages/react/src/useDocumentRenderer.ts @@ -1,20 +1,20 @@ import { useRef, useEffect, useState, useCallback } from 'react' import type { - DocViewOptions, - DocViewState, + PolyRenderOptions, + PolyRenderState, DocumentInfo, - DocViewError, + PolyRenderError, DocumentSource, -} from '@docview/core' -import { DocView } from '@docview/core' +} from '@polyrender/core' +import { PolyRender } from '@polyrender/core' export interface UseDocumentRendererOptions - extends Omit { + extends Omit { source: DocumentSource | null | undefined onReady?: (info: DocumentInfo) => void onPageChange?: (page: number, totalPages: number) => void onZoomChange?: (zoom: number) => void - onError?: (error: DocViewError) => void + onError?: (error: PolyRenderError) => void onLoadingChange?: (loading: boolean) => void } @@ -22,7 +22,7 @@ export interface UseDocumentRendererReturn { /** Ref to attach to the container div. */ containerRef: React.RefObject /** Current viewer state. */ - state: DocViewState + state: PolyRenderState /** Navigate to a page. */ goToPage: (page: number) => void /** Set zoom level. */ @@ -30,13 +30,13 @@ export interface UseDocumentRendererReturn { /** Whether the viewer is mounted and ready. */ ready: boolean /** Current error, if any. */ - error: DocViewError | null + error: PolyRenderError | null } /** - * React hook for the DocView document renderer. + * React hook for the PolyRender document renderer. * - * Manages the lifecycle of a DocView instance, bridging its imperative API + * Manages the lifecycle of a PolyRender instance, bridging its imperative API * to React's declarative model. Handles mounting, updating, and cleanup. * * @example @@ -60,10 +60,10 @@ export function useDocumentRenderer( options: UseDocumentRendererOptions, ): UseDocumentRendererReturn { const containerRef = useRef(null) - const instanceRef = useRef(null) + const instanceRef = useRef(null) const optionsRef = useRef(options) - const [state, setState] = useState({ + const [state, setState] = useState({ loading: true, error: null, currentPage: 1, @@ -73,7 +73,7 @@ export function useDocumentRenderer( }) const [ready, setReady] = useState(false) - const [error, setError] = useState(null) + const [error, setError] = useState(null) // Keep options ref current optionsRef.current = options @@ -93,7 +93,7 @@ export function useDocumentRenderer( setError(null) setState((s) => ({ ...s, loading: true, error: null })) - const instance = new DocView(container, { + const instance = new PolyRender(container, { ...options, source: options.source, onReady: (info) => { @@ -139,7 +139,7 @@ export function useDocumentRenderer( useEffect(() => { if (!instanceRef.current) return - const changed: Partial = {} + const changed: Partial = {} if (options.theme) changed.theme = options.theme if (options.className !== undefined) changed.className = options.className if (options.zoom !== undefined) changed.zoom = options.zoom diff --git a/packages/react/tsup.config.ts b/packages/react/tsup.config.ts index 9039613..8bf3688 100644 --- a/packages/react/tsup.config.ts +++ b/packages/react/tsup.config.ts @@ -6,7 +6,7 @@ export default defineConfig({ dts: true, sourcemap: true, clean: true, - external: ['react', 'react-dom', '@docview/core'], + external: ['react', 'react-dom', '@polyrender/core'], esbuildOptions(options) { options.jsx = 'automatic' }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7be79df..d053099 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,16 +8,13 @@ importers: .: devDependencies: - pdfjs-dist: - specifier: ^5.5.207 - version: 5.5.207 typescript: specifier: ^5.5.0 version: 5.9.3 examples/basic: dependencies: - '@docview/core': + '@polyrender/core': specifier: workspace:* version: link:../../packages/core docx-preview: @@ -51,7 +48,7 @@ importers: examples/vanilla: dependencies: - '@docview/core': + '@polyrender/core': specifier: workspace:* version: link:../../packages/core docx-preview: @@ -116,7 +113,7 @@ importers: packages/react: dependencies: - '@docview/core': + '@polyrender/core': specifier: workspace:* version: link:../core devDependencies: -- 2.51.2