@uurtech/jdf 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/jdfjs.css ADDED
@@ -0,0 +1,225 @@
1
+ /* jdfjs — embed styles */
2
+ .jdfjs {
3
+ --jdfjs-bg: #f1f5f9;
4
+ --jdfjs-page-bg: #ffffff;
5
+ --jdfjs-text: #0f172a;
6
+ --jdfjs-text-soft: #475569;
7
+ --jdfjs-muted: #94a3b8;
8
+ --jdfjs-border: #e2e8f0;
9
+ --jdfjs-brand: #2563eb;
10
+ --jdfjs-brand-soft: rgba(37, 99, 235, 0.08);
11
+ --jdfjs-shadow: 0 1px 3px rgba(15, 23, 42, 0.08), 0 4px 16px rgba(15, 23, 42, 0.06);
12
+
13
+ display: block;
14
+ position: relative;
15
+ width: 100%;
16
+ height: 100%;
17
+ min-height: 500px;
18
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
19
+ font-size: 14px;
20
+ line-height: 1.5;
21
+ color: var(--jdfjs-text);
22
+ background: var(--jdfjs-bg);
23
+ border: 1px solid var(--jdfjs-border);
24
+ border-radius: 8px;
25
+ overflow: hidden;
26
+ box-sizing: border-box;
27
+ -webkit-font-smoothing: antialiased;
28
+ -moz-osx-font-smoothing: grayscale;
29
+ }
30
+
31
+ .jdfjs.jdfjs-dark {
32
+ --jdfjs-bg: #0f172a;
33
+ --jdfjs-page-bg: #ffffff;
34
+ --jdfjs-text: #f1f5f9;
35
+ --jdfjs-text-soft: #cbd5e1;
36
+ --jdfjs-muted: #64748b;
37
+ --jdfjs-border: #334155;
38
+ --jdfjs-brand: #60a5fa;
39
+ --jdfjs-brand-soft: rgba(96, 165, 250, 0.12);
40
+ }
41
+
42
+ .jdfjs *, .jdfjs *::before, .jdfjs *::after { box-sizing: border-box; }
43
+
44
+ .jdfjs-root {
45
+ display: flex;
46
+ flex-direction: column;
47
+ width: 100%;
48
+ height: 100%;
49
+ min-height: inherit;
50
+ }
51
+
52
+ .jdfjs-toolbar {
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 6px;
56
+ padding: 8px 14px;
57
+ background: var(--jdfjs-page-bg);
58
+ border-bottom: 1px solid var(--jdfjs-border);
59
+ font-size: 13px;
60
+ flex-shrink: 0;
61
+ }
62
+
63
+ .jdfjs.jdfjs-dark .jdfjs-toolbar { background: #1e293b; }
64
+
65
+ .jdfjs-title {
66
+ font-weight: 600;
67
+ color: var(--jdfjs-text);
68
+ white-space: nowrap;
69
+ overflow: hidden;
70
+ text-overflow: ellipsis;
71
+ max-width: 240px;
72
+ }
73
+
74
+ .jdfjs-spacer { flex: 1; }
75
+
76
+ .jdfjs-btn {
77
+ display: inline-flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ width: 28px;
81
+ height: 28px;
82
+ background: transparent;
83
+ border: 1px solid transparent;
84
+ color: var(--jdfjs-text-soft);
85
+ border-radius: 6px;
86
+ cursor: pointer;
87
+ font-size: 14px;
88
+ font-family: inherit;
89
+ transition: background 0.12s ease, color 0.12s ease;
90
+ padding: 0;
91
+ }
92
+
93
+ .jdfjs-btn:hover {
94
+ background: var(--jdfjs-brand-soft);
95
+ color: var(--jdfjs-brand);
96
+ }
97
+
98
+ .jdfjs-btn:disabled { opacity: 0.3; cursor: not-allowed; }
99
+
100
+ .jdfjs-page-indicator,
101
+ .jdfjs-zoom-indicator {
102
+ font-family: "JetBrains Mono", ui-monospace, monospace;
103
+ font-size: 11px;
104
+ color: var(--jdfjs-text-soft);
105
+ min-width: 60px;
106
+ text-align: center;
107
+ }
108
+
109
+ .jdfjs-divider {
110
+ width: 1px;
111
+ height: 18px;
112
+ background: var(--jdfjs-border);
113
+ margin: 0 4px;
114
+ }
115
+
116
+ .jdfjs-body {
117
+ display: flex;
118
+ flex: 1;
119
+ min-height: 0;
120
+ }
121
+
122
+ .jdfjs-sidebar {
123
+ width: 140px;
124
+ background: var(--jdfjs-bg);
125
+ border-right: 1px solid var(--jdfjs-border);
126
+ overflow-y: auto;
127
+ padding: 10px;
128
+ display: flex;
129
+ flex-direction: column;
130
+ gap: 8px;
131
+ flex-shrink: 0;
132
+ }
133
+
134
+ .jdfjs-sidebar-thumb {
135
+ background: var(--jdfjs-page-bg);
136
+ border: 2px solid var(--jdfjs-border);
137
+ border-radius: 6px;
138
+ padding: 18px 0;
139
+ cursor: pointer;
140
+ font-family: "JetBrains Mono", monospace;
141
+ font-size: 11px;
142
+ color: var(--jdfjs-muted);
143
+ aspect-ratio: 210 / 297;
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ transition: border-color 0.15s ease;
148
+ }
149
+
150
+ .jdfjs-sidebar-thumb:hover { border-color: var(--jdfjs-text-soft); }
151
+ .jdfjs-sidebar-thumb-active {
152
+ border-color: var(--jdfjs-brand);
153
+ box-shadow: 0 0 0 2px var(--jdfjs-brand-soft);
154
+ }
155
+
156
+ .jdfjs-pages {
157
+ flex: 1;
158
+ overflow: auto;
159
+ padding: 24px 16px;
160
+ display: flex;
161
+ flex-direction: column;
162
+ align-items: center;
163
+ gap: 16px;
164
+ }
165
+
166
+ .jdfjs-page-wrapper {
167
+ display: block;
168
+ scroll-margin-top: 16px;
169
+ }
170
+
171
+ .jdfjs-page {
172
+ position: relative;
173
+ background: var(--jdfjs-page-bg);
174
+ border-radius: 2px;
175
+ box-shadow: var(--jdfjs-shadow);
176
+ overflow: visible;
177
+ }
178
+
179
+ .jdfjs-header,
180
+ .jdfjs-footer {
181
+ position: absolute;
182
+ left: 0;
183
+ right: 0;
184
+ font-size: 11px;
185
+ color: var(--jdfjs-muted);
186
+ }
187
+
188
+ .jdfjs-header { top: 0; }
189
+ .jdfjs-footer { bottom: 0; }
190
+
191
+ .jdfjs-link {
192
+ color: var(--jdfjs-brand);
193
+ text-decoration: underline;
194
+ text-underline-offset: 2px;
195
+ }
196
+
197
+ .jdfjs-link:hover { color: var(--jdfjs-brand); opacity: 0.85; }
198
+
199
+ .jdfjs-loading,
200
+ .jdfjs-error {
201
+ display: flex;
202
+ align-items: center;
203
+ justify-content: center;
204
+ min-height: 240px;
205
+ color: var(--jdfjs-text-soft);
206
+ font-family: "Inter", -apple-system, sans-serif;
207
+ }
208
+
209
+ .jdfjs-error-msg {
210
+ padding: 20px 32px;
211
+ border-radius: 8px;
212
+ background: #fef2f2;
213
+ color: #b91c1c;
214
+ border: 1px solid #fecaca;
215
+ font-size: 13px;
216
+ max-width: 80%;
217
+ text-align: center;
218
+ }
219
+
220
+ /* === <jdf> visual defaults === */
221
+ jdf {
222
+ display: block;
223
+ width: 100%;
224
+ min-height: 600px;
225
+ }
@@ -0,0 +1,113 @@
1
+ import { JdfDocument } from '@jdf/core';
2
+ export { Element, JdfDocument, Page } from '@jdf/core';
3
+
4
+ /**
5
+ * Auto-init: scan the page for <jdf src="..."> tags and render each one.
6
+ *
7
+ * <link rel="stylesheet" href="https://unpkg.com/jdfjs/dist/jdfjs.css">
8
+ * <script type="module" src="https://unpkg.com/jdfjs"></script>
9
+ * <jdf src="/whitepaper.jdf"></jdf>
10
+ *
11
+ * Configuration via attributes on <jdf>:
12
+ * <jdf src="..." width="800" height="600" zoom="1.2" sidebar="true" dark-mode="auto"></jdf>
13
+ *
14
+ * For SPAs that mount content async, call `jdf()` after the new DOM lands.
15
+ * To opt out per-element add `manual`. To disable globally:
16
+ * window.JDFjsAutoInit = false // before loading the script
17
+ */
18
+ /** Manually trigger a scan — for SPAs that mount content asynchronously. */
19
+ declare function jdf(root?: ParentNode): void;
20
+
21
+ interface JDFViewerOptions {
22
+ /** Initial zoom level. 1 = 100%. Default: 1 */
23
+ zoom?: number;
24
+ /** Show the page sidebar. Default: false (most embeds want a clean look) */
25
+ sidebar?: boolean;
26
+ /** Show the toolbar (zoom, page nav, search). Default: true */
27
+ toolbar?: boolean;
28
+ /** Use dark mode. Default: follows `prefers-color-scheme` */
29
+ darkMode?: "auto" | "light" | "dark";
30
+ /** Initial page index (0-based). Default: 0 */
31
+ initialPage?: number;
32
+ /** Container width. Number = pixels. String = any CSS length ("100%", "60ch", "640px"). */
33
+ width?: number | string;
34
+ /** Container height. Number = pixels. String = any CSS length ("80vh", "600px"). Default: "600px". */
35
+ height?: number | string;
36
+ /** Page-fit strategy:
37
+ * "manual" — exact zoom from `zoom` option (default)
38
+ * "fit-width" — auto-zoom each page to fill container width
39
+ * "fit-page" — auto-zoom so a whole page is visible
40
+ */
41
+ fit?: "manual" | "fit-width" | "fit-page";
42
+ /** Called when the user navigates to a different page */
43
+ onPageChange?: (pageIndex: number) => void;
44
+ /** Called once the document finishes rendering */
45
+ onLoad?: (doc: JdfDocument) => void;
46
+ /** Called on any rendering error */
47
+ onError?: (err: Error) => void;
48
+ }
49
+ interface JDFViewerInstance {
50
+ /** The container element */
51
+ container: HTMLElement;
52
+ /** The current document */
53
+ document: JdfDocument;
54
+ /** Get / set zoom (1 = 100%) */
55
+ setZoom: (zoom: number) => void;
56
+ getZoom: () => number;
57
+ /** Navigate to a page (0-based) */
58
+ goToPage: (pageIndex: number) => void;
59
+ getCurrentPage: () => number;
60
+ /** Replace the document */
61
+ setDocument: (doc: JdfDocument) => void;
62
+ /** Tear down — removes DOM and event listeners */
63
+ destroy: () => void;
64
+ }
65
+ /**
66
+ * Embed a JDF document into a container by URL.
67
+ * The simplest "PDF.js-like" usage.
68
+ */
69
+ declare function embed(container: HTMLElement | string, url: string, options?: JDFViewerOptions): Promise<JDFViewerInstance>;
70
+ /**
71
+ * Render a JDF document directly into a container. No fetch.
72
+ */
73
+ declare function render(container: HTMLElement | string, document: JdfDocument, options?: JDFViewerOptions): JDFViewerInstance;
74
+ /**
75
+ * Class form for advanced consumers (event subscriptions, custom toolbar wiring, etc).
76
+ */
77
+ declare class JDFViewer {
78
+ private container;
79
+ private doc;
80
+ private options;
81
+ private zoom;
82
+ private currentPage;
83
+ private resizeObs;
84
+ private pagesEl;
85
+ private toolbarEl;
86
+ private sidebarEl;
87
+ private root;
88
+ private observer;
89
+ constructor(container: HTMLElement, doc: JdfDocument, options?: JDFViewerOptions);
90
+ private applyContainerSize;
91
+ private mount;
92
+ private setupResizeObserver;
93
+ /** Auto-zoom for fit modes. */
94
+ private applyFit;
95
+ private buildToolbar;
96
+ private buildSidebar;
97
+ private updateIndicators;
98
+ private renderAllPages;
99
+ private renderPage;
100
+ private renderHeaderFooter;
101
+ private setupScrollObserver;
102
+ private scrollToPage;
103
+ private applyZoom;
104
+ setZoom(z: number): void;
105
+ getZoom(): number;
106
+ goToPage(idx: number): void;
107
+ getCurrentPage(): number;
108
+ setDocument(doc: JdfDocument): void;
109
+ destroy(): void;
110
+ getInstance(): JDFViewerInstance;
111
+ }
112
+
113
+ export { JDFViewer, type JDFViewerInstance, type JDFViewerOptions, embed, jdf, render };
@@ -0,0 +1,113 @@
1
+ import { JdfDocument } from '@jdf/core';
2
+ export { Element, JdfDocument, Page } from '@jdf/core';
3
+
4
+ /**
5
+ * Auto-init: scan the page for <jdf src="..."> tags and render each one.
6
+ *
7
+ * <link rel="stylesheet" href="https://unpkg.com/jdfjs/dist/jdfjs.css">
8
+ * <script type="module" src="https://unpkg.com/jdfjs"></script>
9
+ * <jdf src="/whitepaper.jdf"></jdf>
10
+ *
11
+ * Configuration via attributes on <jdf>:
12
+ * <jdf src="..." width="800" height="600" zoom="1.2" sidebar="true" dark-mode="auto"></jdf>
13
+ *
14
+ * For SPAs that mount content async, call `jdf()` after the new DOM lands.
15
+ * To opt out per-element add `manual`. To disable globally:
16
+ * window.JDFjsAutoInit = false // before loading the script
17
+ */
18
+ /** Manually trigger a scan — for SPAs that mount content asynchronously. */
19
+ declare function jdf(root?: ParentNode): void;
20
+
21
+ interface JDFViewerOptions {
22
+ /** Initial zoom level. 1 = 100%. Default: 1 */
23
+ zoom?: number;
24
+ /** Show the page sidebar. Default: false (most embeds want a clean look) */
25
+ sidebar?: boolean;
26
+ /** Show the toolbar (zoom, page nav, search). Default: true */
27
+ toolbar?: boolean;
28
+ /** Use dark mode. Default: follows `prefers-color-scheme` */
29
+ darkMode?: "auto" | "light" | "dark";
30
+ /** Initial page index (0-based). Default: 0 */
31
+ initialPage?: number;
32
+ /** Container width. Number = pixels. String = any CSS length ("100%", "60ch", "640px"). */
33
+ width?: number | string;
34
+ /** Container height. Number = pixels. String = any CSS length ("80vh", "600px"). Default: "600px". */
35
+ height?: number | string;
36
+ /** Page-fit strategy:
37
+ * "manual" — exact zoom from `zoom` option (default)
38
+ * "fit-width" — auto-zoom each page to fill container width
39
+ * "fit-page" — auto-zoom so a whole page is visible
40
+ */
41
+ fit?: "manual" | "fit-width" | "fit-page";
42
+ /** Called when the user navigates to a different page */
43
+ onPageChange?: (pageIndex: number) => void;
44
+ /** Called once the document finishes rendering */
45
+ onLoad?: (doc: JdfDocument) => void;
46
+ /** Called on any rendering error */
47
+ onError?: (err: Error) => void;
48
+ }
49
+ interface JDFViewerInstance {
50
+ /** The container element */
51
+ container: HTMLElement;
52
+ /** The current document */
53
+ document: JdfDocument;
54
+ /** Get / set zoom (1 = 100%) */
55
+ setZoom: (zoom: number) => void;
56
+ getZoom: () => number;
57
+ /** Navigate to a page (0-based) */
58
+ goToPage: (pageIndex: number) => void;
59
+ getCurrentPage: () => number;
60
+ /** Replace the document */
61
+ setDocument: (doc: JdfDocument) => void;
62
+ /** Tear down — removes DOM and event listeners */
63
+ destroy: () => void;
64
+ }
65
+ /**
66
+ * Embed a JDF document into a container by URL.
67
+ * The simplest "PDF.js-like" usage.
68
+ */
69
+ declare function embed(container: HTMLElement | string, url: string, options?: JDFViewerOptions): Promise<JDFViewerInstance>;
70
+ /**
71
+ * Render a JDF document directly into a container. No fetch.
72
+ */
73
+ declare function render(container: HTMLElement | string, document: JdfDocument, options?: JDFViewerOptions): JDFViewerInstance;
74
+ /**
75
+ * Class form for advanced consumers (event subscriptions, custom toolbar wiring, etc).
76
+ */
77
+ declare class JDFViewer {
78
+ private container;
79
+ private doc;
80
+ private options;
81
+ private zoom;
82
+ private currentPage;
83
+ private resizeObs;
84
+ private pagesEl;
85
+ private toolbarEl;
86
+ private sidebarEl;
87
+ private root;
88
+ private observer;
89
+ constructor(container: HTMLElement, doc: JdfDocument, options?: JDFViewerOptions);
90
+ private applyContainerSize;
91
+ private mount;
92
+ private setupResizeObserver;
93
+ /** Auto-zoom for fit modes. */
94
+ private applyFit;
95
+ private buildToolbar;
96
+ private buildSidebar;
97
+ private updateIndicators;
98
+ private renderAllPages;
99
+ private renderPage;
100
+ private renderHeaderFooter;
101
+ private setupScrollObserver;
102
+ private scrollToPage;
103
+ private applyZoom;
104
+ setZoom(z: number): void;
105
+ getZoom(): number;
106
+ goToPage(idx: number): void;
107
+ getCurrentPage(): number;
108
+ setDocument(doc: JdfDocument): void;
109
+ destroy(): void;
110
+ getInstance(): JDFViewerInstance;
111
+ }
112
+
113
+ export { JDFViewer, type JDFViewerInstance, type JDFViewerOptions, embed, jdf, render };
package/dist/jdfjs.js ADDED
@@ -0,0 +1,12 @@
1
+ var J={A3:{width:297,height:420},A4:{width:210,height:297},A5:{width:148,height:210},Letter:{width:215.9,height:279.4},Legal:{width:215.9,height:355.6},Tabloid:{width:279.4,height:431.8}},N={top:25,right:20,bottom:25,left:20},A=3.7795275591,Z=96,V=1.3333333333;function f(e,t="mm"){switch(t){case "mm":return e*A;case "in":return e*Z;case "pt":return e*V;case "px":return e;default:return e*A}}function R(e,t="portrait"){let n=typeof e=="string"?J[e]:e;return t==="landscape"?{width:n.height,height:n.width}:{...n}}function H(e){if(typeof e=="number")return `${e}px`;if(typeof e=="string")return e;let{top:t=0,right:n=0,bottom:r=0,left:o=0}=e;return `${t}px ${n}px ${r}px ${o}px`}function m(e){let t={};if(e.fontFamily&&(t["font-family"]=e.fontFamily),e.fontSize&&(t["font-size"]=`${e.fontSize*1.333}px`),e.fontWeight&&(t["font-weight"]=String(e.fontWeight)),e.fontStyle&&(t["font-style"]=e.fontStyle),e.color&&(t.color=e.color),e.backgroundColor&&(t["background-color"]=e.backgroundColor),e.textAlign&&(t["text-align"]=e.textAlign),e.textDecoration){let n=e.textDecoration==="strikethrough"?"line-through":e.textDecoration;t["text-decoration"]=n;}return e.lineHeight&&(t["line-height"]=String(e.lineHeight)),e.letterSpacing!=null&&(t["letter-spacing"]=typeof e.letterSpacing=="number"?`${e.letterSpacing}px`:e.letterSpacing),e.padding!=null&&(t.padding=H(e.padding)),e.margin!=null&&(t.margin=H(e.margin)),e.marginTop!=null&&(t["margin-top"]=`${e.marginTop}px`),e.marginBottom!=null&&(t["margin-bottom"]=`${e.marginBottom}px`),e.border&&(t.border=e.border),e.borderRadius!=null&&(t["border-radius"]=typeof e.borderRadius=="number"?`${e.borderRadius}px`:e.borderRadius),e.opacity!=null&&(t.opacity=String(e.opacity)),t}function E(e,t){if(!e)return {};if(typeof e=="string")return m(t[e]||{});if(Array.isArray(e)){let n={};for(let r of e)n={...n,...t[r]||{}};return m(n)}return m(e)}function b(e,t){for(let[n,r]of Object.entries(t))e.style.setProperty(n,r);}function L(e){if(!e)return null;let t=typeof e=="string"?e:e.target;if(typeof e=="string"?e.startsWith("#"):e.type==="internal"){let r=t.replace(/^#/,"").match(/^page-(\d+)$/i);return r?{href:t,internal:true,pageIndex:Number(r[1])-1}:{href:t,internal:true}}return {href:t,internal:false}}function k(e,t,n){e.href=t.href,t.internal?e.addEventListener("click",r=>{t.pageIndex!=null&&n&&(r.preventDefault(),n(t.pageIndex));}):(e.target="_blank",e.rel="noopener noreferrer");}var j="http://www.w3.org/2000/svg";function w(e,t){let n=document.createElement("div");_(n,e);let r=null;switch(e.type){case "text":r=q(e,t);break;case "richtext":r=B(e,t);break;case "image":r=Q(e,t);break;case "table":r=te(e,t);break;case "list":r=ne(e,t);break;case "shape":r=re(e);break;case "collapsible":r=oe(e,t);break;case "toc":r=ie(e,t);break}return r?(n.appendChild(r),n):null}function _(e,t){let n=t;n.position&&(e.style.position="absolute",n.position.x!=null&&(e.style.left=`${f(n.position.x)}px`),n.position.y!=null&&(e.style.top=`${f(n.position.y)}px`)),n.width!=null&&(e.style.width=`${f(n.width)}px`),n.height!=null&&(e.style.height=`${f(n.height)}px`);}function q(e,t){let n=W(e.heading),r=document.createElement(n);r.className="jdfjs-text",r.style.margin="0",r.style.whiteSpace="pre-wrap",b(r,E(e.style,t.styles)),e.align&&(r.style.textAlign=e.align);let o=L(e.link);if(o){let s=document.createElement("a");k(s,o,t.onNavigatePage),s.className="jdfjs-link",s.textContent=e.content||"",r.appendChild(s);}else r.textContent=e.content||"";return r}function W(e){return e===true?"h1":typeof e=="number"&&e>=1&&e<=6?"h"+e:"p"}function B(e,t){let n=document.createElement("p");n.className="jdfjs-richtext",n.style.margin="0",b(n,E(e.style,t.styles));for(let r of e.runs||[])n.appendChild(X(r,t));return n}function G(e,t){let n={};if(e.style)if(typeof e.style=="string")Object.assign(n,m(t[e.style]||{}));else if(Array.isArray(e.style))for(let o of e.style)Object.assign(n,m(t[o]||{}));else Object.assign(n,m(e.style));e.bold&&(n["font-weight"]="bold"),e.italic&&(n["font-style"]="italic");let r=[];return e.underline&&r.push("underline"),e.strikethrough&&r.push("line-through"),r.length&&(n["text-decoration"]=r.join(" ")),e.color&&(n.color=e.color),e.fontSize&&(n["font-size"]=`${e.fontSize*1.333}px`),e.fontFamily&&(n["font-family"]=e.fontFamily),n}function X(e,t){let n=L(e.link),r=n?document.createElement("a"):document.createElement("span");return n&&(k(r,n,t.onNavigatePage),r.classList.add("jdfjs-link")),b(r,G(e,t.styles)),r.textContent=e.text,r}function U(e,t){if(!e)return;let n=e[t];if(n&&typeof n=="object"&&"data"in n)return n;let r=e.images?.[t];if(r)return r}function K(e,t){if(e.src?.startsWith("data:")||e.src?.startsWith("http"))return e.src;if(e.resource){let n=U(t,e.resource);if(n?.data){let r=n.mimeType||"image/png";return n.data.startsWith("data:")?n.data:`data:${r};base64,${n.data}`}if(n?.path)return n.path}return e.src||""}function Q(e,t){let n=document.createElement("div");n.className="jdfjs-image",n.style.width="100%",n.style.height="100%";let r=document.createElement("img");switch(r.src=K(e,t.resources),r.alt=e.alt||"",r.style.display="block",r.style.width="100%",r.style.height="100%",e.fit){case "cover":r.style.objectFit="cover";break;case "fill":r.style.objectFit="fill";break;case "none":r.style.objectFit="none";break;default:r.style.objectFit="contain";}return b(r,E(e.style,t.styles)),n.appendChild(r),n}function Y(e){return typeof e=="string"?e:e.content}function ee(e){return typeof e=="string"?{}:{colspan:e.colspan,rowspan:e.rowspan}}function te(e,t){let n=document.createElement("div");n.className="jdfjs-table-wrap",b(n,E(e.style,t.styles)),n.style.overflowX="auto";let r=(()=>{let l=e.headerStyle;if(!l)return {};if(typeof l=="string")return m(t.styles[l]||{});if(Array.isArray(l)){let p={};for(let u of l)p={...p,...m(t.styles[u]||{})};return p}return m(l)})(),o=(()=>{let l=e.rowStyle;if(!l)return {};if(typeof l=="string")return m(t.styles[l]||{});if(Array.isArray(l)){let p={};for(let u of l)p={...p,...m(t.styles[u]||{})};return p}return m(l)})(),s=(()=>{let l=e.alternateRowStyle;if(!l){let p=e.alternatingRowColor;return p?{"background-color":p}:{}}if(typeof l=="string")return m(t.styles[l]||{});if(Array.isArray(l)){let p={};for(let u of l)p={...p,...m(t.styles[u]||{})};return p}return m(l)})(),a=(()=>{let l=e.borders;return l===false?{outer:false,inner:false}:l===true||l===void 0?{outer:true,inner:true,color:"#e2e8f0",width:1}:{outer:true,inner:true,color:"#e2e8f0",width:1,...l}})(),d=e.headers??e.columns?.map(l=>l.header||"").filter(l=>l!==""),c=l=>e.columns?.[l]?.align,i=document.createElement("table");if(i.style.width="100%",i.style.borderCollapse="collapse",i.style.fontSize="14px",a.outer&&(i.style.border=`${a.width||1}px solid ${a.color||"#e2e8f0"}`),d&&d.length>0){let l=document.createElement("thead"),p=document.createElement("tr");b(p,r),d.forEach((u,x)=>{let g=document.createElement("th");g.textContent=u,g.style.padding="8px 12px",g.style.fontWeight="600",g.style.background="#f8fafc",g.style.textAlign=c(x)||"left",a.inner&&(g.style.border=`${a.width||1}px solid ${a.color||"#e2e8f0"}`),p.appendChild(g);}),l.appendChild(p),i.appendChild(l);}let h=document.createElement("tbody");return e.rows.forEach((l,p)=>{let u=document.createElement("tr");b(u,o),p%2===1&&b(u,s),l.forEach((x,g)=>{let y=document.createElement("td");y.textContent=Y(x);let v=ee(x);v.colspan&&(y.colSpan=v.colspan),v.rowspan&&(y.rowSpan=v.rowspan),y.style.padding="8px 12px",y.style.verticalAlign="top",y.style.textAlign=c(g)||"left",a.inner&&(y.style.border=`${a.width||1}px solid ${a.color||"#e2e8f0"}`),u.appendChild(y);}),h.appendChild(u);}),i.appendChild(h),n.appendChild(i),n}function ne(e,t){let n=document.createElement("div");n.className="jdfjs-list-wrap",b(n,E(e.style,t.styles));let r=e.listType??(e.ordered?"ordered":"unordered"),o=z(r);return $(o,e.items||[],r),n.appendChild(o),n}function z(e){let t=document.createElement(e==="ordered"?"ol":"ul");return t.style.margin="0",t.style.paddingLeft="20px",t.style.listStyle=e==="ordered"?"decimal":"disc",t}function $(e,t,n,r){for(let o of t){let s=document.createElement("li");if(s.style.fontSize="14px",s.style.lineHeight="1.6",s.appendChild(document.createTextNode(o.content)),o.children?.length){let a=o.listType||n,d=z(a);d.style.marginTop="4px",$(d,o.children,a),s.appendChild(d);}e.appendChild(s);}}function re(e){let t=document.createElement("div");t.className="jdfjs-shape",t.style.width="100%",t.style.height="100%";let n=e.width??100,r=e.height??100,o=e.fill??"none",s=(()=>{let i=e.stroke;return typeof i=="string"?i:i?.color?i.color:"none"})(),a=(()=>{let i=e.stroke;return typeof i=="object"&&i?.width!=null?i.width:e.strokeWidth??0})(),d=document.createElementNS(j,"svg");d.setAttribute("width","100%"),d.setAttribute("height","100%"),d.setAttribute("viewBox",`0 0 ${n} ${r}`),d.setAttribute("preserveAspectRatio","none"),d.style.display="block",d.style.overflow="visible";let c=null;switch(e.shape){case "rect":{let i=document.createElementNS(j,"rect");i.setAttribute("x","0"),i.setAttribute("y","0"),i.setAttribute("width",String(n)),i.setAttribute("height",String(r)),e.borderRadius&&i.setAttribute("rx",String(e.borderRadius)),c=i;break}case "circle":{let i=document.createElementNS(j,"circle");i.setAttribute("cx",String(n/2)),i.setAttribute("cy",String(r/2)),i.setAttribute("r",String(Math.min(n,r)/2)),c=i;break}case "ellipse":{let i=document.createElementNS(j,"ellipse");i.setAttribute("cx",String(n/2)),i.setAttribute("cy",String(r/2)),i.setAttribute("rx",String(n/2)),i.setAttribute("ry",String(r/2)),c=i;break}case "line":{let i=document.createElementNS(j,"line");return i.setAttribute("x1","0"),i.setAttribute("y1","0"),i.setAttribute("x2",String(n)),i.setAttribute("y2",String(r)),i.setAttribute("stroke",s==="none"?o!=="none"?o:"currentColor":s),i.setAttribute("stroke-width",String(a||.3)),d.appendChild(i),t.appendChild(d),t}case "path":{let i=document.createElementNS(j,"path");i.setAttribute("d",e.path||""),i.setAttribute("fill-rule","evenodd"),c=i;break}}return c&&(c.setAttribute("fill",o),c.setAttribute("stroke",s),a&&c.setAttribute("stroke-width",String(a)),d.appendChild(c)),t.appendChild(d),t}function oe(e,t){let n=document.createElement("div");n.className="jdfjs-collapsible",b(n,E(e.style,t.styles)),n.style.border="1px solid #e2e8f0",n.style.borderRadius="8px",n.style.overflow="hidden",n.style.background="#ffffff";let r=document.createElement("button");r.type="button",r.className="jdfjs-collapsible-header",r.style.width="100%",r.style.display="flex",r.style.alignItems="center",r.style.gap="8px",r.style.padding="10px 16px",r.style.fontSize="14px",r.style.fontWeight="500",r.style.background="#f8fafc",r.style.border="0",r.style.cursor="pointer",r.style.textAlign="left";let o=document.createElement("span");o.textContent="\u25B6",o.style.fontSize="10px",o.style.transition="transform 0.15s ease";let s=document.createElement("span");s.textContent=e.title||"Section",s.style.flex="1",r.appendChild(o),r.appendChild(s),n.appendChild(r);let a=document.createElement("div");a.className="jdfjs-collapsible-body",a.style.padding="12px 16px",a.style.position="relative";let d=e.expanded??false,c=()=>{a.style.display=d?"block":"none",o.style.transform=d?"rotate(90deg)":"rotate(0deg)";};c(),r.addEventListener("click",()=>{d=!d,c();});for(let i of e.elements||[]){let h=w(i,{...t,path:[...t.path,"elements"]});h&&a.appendChild(h);}return n.appendChild(a),n}function ie(e,t){let n=document.createElement("div");n.className="jdfjs-toc",b(n,E(e.style,t.styles));let r=e.depth??6,o=[];t.document.pages.forEach((s,a)=>{for(let d of s.elements){if(d.type!=="text")continue;let c=d,i=c.tocEntry||(c.heading?c.content:null);if(!i)continue;let h=typeof c.tocLevel=="number"?c.tocLevel:typeof c.heading=="number"?c.heading:1;h>r||o.push({title:i,pageIndex:a,level:h});}});for(let s of o){let a=document.createElement("button");a.type="button",a.className="jdfjs-toc-entry",a.style.display="flex",a.style.alignItems="baseline",a.style.gap="8px",a.style.width="100%",a.style.background="transparent",a.style.border="0",a.style.borderBottom="1px dotted #e2e8f0",a.style.padding=`6px 8px 6px ${(s.level-1)*16+8}px`,a.style.cursor="pointer",a.style.fontSize="14px",a.style.color="#334155",a.style.textAlign="left";let d=document.createElement("span");d.textContent=s.title,d.style.flex="1";let c=document.createElement("span");c.style.flex="1",c.style.borderBottom="1px dotted #cbd5e1",c.style.alignSelf="end",c.style.marginBottom="4px";let i=document.createElement("span");i.textContent=String(s.pageIndex+1),i.style.fontFamily="JetBrains Mono, monospace",i.style.fontSize="12px",i.style.color="#94a3b8",i.style.flexShrink="0",a.appendChild(d),a.appendChild(c),a.appendChild(i),a.addEventListener("click",()=>t.onNavigatePage?.(s.pageIndex)),n.appendChild(a);}return n}function D(e,t){return e.replace(/\{\{pageNumber\}\}/g,String(t.pageNumber)).replace(/\{\{totalPages\}\}/g,String(t.totalPages)).replace(/\{\{title\}\}/g,t.title).replace(/\{\{author\}\}/g,t.author)}async function P(e,t,n={}){let r=O(e);r.classList.add("jdfjs-loading");try{let o=await fetch(t,{headers:{Accept:"application/json,application/jdf+json"}});if(!o.ok)throw new Error(`Failed to fetch ${t} (${o.status})`);let s=await o.json();if(!s?.$jdf)throw new Error("Not a valid JDF document (missing $jdf field)");return r.classList.remove("jdfjs-loading"),I(r,s,n)}catch(o){throw r.classList.remove("jdfjs-loading"),r.classList.add("jdfjs-error"),r.innerHTML=`<div class="jdfjs-error-msg">${se(o.message)}</div>`,n.onError?.(o),o}}function I(e,t,n={}){let r=O(e);return new S(r,t,n).getInstance()}var S=class{constructor(t,n,r={}){this.resizeObs=null;this.toolbarEl=null;this.sidebarEl=null;this.observer=null;this.container=t,this.doc=n,this.options={zoom:r.zoom??1,sidebar:r.sidebar??false,toolbar:r.toolbar??true,darkMode:r.darkMode??"auto",initialPage:r.initialPage??0,fit:r.fit??"manual",...r},this.zoom=this.options.zoom,this.currentPage=this.options.initialPage,this.applyContainerSize(),this.mount(),queueMicrotask(()=>this.options.onLoad?.(this.doc));}applyContainerSize(){let t=this.options.width,n=this.options.height;t!=null&&(this.container.style.width=typeof t=="number"?`${t}px`:t),n!=null&&(this.container.style.height=typeof n=="number"?`${n}px`:n);}mount(){this.container.innerHTML="",this.container.classList.add("jdfjs"),this.options.darkMode==="dark"?this.container.classList.add("jdfjs-dark"):this.options.darkMode==="auto"&&window.matchMedia?.("(prefers-color-scheme: dark)").matches&&this.container.classList.add("jdfjs-dark"),this.root=document.createElement("div"),this.root.className="jdfjs-root",this.options.toolbar&&(this.toolbarEl=this.buildToolbar(),this.root.appendChild(this.toolbarEl));let t=document.createElement("div");t.className="jdfjs-body",this.options.sidebar&&(this.sidebarEl=this.buildSidebar(),t.appendChild(this.sidebarEl)),this.pagesEl=document.createElement("div"),this.pagesEl.className="jdfjs-pages",t.appendChild(this.pagesEl),this.root.appendChild(t),this.container.appendChild(this.root),this.renderAllPages(),this.setupScrollObserver(),this.setupResizeObserver(),this.applyFit(),this.currentPage>0&&this.scrollToPage(this.currentPage);}setupResizeObserver(){typeof ResizeObserver>"u"||(this.resizeObs?.disconnect(),this.resizeObs=new ResizeObserver(()=>this.applyFit()),this.resizeObs.observe(this.pagesEl));}applyFit(){if(this.options.fit==="manual")return;let t=this.pagesEl.querySelector(".jdfjs-page");if(!t)return;let n=parseFloat(t.style.width||"0"),r=parseFloat(t.style.minHeight||"0");if(!n||!r)return;let o=this.pagesEl.clientWidth-32,s=this.pagesEl.clientHeight-32;this.options.fit==="fit-width"?this.zoom=Math.max(.25,Math.min(3,o/n)):this.options.fit==="fit-page"&&(this.zoom=Math.max(.25,Math.min(3,Math.min(o/n,s/r)))),this.applyZoom(),this.updateIndicators();}buildToolbar(){let t=document.createElement("div");return t.className="jdfjs-toolbar",t.innerHTML=`
2
+ <span class="jdfjs-title"></span>
3
+ <div class="jdfjs-spacer"></div>
4
+ <button class="jdfjs-btn" data-act="prev" title="Previous page">\u2039</button>
5
+ <span class="jdfjs-page-indicator"></span>
6
+ <button class="jdfjs-btn" data-act="next" title="Next page">\u203A</button>
7
+ <span class="jdfjs-divider"></span>
8
+ <button class="jdfjs-btn" data-act="zoom-out" title="Zoom out">\u2212</button>
9
+ <span class="jdfjs-zoom-indicator"></span>
10
+ <button class="jdfjs-btn" data-act="zoom-in" title="Zoom in">+</button>
11
+ `,t.querySelector(".jdfjs-title").textContent=this.doc.meta?.title??"Document",this.updateIndicators(t),t.addEventListener("click",n=>{let r=n.target.closest("[data-act]");if(!r)return;let o=r.getAttribute("data-act");o==="prev"?this.goToPage(this.currentPage-1):o==="next"?this.goToPage(this.currentPage+1):o==="zoom-in"?this.setZoom(this.zoom+.1):o==="zoom-out"&&this.setZoom(this.zoom-.1);}),t}buildSidebar(){let t=document.createElement("div");return t.className="jdfjs-sidebar",this.doc.pages.forEach((n,r)=>{let o=document.createElement("button");o.className="jdfjs-sidebar-thumb",o.setAttribute("data-page",String(r)),o.innerHTML=`<span class="jdfjs-sidebar-num">${r+1}</span>`,o.addEventListener("click",()=>this.goToPage(r)),t.appendChild(o);}),t}updateIndicators(t=this.toolbarEl){if(!t)return;let n=t.querySelector(".jdfjs-page-indicator");n&&(n.textContent=`${this.currentPage+1} / ${this.doc.pages.length}`);let r=t.querySelector(".jdfjs-zoom-indicator");r&&(r.textContent=`${Math.round(this.zoom*100)}%`),this.sidebarEl&&this.sidebarEl.querySelectorAll(".jdfjs-sidebar-thumb").forEach(o=>{let s=Number(o.getAttribute("data-page"));o.classList.toggle("jdfjs-sidebar-thumb-active",s===this.currentPage);});}renderAllPages(){this.pagesEl.innerHTML="";let t=this.doc.styles??{};this.doc.pages.forEach((n,r)=>{let o=this.renderPage(n,r,t);this.pagesEl.appendChild(o);}),this.applyZoom();}renderPage(t,n,r){let o=R(t.pageSize??this.doc.meta?.pageSize??"A4",t.pageOrientation??this.doc.meta?.pageOrientation??"portrait"),s={...N,...this.doc.meta?.margins||{},...t.margins||{}},a=document.createElement("div");a.className="jdfjs-page-wrapper",a.setAttribute("data-page-index",String(n));let d=document.createElement("div");d.className="jdfjs-page",d.style.width=`${f(o.width)}px`,d.style.minHeight=`${f(o.height)}px`,t.background&&(d.style.backgroundColor=t.background);let c=t.header??this.doc.header,i=t.footer??this.doc.footer,h=c?.height??0,l=i?.height??0;if(c){let u=this.renderHeaderFooter(c,n,this.doc.pages.length,r);u.classList.add("jdfjs-header"),u.style.paddingTop=`${f(s.top/2)}px`,u.style.paddingLeft=`${f(s.left)}px`,u.style.paddingRight=`${f(s.right)}px`,d.appendChild(u);}let p=document.createElement("div");if(p.className="jdfjs-page-content",p.style.position="relative",p.style.paddingTop=`${f((s.top||0)+h)}px`,p.style.paddingRight=`${f(s.right||0)}px`,p.style.paddingBottom=`${f((s.bottom||0)+l)}px`,p.style.paddingLeft=`${f(s.left||0)}px`,t.elements.forEach((u,x)=>{let g=w(u,{styles:r,resources:this.doc.resources,document:this.doc,path:["pages",n,"elements",x],onNavigatePage:y=>this.goToPage(y)});g&&p.appendChild(g);}),d.appendChild(p),i){let u=this.renderHeaderFooter(i,n,this.doc.pages.length,r);u.classList.add("jdfjs-footer"),u.style.paddingBottom=`${f(s.bottom/2)}px`,u.style.paddingLeft=`${f(s.left)}px`,u.style.paddingRight=`${f(s.right)}px`,d.appendChild(u);}return a.appendChild(d),a}renderHeaderFooter(t,n,r,o){let s=document.createElement("div");if(t.elements?.length)t.elements.forEach((a,d)=>{let c=w(a,{styles:o,resources:this.doc.resources,document:this.doc,path:["__hf__",n,d],onNavigatePage:i=>this.goToPage(i)});c&&s.appendChild(c);});else if(t.content){let a=D(t.content,{pageNumber:n+1,totalPages:r,title:this.doc.meta?.title??"",author:this.doc.meta?.author??""}),d=document.createElement("div");d.textContent=a,Object.assign(d.style,E(t.style,o)),s.appendChild(d);}return s}setupScrollObserver(){typeof IntersectionObserver>"u"||(this.observer?.disconnect(),this.observer=new IntersectionObserver(t=>{let n=t.filter(r=>r.isIntersecting).sort((r,o)=>o.intersectionRatio-r.intersectionRatio);if(n[0]){let r=Number(n[0].target.getAttribute("data-page-index"));!isNaN(r)&&r!==this.currentPage&&(this.currentPage=r,this.updateIndicators(),this.options.onPageChange?.(r));}},{root:this.pagesEl,threshold:[.25,.5,.75]}),this.pagesEl.querySelectorAll("[data-page-index]").forEach(t=>this.observer.observe(t)));}scrollToPage(t){let n=this.pagesEl.querySelector(`[data-page-index="${t}"]`);n&&n.scrollIntoView({behavior:"smooth",block:"start"});}applyZoom(){this.pagesEl.style.setProperty("--jdfjs-zoom",String(this.zoom)),this.pagesEl.querySelectorAll(".jdfjs-page").forEach(t=>{t.style.transform=`scale(${this.zoom})`,t.style.transformOrigin="top center";});}setZoom(t){this.zoom=Math.max(.25,Math.min(3,t)),this.applyZoom(),this.updateIndicators();}getZoom(){return this.zoom}goToPage(t){let n=Math.max(0,Math.min(this.doc.pages.length-1,t));this.currentPage=n,this.scrollToPage(n),this.updateIndicators(),this.options.onPageChange?.(n);}getCurrentPage(){return this.currentPage}setDocument(t){if(this.doc=t,this.currentPage=0,this.toolbarEl){let n=this.toolbarEl.querySelector(".jdfjs-title");n&&(n.textContent=t.meta?.title??"Document");}this.sidebarEl&&(this.sidebarEl.innerHTML="",this.buildSidebar().querySelectorAll(".jdfjs-sidebar-thumb").forEach(r=>this.sidebarEl.appendChild(r))),this.renderAllPages(),this.setupScrollObserver(),this.options.onLoad?.(t);}destroy(){this.observer?.disconnect(),this.resizeObs?.disconnect(),this.container.innerHTML="",this.container.classList.remove("jdfjs","jdfjs-dark","jdfjs-loading","jdfjs-error"),this.container.style.removeProperty("width"),this.container.style.removeProperty("height");}getInstance(){return {container:this.container,document:this.doc,setZoom:t=>this.setZoom(t),getZoom:()=>this.getZoom(),goToPage:t=>this.goToPage(t),getCurrentPage:()=>this.getCurrentPage(),setDocument:t=>this.setDocument(t),destroy:()=>this.destroy()}}};function O(e){if(typeof e=="string"){let t=document.querySelector(e);if(!t)throw new Error(`jdfjs: container "${e}" not found`);return t}return e}function se(e){return e.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}var M=new WeakSet;function ae(e){let t={},n=l=>e.getAttribute(l),r=n("zoom");if(r!=null){let l=Number(r);isNaN(l)||(t.zoom=l);}let o=n("sidebar");o!=null&&(t.sidebar=o!=="false"&&o!=="0");let s=n("toolbar");s!=null&&(t.toolbar=s!=="false"&&s!=="0");let a=n("dark-mode")||n("darkmode");(a==="auto"||a==="light"||a==="dark")&&(t.darkMode=a);let d=n("page");if(d!=null){let l=Number(d);isNaN(l)||(t.initialPage=l);}let c=n("width");if(c!=null){let l=Number(c);t.width=isNaN(l)?c:l;}let i=n("height");if(i!=null){let l=Number(i);t.height=isNaN(l)?i:l;}let h=n("fit");return (h==="manual"||h==="fit-width"||h==="fit-page")&&(t.fit=h),t}function T(e){if(M.has(e)||e.hasAttribute("manual"))return;let t=e.getAttribute("src");if(!t)return;M.add(e);let n=e,r=ae(e);P(n,t,r).catch(o=>{console.error("[jdf.js] failed to embed",t,o),n.dispatchEvent(new CustomEvent("jdf-error",{detail:o,bubbles:true}));});}function C(e=document){e.querySelectorAll("jdf").forEach(T);}function F(){if(typeof MutationObserver>"u")return;new MutationObserver(t=>{for(let n of t)n.addedNodes.forEach(r=>{r instanceof Element&&(r.tagName.toLowerCase()==="jdf"?T(r):C(r));});}).observe(document.body,{childList:true,subtree:true});}function le(e=document){C(e);}function de(){typeof window>"u"||typeof document>"u"||window.JDFjsAutoInit!==false&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{C(),F();},{once:true}):(C(),F()));}if(typeof customElements<"u"&&!customElements.get("jdf")){class e extends HTMLElement{static get observedAttributes(){return ["src","width","height","zoom"]}connectedCallback(){T(this);}attributeChangedCallback(n,r,o){if(r!==o){if(n==="src"&&o)M.delete(this),T(this);else if(n==="width"&&o){let s=Number(o);this.style.width=isNaN(s)?o:`${s}px`;}else if(n==="height"&&o){let s=Number(o);this.style.height=isNaN(s)?o:`${s}px`;}}}}customElements.define("jdf",e);}de();export{S as JDFViewer,P as embed,le as jdf,I as render};//# sourceMappingURL=jdfjs.js.map
12
+ //# sourceMappingURL=jdfjs.js.map