@michaelyagi/kiri 0.1.0-alpha.2
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/batch.d.ts +34 -0
- package/dist/exif.d.ts +17 -0
- package/dist/export.d.ts +16 -0
- package/dist/filters.d.ts +11 -0
- package/dist/gestures.d.ts +35 -0
- package/dist/index.d.ts +4 -0
- package/dist/kiri.css +72 -0
- package/dist/kiri.d.ts +88 -0
- package/dist/kiri.js +779 -0
- package/dist/kiri.js.map +1 -0
- package/dist/kiri.min.css +1 -0
- package/dist/kiri.min.js +10 -0
- package/dist/kiri.min.js.map +1 -0
- package/dist/kiri.mjs +613 -0
- package/dist/kiri.mjs.map +1 -0
- package/dist/stage.d.ts +24 -0
- package/dist/types.d.ts +117 -0
- package/dist/upload.d.ts +10 -0
- package/dist/validate.d.ts +7 -0
- package/package.json +57 -0
package/dist/batch.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Kiri } from './kiri';
|
|
2
|
+
import { ExportOptions, ExportResult, KiriOptions, LoadOptions } from './types';
|
|
3
|
+
export interface KiriBatchItem {
|
|
4
|
+
source: File | Blob | string;
|
|
5
|
+
loadOptions?: LoadOptions;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Steps a single shared `Kiri` instance through a queue of images, so the
|
|
9
|
+
* whole drag/zoom/rotate/flip/filters interaction surface is reused as-is —
|
|
10
|
+
* no per-image instance, no duplicated cropping logic.
|
|
11
|
+
*/
|
|
12
|
+
export declare class KiriBatch {
|
|
13
|
+
/** The shared `Kiri` instance. Use its normal methods (drag/zoom/rotate/filters/etc.) to adjust the currently-loaded item. */
|
|
14
|
+
readonly cropper: Kiri;
|
|
15
|
+
private readonly items;
|
|
16
|
+
private index;
|
|
17
|
+
private captures;
|
|
18
|
+
/** @param items An initial queue; more can be added later via `add()`. */
|
|
19
|
+
constructor(container: HTMLElement, options?: KiriOptions, items?: KiriBatchItem[]);
|
|
20
|
+
/** Appends an item to the queue. */
|
|
21
|
+
add(item: KiriBatchItem): void;
|
|
22
|
+
/** Total number of queued items. */
|
|
23
|
+
get length(): number;
|
|
24
|
+
/** Loads the next queued image into `cropper`. Returns false once the queue is exhausted. */
|
|
25
|
+
next(): Promise<boolean>;
|
|
26
|
+
/** Metadata for the currently loaded item, or null before the first next() / after exhaustion. */
|
|
27
|
+
current(): KiriBatchItem | null;
|
|
28
|
+
/** Exports the current crop and stores it, indexed by item order. */
|
|
29
|
+
capture(options?: ExportOptions): Promise<ExportResult>;
|
|
30
|
+
/** All captures made so far, in item order (sparse where an item hasn't been captured yet). */
|
|
31
|
+
results(): ExportResult[];
|
|
32
|
+
/** Tears down the shared `Kiri` instance. */
|
|
33
|
+
destroy(): void;
|
|
34
|
+
}
|
package/dist/exif.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the EXIF orientation tag (1-8) from a JPEG ArrayBuffer, defaulting to 1
|
|
3
|
+
* (no adjustment) for images with no EXIF data or non-JPEG input.
|
|
4
|
+
*/
|
|
5
|
+
export declare function readExifOrientation(buffer: ArrayBuffer): number;
|
|
6
|
+
export interface OrientationTransform {
|
|
7
|
+
rotation: number;
|
|
8
|
+
flipHorizontal: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Maps an EXIF orientation value to the rotation + horizontal-flip pair that
|
|
12
|
+
* normalizes it. A vertical flip is never needed on its own: orientation 4
|
|
13
|
+
* (mirror vertical) is expressed as rotate(180) + flip horizontal, which is
|
|
14
|
+
* mathematically equivalent and matches Kiri's transform order (flip is
|
|
15
|
+
* applied before rotation, so this composes correctly).
|
|
16
|
+
*/
|
|
17
|
+
export declare function orientationToTransform(orientation: number): OrientationTransform;
|
package/dist/export.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ExportOptions, ExportResult, FrameShape, KiriState, Offset } from './types';
|
|
2
|
+
import { Size } from './gestures';
|
|
3
|
+
/**
|
|
4
|
+
* Top-left corner of the frame, in the local pixel space of the rendered
|
|
5
|
+
* (rotated + scaled) image. Pure so the crop-region math is testable without
|
|
6
|
+
* a real canvas: the frame is centered on the stage, the image's own center
|
|
7
|
+
* sits at `offset` from that same point, so the frame's top-left in the
|
|
8
|
+
* rendered image's local space is the rendered center, shifted back by the
|
|
9
|
+
* offset.
|
|
10
|
+
*/
|
|
11
|
+
export declare function computeFrameSourceRect(rendered: Size, offset: Offset, frame: Size): {
|
|
12
|
+
left: number;
|
|
13
|
+
top: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function renderCropToCanvas(img: HTMLImageElement, state: KiriState, frame: Size, outputWidth: number, outputHeight: number, frameShape: FrameShape): HTMLCanvasElement;
|
|
16
|
+
export declare function exportCrop(img: HTMLImageElement, state: KiriState, frame: Size, frameShape: FrameShape, options: ExportOptions): Promise<ExportResult>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Filters } from './types';
|
|
2
|
+
export declare const DEFAULT_FILTERS: Filters;
|
|
3
|
+
/** Merges a partial filter update into the current filters, clamping numeric values to >= 0. */
|
|
4
|
+
export declare function mergeFilters(current: Filters, partial: Partial<Filters>): Filters;
|
|
5
|
+
/**
|
|
6
|
+
* Builds a CSS `filter` value from the current filter state. Used verbatim as
|
|
7
|
+
* both the live-preview `img.style.filter` and a canvas 2D context's
|
|
8
|
+
* `ctx.filter` before drawing, so preview and export always match exactly —
|
|
9
|
+
* no separately hand-rolled brightness/contrast/saturation pixel math.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildFilterString(filters: Filters): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { KiriState, Offset } from './types';
|
|
2
|
+
export interface Size {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
}
|
|
6
|
+
/** Rotation-aware natural size: 90/270 degrees swap width and height. */
|
|
7
|
+
export declare function effectiveNaturalSize(natural: Size, rotationDeg: number): Size;
|
|
8
|
+
/** The smallest scale at which the (rotation-adjusted) image still fully covers the frame. */
|
|
9
|
+
export declare function computeCoverScale(natural: Size, frame: Size, rotationDeg: number): number;
|
|
10
|
+
/** Rendered size of the image layer given a user zoom multiplier (relative to cover scale). */
|
|
11
|
+
export declare function effectiveRenderedSize(natural: Size, frame: Size, rotationDeg: number, zoom: number): Size;
|
|
12
|
+
export declare function clampZoom(zoom: number, minZoom: number, maxZoom: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* Clamp the image-center offset (from stage/frame center) so the frame stays
|
|
15
|
+
* fully covered by the rendered image on every axis.
|
|
16
|
+
*/
|
|
17
|
+
export declare function clampOffset(offset: Offset, rendered: Size, frame: Size): Offset;
|
|
18
|
+
export declare function normalizeRotation(deg: number): number;
|
|
19
|
+
interface GestureCallbacks {
|
|
20
|
+
getNaturalSize: () => Size;
|
|
21
|
+
getFrameSize: () => Size;
|
|
22
|
+
getState: () => KiriState;
|
|
23
|
+
getMinMaxZoom: () => {
|
|
24
|
+
min: number;
|
|
25
|
+
max: number;
|
|
26
|
+
};
|
|
27
|
+
setState: (next: KiriState) => void;
|
|
28
|
+
}
|
|
29
|
+
export interface GestureOptions {
|
|
30
|
+
mouseWheelZoom?: boolean | "ctrl";
|
|
31
|
+
}
|
|
32
|
+
export declare function attachGestures(stageEl: HTMLElement, callbacks: GestureCallbacks, options: GestureOptions): {
|
|
33
|
+
destroy: () => void;
|
|
34
|
+
};
|
|
35
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { Kiri } from './kiri';
|
|
2
|
+
export { KiriBatch } from './batch';
|
|
3
|
+
export type { KiriBatchItem } from './batch';
|
|
4
|
+
export type { ExportFormat, ExportOptions, ExportResult, ExportType, Filters, Flip, FrameShape, FrameSize, KiriEventCallback, KiriEventName, KiriOptions, KiriState, LoadOptions, Offset, UploadOptions, Uploader, ZoomerPosition, } from './types';
|
package/dist/kiri.css
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
.kiri-stage {
|
|
2
|
+
position: relative;
|
|
3
|
+
overflow: hidden;
|
|
4
|
+
width: 100%;
|
|
5
|
+
height: 100%;
|
|
6
|
+
min-height: 200px;
|
|
7
|
+
background: #333;
|
|
8
|
+
touch-action: none;
|
|
9
|
+
cursor: grab;
|
|
10
|
+
user-select: none;
|
|
11
|
+
}
|
|
12
|
+
.kiri-stage.kiri-dragging {
|
|
13
|
+
cursor: grabbing;
|
|
14
|
+
}
|
|
15
|
+
.kiri-image-layer {
|
|
16
|
+
position: absolute;
|
|
17
|
+
top: 50%;
|
|
18
|
+
left: 50%;
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
}
|
|
21
|
+
.kiri-image-layer img {
|
|
22
|
+
display: block;
|
|
23
|
+
max-width: none;
|
|
24
|
+
}
|
|
25
|
+
.kiri-frame {
|
|
26
|
+
position: absolute;
|
|
27
|
+
top: 50%;
|
|
28
|
+
left: 50%;
|
|
29
|
+
transform: translate(-50%, -50%);
|
|
30
|
+
border: 1px solid rgba(255, 255, 255, 0.85);
|
|
31
|
+
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
|
|
32
|
+
pointer-events: none;
|
|
33
|
+
}
|
|
34
|
+
.kiri-frame--circle {
|
|
35
|
+
border-radius: 50%;
|
|
36
|
+
}
|
|
37
|
+
.kiri-frame-handle {
|
|
38
|
+
position: absolute;
|
|
39
|
+
width: 10px;
|
|
40
|
+
height: 10px;
|
|
41
|
+
background: #fff;
|
|
42
|
+
border: 1px solid #333;
|
|
43
|
+
border-radius: 50%;
|
|
44
|
+
pointer-events: auto;
|
|
45
|
+
cursor: nwse-resize;
|
|
46
|
+
}
|
|
47
|
+
.kiri-root {
|
|
48
|
+
display: inline-flex;
|
|
49
|
+
gap: 10px;
|
|
50
|
+
align-items: stretch;
|
|
51
|
+
}
|
|
52
|
+
.kiri-root--bottom,
|
|
53
|
+
.kiri-root--top {
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
}
|
|
56
|
+
.kiri-root--left,
|
|
57
|
+
.kiri-root--right {
|
|
58
|
+
flex-direction: row;
|
|
59
|
+
}
|
|
60
|
+
.kiri-root--top .kiri-zoomer,
|
|
61
|
+
.kiri-root--left .kiri-zoomer {
|
|
62
|
+
order: -1;
|
|
63
|
+
}
|
|
64
|
+
.kiri-zoomer {
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
}
|
|
67
|
+
.kiri-root--left .kiri-zoomer,
|
|
68
|
+
.kiri-root--right .kiri-zoomer {
|
|
69
|
+
writing-mode: vertical-lr;
|
|
70
|
+
direction: rtl;
|
|
71
|
+
width: 24px;
|
|
72
|
+
}
|
package/dist/kiri.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ExportOptions, ExportResult, Filters, KiriEventCallback, KiriEventName, KiriOptions, KiriState, LoadOptions, UploadOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* An interactive image cropper attached to a plain DOM element. Drag to pan,
|
|
4
|
+
* zoom via wheel/pinch/an optional built-in slider, rotate in 90° steps,
|
|
5
|
+
* flip, apply filters, then export or upload the crop.
|
|
6
|
+
*/
|
|
7
|
+
export declare class Kiri {
|
|
8
|
+
private readonly container;
|
|
9
|
+
private readonly opts;
|
|
10
|
+
private readonly stage;
|
|
11
|
+
private readonly gestureHandle;
|
|
12
|
+
private resizeHandle;
|
|
13
|
+
private zoomerHandle;
|
|
14
|
+
private naturalSize;
|
|
15
|
+
private state;
|
|
16
|
+
private listeners;
|
|
17
|
+
/**
|
|
18
|
+
* @param container An element already present in the DOM. Passing
|
|
19
|
+
* `null`/`undefined`, or an element that isn't in the DOM yet, throws.
|
|
20
|
+
* @param options See the {@link KiriOptions} fields for defaults.
|
|
21
|
+
*/
|
|
22
|
+
constructor(container: HTMLElement, options?: KiriOptions);
|
|
23
|
+
/**
|
|
24
|
+
* Loads an image, replacing whatever was loaded before. EXIF orientation
|
|
25
|
+
* (rotation + horizontal flip) is corrected automatically unless
|
|
26
|
+
* `useExifOrientation: false` was passed to the constructor — only for
|
|
27
|
+
* `File`/`Blob` sources, since a plain URL string can't be read for EXIF
|
|
28
|
+
* data without an extra fetch.
|
|
29
|
+
* @param source A `File` (e.g. from a file input), a `Blob`, or a URL string.
|
|
30
|
+
* @param loadOptions Initial `zoom`/`offset`/`rotation`/`flip`.
|
|
31
|
+
*/
|
|
32
|
+
load(source: File | Blob | string, loadOptions?: LoadOptions): Promise<void>;
|
|
33
|
+
/** A snapshot of the current state — mutating the returned object has no effect. */
|
|
34
|
+
getState(): KiriState;
|
|
35
|
+
/** Sets the zoom to an absolute value, clamped to `[minZoom, maxZoom]`. */
|
|
36
|
+
setZoom(zoom: number): void;
|
|
37
|
+
/**
|
|
38
|
+
* Rotates relative to the current rotation, snapped to the nearest 90°.
|
|
39
|
+
* No-op if `rotatable: false` was passed to the constructor.
|
|
40
|
+
*/
|
|
41
|
+
rotate(deltaDeg: number): void;
|
|
42
|
+
/** Toggles horizontal flip, independent of rotation. No-op if `flippable: false`. */
|
|
43
|
+
flipHorizontal(): void;
|
|
44
|
+
/** Toggles vertical flip, independent of rotation. No-op if `flippable: false`. */
|
|
45
|
+
flipVertical(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Resizes the frame. Also resizes the stage to match, if
|
|
48
|
+
* `autoSizeStage: true` (the default). Each axis is clamped to a 20px
|
|
49
|
+
* minimum.
|
|
50
|
+
*/
|
|
51
|
+
setFrameSize(width: number, height: number): void;
|
|
52
|
+
/**
|
|
53
|
+
* Merges a partial update into the current filters (omitted fields are
|
|
54
|
+
* left as they are). Numeric values are clamped to `>= 0`.
|
|
55
|
+
*/
|
|
56
|
+
setFilters(filters: Partial<Filters>): void;
|
|
57
|
+
/**
|
|
58
|
+
* Renders the current crop. A circle frame is a real clip in the output
|
|
59
|
+
* (transparent corners on PNG/WebP); a circle exported as JPEG warns and
|
|
60
|
+
* renders solid black corners instead, since JPEG has no alpha channel.
|
|
61
|
+
* @returns A data URL string (`type: "base64"`, the default), a `Blob`, or an `HTMLCanvasElement`.
|
|
62
|
+
*/
|
|
63
|
+
export(options?: ExportOptions): Promise<ExportResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Exports the current crop as a blob, then uploads it — a default
|
|
66
|
+
* FormData/`fetch` POST, or a custom `uploader` (per-call `options.uploader`
|
|
67
|
+
* wins over the constructor's, which wins over the built-in default).
|
|
68
|
+
*/
|
|
69
|
+
upload(url: string, options?: UploadOptions): Promise<unknown>;
|
|
70
|
+
/** Subscribes to `"change"` — fires on every state update (drag/zoom/rotate/flip/filters), and once after `load()` resolves. */
|
|
71
|
+
on(event: KiriEventName, callback: KiriEventCallback): void;
|
|
72
|
+
/** Unsubscribes a callback previously passed to {@link on}. */
|
|
73
|
+
off(event: KiriEventName, callback: KiriEventCallback): void;
|
|
74
|
+
/**
|
|
75
|
+
* Tears the instance down: removes all pointer/wheel event listeners
|
|
76
|
+
* (drag/zoom gestures), the resize-handle listener (if `resizableFrame`),
|
|
77
|
+
* and the zoom-slider listener (if `showZoomer`); clears the container's
|
|
78
|
+
* `innerHTML`, leaving an empty container element; and clears all
|
|
79
|
+
* `"change"` listeners. Call this when you're done with an instance (e.g.
|
|
80
|
+
* unmounting) to avoid leaking listeners.
|
|
81
|
+
*/
|
|
82
|
+
destroy(): void;
|
|
83
|
+
private getFrameSize;
|
|
84
|
+
private syncStageSize;
|
|
85
|
+
private commitState;
|
|
86
|
+
private enableZoomer;
|
|
87
|
+
private enableFrameResize;
|
|
88
|
+
}
|