lite-image-preview 1.0.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/src/types.ts ADDED
@@ -0,0 +1,98 @@
1
+ /**
2
+ * A 2D point in viewport/client coordinates.
3
+ */
4
+ export type Point = {
5
+ x: number
6
+ y: number
7
+ }
8
+
9
+ /**
10
+ * A simple transform state used by the default transform-based adapter.
11
+ */
12
+ export type TransformState = {
13
+ scale: number
14
+ x: number
15
+ y: number
16
+ }
17
+
18
+ /**
19
+ * An SVG viewBox rectangle.
20
+ */
21
+ export type ViewBox = {
22
+ x: number
23
+ y: number
24
+ w: number
25
+ h: number
26
+ }
27
+
28
+ /**
29
+ * A function that closes an opened preview dialog.
30
+ */
31
+ export type PreviewCloseHandle = () => void
32
+
33
+ /**
34
+ * The minimum controller interface used by the preview container.
35
+ * Custom adapters may extend this behavior, but these methods are required.
36
+ */
37
+ export interface PreviewController {
38
+ /**
39
+ * Zoom in or out using a wheel event.
40
+ * Implementations should call preventDefault() when appropriate.
41
+ */
42
+ zoomWithWheel: (e: WheelEvent) => void
43
+
44
+ /**
45
+ * Release listeners and internal state created by the adapter.
46
+ */
47
+ destroy: () => void
48
+
49
+ /**
50
+ * Restore the element's inline styles or attributes back to the pre-preview state.
51
+ */
52
+ resetStyle: () => void
53
+ }
54
+
55
+ /**
56
+ * The public adapter interface used by createPreview.
57
+ *
58
+ * The preview container handles dialog lifecycle and gesture routing,
59
+ * while the adapter owns the actual rendering backend.
60
+ * For example:
61
+ * - image previews can use CSS transform
62
+ * - SVG previews can use viewBox
63
+ */
64
+ export interface PreviewAdapter extends PreviewController {
65
+ /**
66
+ * Reset the visual state to the initial "fit to stage" layout.
67
+ */
68
+ fitToStage: (stage: HTMLElement) => void
69
+
70
+ /**
71
+ * Move the content by a delta measured in client pixels.
72
+ */
73
+ panBy: (dx: number, dy: number) => void
74
+
75
+ /**
76
+ * Zoom around a specific client coordinate.
77
+ *
78
+ * @param clientX - Client X coordinate.
79
+ * @param clientY - Client Y coordinate.
80
+ * @param factor - Multiplicative zoom factor.
81
+ */
82
+ zoomAt: (clientX: number, clientY: number, factor: number) => void
83
+
84
+ /**
85
+ * Start a pinch gesture using two points in client coordinates.
86
+ */
87
+ beginPinch: (points: [Point, Point]) => void
88
+
89
+ /**
90
+ * Update an active pinch gesture using two points in client coordinates.
91
+ */
92
+ updatePinch: (points: [Point, Point]) => void
93
+ }
94
+
95
+ /**
96
+ * Factory function used by createPreview to initialize a rendering adapter.
97
+ */
98
+ export type PreviewAdapterFactory = (stage: HTMLElement) => PreviewAdapter
package/src/util.ts ADDED
@@ -0,0 +1,102 @@
1
+ import type { Point, ViewBox } from './types'
2
+
3
+ /**
4
+ * Clamp a number into the given range.
5
+ */
6
+ export function clamp(n: number, min: number, max: number) {
7
+ return Math.min(max, Math.max(min, n))
8
+ }
9
+
10
+ /**
11
+ * Return whether a value is a finite number.
12
+ */
13
+ export function isFiniteNumber(n: number) {
14
+ return Number.isFinite(n)
15
+ }
16
+
17
+ /**
18
+ * Compute the Euclidean distance between two points.
19
+ */
20
+ export function dist(a: Point, b: Point) {
21
+ return Math.hypot(a.x - b.x, a.y - b.y)
22
+ }
23
+
24
+ /**
25
+ * Compute the midpoint between two points.
26
+ */
27
+ export function mid(a: Point, b: Point) {
28
+ return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }
29
+ }
30
+
31
+ /**
32
+ * Read a safe client rect. Width and height are never returned as zero.
33
+ */
34
+ export function safeRect(el: Element) {
35
+ const rect = el.getBoundingClientRect()
36
+ return {
37
+ left: rect.left,
38
+ top: rect.top,
39
+ width: rect.width || 1,
40
+ height: rect.height || 1,
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Parse a numeric SVG/CSS length value.
46
+ * Returns null when the value is missing or invalid.
47
+ */
48
+ export function parseLength(value: string | null): number | null {
49
+ if (!value) return null
50
+ const n = Number.parseFloat(value)
51
+ return Number.isFinite(n) && n > 0 ? n : null
52
+ }
53
+
54
+ /**
55
+ * Measure the base SVG size used to initialize the preview viewBox.
56
+ */
57
+ export function measureSvgBaseViewBox(svg: SVGSVGElement): ViewBox {
58
+ const vb = svg.viewBox.baseVal
59
+ if (vb && vb.width > 0 && vb.height > 0) {
60
+ return { x: vb.x, y: vb.y, w: vb.width, h: vb.height }
61
+ }
62
+
63
+ const attrW = parseLength(svg.getAttribute('width'))
64
+ const attrH = parseLength(svg.getAttribute('height'))
65
+ if (attrW && attrH) {
66
+ return { x: 0, y: 0, w: attrW, h: attrH }
67
+ }
68
+
69
+ try {
70
+ const bbox = svg.getBBox()
71
+ if (bbox.width > 0 && bbox.height > 0) {
72
+ return { x: bbox.x, y: bbox.y, w: bbox.width, h: bbox.height }
73
+ }
74
+ } catch {
75
+ // Some SVGs may fail getBBox() before they are fully laid out.
76
+ }
77
+
78
+ return { x: 0, y: 0, w: 100, h: 100 }
79
+ }
80
+
81
+ /**
82
+ * Wait until an HTML image is fully loaded.
83
+ */
84
+ export function waitForImageLoad(img: HTMLImageElement): Promise<void> {
85
+ return new Promise((resolve, reject) => {
86
+ if (img.complete) {
87
+ if (img.naturalWidth > 0) {
88
+ resolve()
89
+ } else {
90
+ reject(new Error(`Image failed to load: ${img.currentSrc || img.src}`))
91
+ }
92
+ return
93
+ }
94
+
95
+ img.addEventListener('load', () => resolve(), { once: true })
96
+ img.addEventListener(
97
+ 'error',
98
+ () => reject(new Error(`Image failed to load: ${img.currentSrc || img.src}`)),
99
+ { once: true }
100
+ )
101
+ })
102
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ // Visit https://aka.ms/tsconfig to read more about this file
3
+ "compilerOptions": {
4
+ // File Layout
5
+ "rootDir": "./src",
6
+ "outDir": "./dist",
7
+
8
+ // Environment Settings
9
+ // See also https://aka.ms/tsconfig/module
10
+ "module": "ESNext",
11
+ "target": "es2022",
12
+ "lib": ["ES2022", "DOM"],
13
+ "types": [],
14
+ // For nodejs:
15
+ // "lib": ["esnext"],
16
+ // "types": ["node"],
17
+ // and npm install -D @types/node
18
+
19
+ // Other Outputs
20
+ "sourceMap": true,
21
+ "declaration": true,
22
+ "declarationMap": true,
23
+
24
+ // Stricter Typechecking Options
25
+ "noUncheckedIndexedAccess": true,
26
+ "exactOptionalPropertyTypes": true,
27
+
28
+ // Style Options
29
+ // "noImplicitReturns": true,
30
+ // "noImplicitOverride": true,
31
+ "noUnusedLocals": true,
32
+ // "noUnusedParameters": true,
33
+ "noFallthroughCasesInSwitch": true,
34
+ // "noPropertyAccessFromIndexSignature": true,
35
+
36
+ // Recommended Options
37
+ "strict": true,
38
+ "verbatimModuleSyntax": true,
39
+ "isolatedModules": true,
40
+ "noUncheckedSideEffectImports": true,
41
+ "moduleDetection": "force",
42
+ "skipLibCheck": true,
43
+ "moduleResolution": "bundler",
44
+ },
45
+ "include": ["src/**/*"],
46
+ "exclude": ["node_modules", "dist"]
47
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ {
5
+ "path": "./tsconfig.node.json"
6
+ },
7
+ {
8
+ "path": "./tsconfig.app.json"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": "@tsconfig/node24/tsconfig.json",
3
+ "include": [
4
+ "rolldown.config.*",
5
+ "vite.config.*",
6
+ "vitest.config.*",
7
+ "cypress.config.*",
8
+ "nightwatch.conf.*",
9
+ "playwright.config.*",
10
+ "eslint.config.*"
11
+ ],
12
+ "compilerOptions": {
13
+ "noEmit": true,
14
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
15
+
16
+ "allowJs": true,
17
+ "checkJs": true,
18
+ "strict": false,
19
+
20
+ "module": "ESNext",
21
+ "moduleResolution": "Bundler",
22
+ "types": ["node"]
23
+ }
24
+ }