@scaleflex/crop 2.0.1

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +452 -0
  3. package/dist/a11y/aria.d.ts +5 -0
  4. package/dist/a11y/keyboard.d.ts +13 -0
  5. package/dist/animation/lerp.d.ts +15 -0
  6. package/dist/animation/spring.d.ts +15 -0
  7. package/dist/canvas/bleed-layer.d.ts +6 -0
  8. package/dist/canvas/crop-frame.d.ts +32 -0
  9. package/dist/canvas/grid-layer.d.ts +7 -0
  10. package/dist/canvas/hit-test.d.ts +10 -0
  11. package/dist/canvas/image-layer.d.ts +28 -0
  12. package/dist/canvas/overlay-layer.d.ts +6 -0
  13. package/dist/canvas/renderer.d.ts +34 -0
  14. package/dist/chunks/sfx-crop-1LGASewd.cjs +353 -0
  15. package/dist/chunks/sfx-crop-CEe6OfTZ.js +2030 -0
  16. package/dist/core/config.d.ts +10 -0
  17. package/dist/core/crop-controller.d.ts +65 -0
  18. package/dist/core/types.d.ts +270 -0
  19. package/dist/define.cjs +1194 -0
  20. package/dist/define.d.ts +1 -0
  21. package/dist/define.js +1746 -0
  22. package/dist/elements/base.d.ts +17 -0
  23. package/dist/elements/icons.d.ts +21 -0
  24. package/dist/elements/parse-shapes.d.ts +13 -0
  25. package/dist/elements/popover-anchor.d.ts +20 -0
  26. package/dist/elements/sfx-crop-canvas.d.ts +24 -0
  27. package/dist/elements/sfx-crop-canvas.styles.d.ts +1 -0
  28. package/dist/elements/sfx-crop-rotate.d.ts +42 -0
  29. package/dist/elements/sfx-crop-rotate.styles.d.ts +5 -0
  30. package/dist/elements/sfx-crop-shapes.d.ts +67 -0
  31. package/dist/elements/sfx-crop-shapes.styles.d.ts +6 -0
  32. package/dist/elements/sfx-crop-toolbar.d.ts +64 -0
  33. package/dist/elements/sfx-crop-toolbar.styles.d.ts +7 -0
  34. package/dist/elements/sfx-crop-zoom.d.ts +66 -0
  35. package/dist/elements/sfx-crop-zoom.styles.d.ts +7 -0
  36. package/dist/elements/sfx-crop.d.ts +134 -0
  37. package/dist/elements/sfx-crop.styles.d.ts +9 -0
  38. package/dist/export/exporter.d.ts +19 -0
  39. package/dist/index.cjs +2 -0
  40. package/dist/index.d.ts +22 -0
  41. package/dist/index.js +65 -0
  42. package/dist/interactions/drag-crop.d.ts +10 -0
  43. package/dist/interactions/pinch-zoom.d.ts +14 -0
  44. package/dist/interactions/pointer-tracker.d.ts +29 -0
  45. package/dist/interactions/resize-handles.d.ts +13 -0
  46. package/dist/interactions/wheel-zoom.d.ts +12 -0
  47. package/dist/react/define-CVJd5aYk.cjs +1545 -0
  48. package/dist/react/define-t4Z6KaLY.js +2590 -0
  49. package/dist/react/index-B-csHwK2.cjs +2 -0
  50. package/dist/react/index-CktjrogS.js +1468 -0
  51. package/dist/react/index.cjs +2 -0
  52. package/dist/react/index.d.ts +21 -0
  53. package/dist/react/index.js +10 -0
  54. package/dist/react/sfx-crop.d.ts +86 -0
  55. package/dist/react/use-sfx-crop-controller.d.ts +74 -0
  56. package/dist/react/use-sfx-crop.d.ts +31 -0
  57. package/dist/styles/shared.css.d.ts +20 -0
  58. package/dist/transforms/constrain.d.ts +68 -0
  59. package/dist/transforms/matrix.d.ts +23 -0
  60. package/dist/transforms/transform-state.d.ts +12 -0
  61. package/dist/utils/events.d.ts +16 -0
  62. package/dist/utils/math.d.ts +12 -0
  63. package/package.json +108 -0
@@ -0,0 +1,17 @@
1
+ import { LitElement } from 'lit';
2
+ /**
3
+ * Shared base class for all @scaleflex/crop Lit elements.
4
+ *
5
+ * Provides:
6
+ * - Safe guard for double custom-element registration (StrictMode-safe).
7
+ * - A hook point for future cross-cutting concerns (telemetry, theme, etc.).
8
+ *
9
+ * Scaleflex convention — every public element extends this.
10
+ */
11
+ export declare class SfxCropBaseElement extends LitElement {
12
+ }
13
+ /**
14
+ * Idempotent custom-element registration — safe under React StrictMode
15
+ * double-mount and repeated `import '.../define.js'` calls.
16
+ */
17
+ export declare function safeDefine(tagName: string, ctor: CustomElementConstructor): void;
@@ -0,0 +1,21 @@
1
+ import { CropIconOverrides } from '../core/types';
2
+ export declare const ICON_ROTATE_LEFT = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 9V7a2 2 0 0 0-2-2h-6\"/><path d=\"m15 8-3-3 3-3\"/><path d=\"M4 14a2 2 0 0 0-2 2v3a2 2 0 0 0 2 2h13a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2Z\"/></svg>";
3
+ export declare const ICON_FLIP_H = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3\"/><path d=\"M16 3h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-3\"/><path d=\"M12 20v2\"/><path d=\"M12 15v2\"/><path d=\"M12 10v2\"/><path d=\"M12 5v2\"/></svg>";
4
+ export declare const ICON_CROP_CUSTOM = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 2v14a2 2 0 0 0 2 2h14\"/><path d=\"M18 22V8a2 2 0 0 0-2-2H2\"/></svg>";
5
+ export declare const ICON_ORIENT_LANDSCAPE = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"20\" height=\"14\" x=\"2\" y=\"3\" rx=\"2\"/><line x1=\"8\" x2=\"16\" y1=\"21\" y2=\"21\"/><line x1=\"12\" x2=\"12\" y1=\"17\" y2=\"21\"/></svg>";
6
+ export declare const ICON_ORIENT_PORTRAIT = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"14\" height=\"20\" x=\"5\" y=\"2\" rx=\"2\" ry=\"2\"/><path d=\"M12 18h.01\"/></svg>";
7
+ export declare const ICON_CROP_CIRCLE = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/></svg>";
8
+ export declare const ICON_CROP_ROUNDED_RECT = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"5\"/></svg>";
9
+ export declare const ICON_CROP_ASPECT = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"12\" height=\"20\" x=\"6\" y=\"2\" rx=\"2\"/><rect width=\"20\" height=\"12\" x=\"2\" y=\"6\" rx=\"2\"/></svg>";
10
+ export declare const ICON_CHEVRON_DOWN = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 9 6 6 6-6\"/></svg>";
11
+ export declare const ICON_TILT = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 22h20\"/><rect x=\"3\" y=\"6\" width=\"18\" height=\"14\" rx=\"2\" transform=\"rotate(-10 12 13)\"/></svg>";
12
+ export declare const ICON_ZOOM_IN = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" x2=\"16.65\" y1=\"21\" y2=\"16.65\"/><line x1=\"11\" x2=\"11\" y1=\"8\" y2=\"14\"/><line x1=\"8\" x2=\"14\" y1=\"11\" y2=\"11\"/></svg>";
13
+ export declare const ICON_ZOOM_OUT = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" x2=\"16.65\" y1=\"21\" y2=\"16.65\"/><line x1=\"8\" x2=\"14\" y1=\"11\" y2=\"11\"/></svg>";
14
+ export declare const ICON_LOUPE = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" x2=\"16.65\" y1=\"21\" y2=\"16.65\"/><line x1=\"11\" x2=\"11\" y1=\"8\" y2=\"14\"/><line x1=\"8\" x2=\"14\" y1=\"11\" y2=\"11\"/></svg>";
15
+ export declare const ICON_RESET = "<svg aria-hidden=\"true\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 12a9 9 0 1 0 3-6.7L3 8\"/><path d=\"M3 3v5h5\"/></svg>";
16
+ /**
17
+ * Resolve an icon slot. Returns the consumer's override when present, else
18
+ * the library's default SVG. `overrides` is typically the `icons` property
19
+ * prop passed down from `<sfx-crop>` to each toolbar / popover sub-element.
20
+ */
21
+ export declare function resolveIcon(key: keyof CropIconOverrides, overrides?: CropIconOverrides): string;
@@ -0,0 +1,13 @@
1
+ import { CropShapeName } from '../core/types';
2
+ declare const DEFAULT_SHAPES: CropShapeName[];
3
+ /**
4
+ * Normalize the `availableShapes` / `available-shapes` input across the three
5
+ * shapes an attribute/property can take: a live `CropShapeName[]` assigned via
6
+ * property binding, a JSON array (`'["free","circle"]'`), or a CSV/whitespace
7
+ * string (`'free, circle'`).
8
+ *
9
+ * Returns `undefined` when the caller didn't specify anything and wants the
10
+ * default list — element/toolbar consumers fall back to {@link DEFAULT_SHAPES}.
11
+ */
12
+ export declare function parseAvailableShapes(v: CropShapeName[] | string | null | undefined): CropShapeName[] | undefined;
13
+ export { DEFAULT_SHAPES };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Position a slider popover (`.sfx-cr-*-popover`) at the bottom of the
3
+ * `<sfx-crop>` canvas rect, regardless of where its trigger sits in the
4
+ * toolbar. The popover uses `position: fixed` with CSS variables
5
+ * `--sfx-cr-popover-left` / `--sfx-cr-popover-top` that this helper writes
6
+ * on the popover element.
7
+ *
8
+ * The `<sfx-crop>` host's bounding rect matches the rendered canvas area
9
+ * (see `SfxCropElement.fitHostToImage`), so anchoring to its bottom edge
10
+ * places the ruler over the lower strip of the photo.
11
+ */
12
+ export interface PopoverAnchor {
13
+ /** Keep the popover in sync with the canvas rect while it's open. */
14
+ start(): void;
15
+ /** Stop observing + detach listeners. Idempotent. */
16
+ stop(): void;
17
+ /** Recompute the popover's top/left from the current canvas rect. */
18
+ update(): void;
19
+ }
20
+ export declare function createPopoverAnchor(element: HTMLElement, popoverSelector: string): PopoverAnchor;
@@ -0,0 +1,24 @@
1
+ import { SfxCropBaseElement } from './base';
2
+ /**
3
+ * `<sfx-crop-canvas>` — minimal host for the editor's `<canvas>`.
4
+ *
5
+ * Owns its own shadow root so the canvas styling + `touch-action: none` live
6
+ * encapsulated alongside the `<canvas>` node. The parent `<sfx-crop>`'s host-
7
+ * level `--sfx-cr-*` tokens cascade in via CSS custom-property inheritance.
8
+ *
9
+ * The `<canvas>` is rendered once and never re-created, so `setPointerCapture`,
10
+ * non-passive `wheel` listeners, and the ResizeObserver bound by the
11
+ * controller stay stable across Lit updates.
12
+ *
13
+ * Theme a consumer via `::part(canvas)` from the parent host.
14
+ */
15
+ export declare class SfxCropCanvasElement extends SfxCropBaseElement {
16
+ static styles: import('lit').CSSResult[];
17
+ canvasEl: HTMLCanvasElement;
18
+ render(): unknown;
19
+ }
20
+ declare global {
21
+ interface HTMLElementTagNameMap {
22
+ 'sfx-crop-canvas': SfxCropCanvasElement;
23
+ }
24
+ }
@@ -0,0 +1 @@
1
+ export declare const sfxCropCanvasStyles: import('lit').CSSResult;
@@ -0,0 +1,42 @@
1
+ import { PropertyValues } from 'lit';
2
+ import { SfxCropBaseElement } from './base';
3
+ import { CropIconOverrides } from '../core/types';
4
+ /**
5
+ * `<sfx-crop-rotate>` — always-visible fine-rotation ruler.
6
+ *
7
+ * Ticks scroll under a fixed center marker; the current degrees label sits
8
+ * below. Drag the ruler left/right to tilt; double-click to reset.
9
+ *
10
+ * Event:
11
+ * `sfx-crop-rotate-change` — `{ detail: { degrees: number } }`,
12
+ * bubbles + composed.
13
+ */
14
+ export declare class SfxCropRotateElement extends SfxCropBaseElement {
15
+ static styles: import('lit').CSSResult[];
16
+ value: number;
17
+ min: number;
18
+ max: number;
19
+ icons: CropIconOverrides;
20
+ private dragging;
21
+ private activePointer;
22
+ private pointerStartX;
23
+ private pointerStartValue;
24
+ private popoverAnchor;
25
+ connectedCallback(): void;
26
+ disconnectedCallback(): void;
27
+ updated(changed: PropertyValues): void;
28
+ render(): unknown;
29
+ /** Sync value without emitting — used by host to reflect controller state. */
30
+ setValue(degrees: number): void;
31
+ private onKeyDown;
32
+ private onPointerDown;
33
+ private onPointerMove;
34
+ private onPointerUp;
35
+ private emit;
36
+ private onReset;
37
+ }
38
+ declare global {
39
+ interface HTMLElementTagNameMap {
40
+ 'sfx-crop-rotate': SfxCropRotateElement;
41
+ }
42
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Fine-rotation ruler — always visible inline in the toolbar. Dotted tick
3
+ * marks scroll under a centered indicator with the degree readout below.
4
+ */
5
+ export declare const sfxCropRotateStyles: import('lit').CSSResult;
@@ -0,0 +1,67 @@
1
+ import { PropertyValues } from 'lit';
2
+ import { SfxCropBaseElement } from './base';
3
+ import { CropShapeName, CropIconOverrides } from '../core/types';
4
+ /**
5
+ * `<sfx-crop-shapes>` — trigger + dropdown with aspect-ratio / shape presets.
6
+ * Split into an orientation toggle (landscape / portrait) at the top, then a
7
+ * list of shapes that pass both the `shapes` allow-list and the selected
8
+ * orientation filter. Non-ratio shapes (Custom, Circle, Rounded, 1:1) sit in
9
+ * both orientation tabs so they're never hidden by a toggle click.
10
+ *
11
+ * Keyboard: Enter/Space toggles the dropdown; Arrow keys navigate options;
12
+ * Enter/Space commits; Escape closes.
13
+ *
14
+ * Event:
15
+ * `sfx-crop-shape-change` — `{ detail: { shape: CropShapeName } }`,
16
+ * bubbles + composed.
17
+ */
18
+ export declare class SfxCropShapesElement extends SfxCropBaseElement {
19
+ static styles: import('lit').CSSResult[];
20
+ value: CropShapeName;
21
+ /** Supported shape names. Default covers the canonical ratio pairs +
22
+ * Free + Square. `Circle` / `rounded-rect` are built-in geometry
23
+ * shapes but must be opted into explicitly. Any `"W:H"` string is
24
+ * accepted — the dropdown generates a proportional icon for it. */
25
+ shapes: CropShapeName[];
26
+ open: boolean;
27
+ /** Reflected so the trigger can pick up a translucent pill in the fixed variant. */
28
+ variant: 'classic' | 'fixed';
29
+ icons: CropIconOverrides;
30
+ private orientation;
31
+ private focusedIndex;
32
+ private focusRafId;
33
+ private docClickHandler;
34
+ connectedCallback(): void;
35
+ disconnectedCallback(): void;
36
+ updated(changed: PropertyValues): void;
37
+ render(): unknown;
38
+ /**
39
+ * Pick the icon for a dropdown option, consulting consumer overrides
40
+ * for the three non-ratio geometry shapes. Aspect-ratio options stay
41
+ * on their proportional rectangle — not overridable, and that's
42
+ * intentional since each one is generated from the ratio itself.
43
+ */
44
+ private optionIcon;
45
+ /** Sync value without emitting — used by host. */
46
+ setValue(shape: CropShapeName): void;
47
+ private setOrientation;
48
+ /**
49
+ * Options visible under the current orientation tab, respecting the
50
+ * `shapes` allow-list. Each allowed name is resolved via `resolveShape`
51
+ * so consumer-supplied `"W:H"` strings (not in SHAPE_MAP) get a dynamic
52
+ * option with a proportional icon. Sorted: named shapes first in a
53
+ * fixed order, then ratios by numeric value.
54
+ */
55
+ private getVisibleOptions;
56
+ private emit;
57
+ private close;
58
+ private onTriggerClick;
59
+ private onOptionClick;
60
+ private onKeyDown;
61
+ private focusOption;
62
+ }
63
+ declare global {
64
+ interface HTMLElementTagNameMap {
65
+ 'sfx-crop-shapes': SfxCropShapesElement;
66
+ }
67
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Capsule shape-selector. Trigger + dropdown follow uploader's source-pill
3
+ * language: glassy trigger, primary tint on hover, 12px dropdown radius
4
+ * with a soft shadow, primary-blue accent on the active option.
5
+ */
6
+ export declare const sfxCropShapesStyles: import('lit').CSSResult;
@@ -0,0 +1,64 @@
1
+ import { SfxCropBaseElement } from './base';
2
+ import { CropShapeName, CropIconOverrides } from '../core/types';
3
+ /**
4
+ * Unified descriptor dispatched on `sfx-crop-toolbar-command` so the host
5
+ * `<sfx-crop>` routes interactions through a single handler.
6
+ */
7
+ export type SfxCropToolbarCommand = {
8
+ type: 'reset';
9
+ } | {
10
+ type: 'rotate-left';
11
+ } | {
12
+ type: 'flip-h';
13
+ } | {
14
+ type: 'rotation';
15
+ value: number;
16
+ } | {
17
+ type: 'scale';
18
+ value: number;
19
+ } | {
20
+ type: 'shape';
21
+ value: CropShapeName;
22
+ } | {
23
+ type: 'save';
24
+ };
25
+ /**
26
+ * `<sfx-crop-toolbar>` — composes rotate/flip buttons + the always-visible
27
+ * `<sfx-crop-rotate>` fine-rotation ruler + `<sfx-crop-shapes>` into the
28
+ * editor's action bar. Zoom is wheel-only and has no toolbar control.
29
+ */
30
+ export declare class SfxCropToolbarElement extends SfxCropBaseElement {
31
+ static styles: import('lit').CSSResult[];
32
+ shape: CropShapeName;
33
+ rotation: number;
34
+ showRotateButton: boolean;
35
+ showFlipButton: boolean;
36
+ showRotateSlider: boolean;
37
+ showShapeSelector: boolean;
38
+ toolbarPosition: 'top' | 'bottom';
39
+ /** Reflected so the stylesheet can center the bar over the fixed frame. */
40
+ variant: 'classic' | 'fixed';
41
+ /** JSON-serialized or CSV string on the attribute; `CropShapeName[]` via property. */
42
+ availableShapes: CropShapeName[] | string | null;
43
+ /** Consumer icon overrides propagated from `<sfx-crop>`. */
44
+ icons: CropIconOverrides;
45
+ private rotateEl?;
46
+ private shapesEl?;
47
+ render(): unknown;
48
+ /**
49
+ * Mutual exclusion for collapsible popovers. The rotate ruler is now
50
+ * inline (always visible), so only the shapes popover participates —
51
+ * keeping the handler future-proof if more popovers are added.
52
+ */
53
+ private onPopoverOpen;
54
+ /** Sync the rotation slider without firing an event. */
55
+ setRotationValue(degrees: number): void;
56
+ /** Sync the shape selector without firing an event. */
57
+ setShapeValue(shape: CropShapeName): void;
58
+ private emit;
59
+ }
60
+ declare global {
61
+ interface HTMLElementTagNameMap {
62
+ 'sfx-crop-toolbar': SfxCropToolbarElement;
63
+ }
64
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Floating pill toolbar — mirrors the glassy source-pill row in
3
+ * `@scaleflex/uploader`. The bar is centered horizontally above or below
4
+ * the canvas with a translucent white background, backdrop blur, and a
5
+ * soft shadow ring.
6
+ */
7
+ export declare const sfxCropToolbarStyles: import('lit').CSSResult;
@@ -0,0 +1,66 @@
1
+ import { PropertyValues } from 'lit';
2
+ import { SfxCropBaseElement } from './base';
3
+ import { CropIconOverrides } from '../core/types';
4
+ /**
5
+ * `<sfx-crop-zoom>` — loupe trigger + ruler scrubber popover.
6
+ *
7
+ * The trigger matches the toolbar's other icon buttons. Click opens a
8
+ * transparent ruler of dotted ticks under a fixed center indicator; drag
9
+ * left/right to scrub zoom. Ruler operates in logarithmic slider-space
10
+ * (so 1× is exactly at one end of the range proportional to `min/max`),
11
+ * and the center label shows the current percentage of the scale.
12
+ *
13
+ * Event:
14
+ * `sfx-crop-zoom-change` — `{ detail: { scale: number } }`, bubbles + composed.
15
+ */
16
+ export declare class SfxCropZoomElement extends SfxCropBaseElement {
17
+ static styles: import('lit').CSSResult[];
18
+ min: number;
19
+ max: number;
20
+ value: number;
21
+ open: boolean;
22
+ icons: CropIconOverrides;
23
+ private dragging;
24
+ private activePointer;
25
+ private pointerStartX;
26
+ private pointerStartSlider;
27
+ /**
28
+ * Timer id for {@link showTemporarily}'s auto-close. Non-null only while
29
+ * the popover was opened by wheel-zoom activity and is awaiting an idle
30
+ * window before collapsing again. Any manual interaction (trigger click,
31
+ * ruler drag) clears it so the popover stays open under the user's hand.
32
+ */
33
+ private autoCloseTimer;
34
+ private docClickHandler;
35
+ private popoverAnchor;
36
+ connectedCallback(): void;
37
+ disconnectedCallback(): void;
38
+ updated(changed: PropertyValues): void;
39
+ render(): unknown;
40
+ /** Sync value without emitting — used by host to reflect controller state. */
41
+ setValue(scale: number): void;
42
+ /**
43
+ * Open the popover and auto-close after `duration` ms of inactivity.
44
+ * Subsequent calls debounce the close (so a stream of wheel events keeps
45
+ * the slider visible). Manual interaction (trigger click, ruler drag)
46
+ * cancels the pending close.
47
+ *
48
+ * If the popover is already open via manual click (no timer armed) this
49
+ * is a no-op: a wheel burst must never hijack a user-opened popover into
50
+ * auto-closing.
51
+ */
52
+ showTemporarily(duration?: number): void;
53
+ private clearAutoClose;
54
+ private onTriggerClick;
55
+ private onKeyDown;
56
+ private onPointerDown;
57
+ private onPointerMove;
58
+ private onPointerUp;
59
+ private emit;
60
+ private onReset;
61
+ }
62
+ declare global {
63
+ interface HTMLElementTagNameMap {
64
+ 'sfx-crop-zoom': SfxCropZoomElement;
65
+ }
66
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Zoom popover: ruler-style scrubber with dotted tick marks and a
3
+ * centered indicator + percentage readout. No pill around the ruler —
4
+ * ticks float transparently over the photo; only the percent value gets
5
+ * a small glassy plate for readability.
6
+ */
7
+ export declare const sfxCropZoomStyles: import('lit').CSSResult;
@@ -0,0 +1,134 @@
1
+ import { PropertyValues } from 'lit';
2
+ import { CropShapeName, CropRect, TransformState, TransformParams, CropIconOverrides } from '../core/types';
3
+ import { SfxCropBaseElement } from './base';
4
+ /**
5
+ * `<sfx-crop>` — Scaleflex interactive image-crop editor web component.
6
+ *
7
+ * Renders `<sfx-crop-canvas>` plus an optional `<sfx-crop-toolbar>` and a zoom
8
+ * slider inside an open shadow root. A {@link createCropController} instance
9
+ * drives the canvas / pointer / keyboard pipeline against the pre-created
10
+ * `<canvas>` ref — the canvas node is never re-created, so pointer capture and
11
+ * non-passive `wheel` listeners stay stable across Lit updates.
12
+ *
13
+ * Events (all `bubbles: true, composed: true`):
14
+ * - `sfx-crop-ready` `{ element }`
15
+ * - `sfx-crop-image-load` `{ image }`
16
+ * - `sfx-crop-change` `TransformState`
17
+ * - `sfx-crop-crop-change` `CropRect` (image-pixel coords)
18
+ * - `sfx-crop-save` `{ blob, dataURL, params }` (from imperative `.save()`)
19
+ * - `sfx-crop-cancel` (from imperative `.cancel()`)
20
+ * - `sfx-crop-error` `{ error }`
21
+ *
22
+ * Theme via `--sfx-cr-*` custom properties set on the host, or via
23
+ * `::part(container|canvas-host|toolbar|loading|error)` from light DOM.
24
+ */
25
+ export declare class SfxCropElement extends SfxCropBaseElement {
26
+ static styles: import('lit').CSSResult[];
27
+ src: string;
28
+ cropShape: CropShapeName;
29
+ theme: 'light' | 'dark';
30
+ initialRotation: number;
31
+ initialScale: number;
32
+ minScale: number;
33
+ maxScale: number;
34
+ minCropSize: number;
35
+ handleSize: number;
36
+ borderRadius: number;
37
+ outputQuality: number;
38
+ maxOutputWidth: number;
39
+ maxOutputHeight: number;
40
+ bleedMarginSize: number;
41
+ animationSpeed: number;
42
+ handleColor: string;
43
+ overlayColor: string;
44
+ bleedMarginColor: string;
45
+ outputType: string;
46
+ toolbarPosition: 'bottom' | 'top';
47
+ /**
48
+ * Display variant. `'classic'` (default) = movable/resizable frame over a
49
+ * photo-aspect canvas. `'fixed'` = the editor box IS the crop frame (sized to
50
+ * `cropShape`), with the photo cover-fit and panned underneath; toolbar
51
+ * overlays the frame.
52
+ */
53
+ variant: 'classic' | 'fixed';
54
+ /** `true | false | 'interaction'` (default). Attribute accepts all three as strings. */
55
+ showGrid: boolean | 'interaction';
56
+ showToolbar: boolean;
57
+ showRotateSlider: boolean;
58
+ showZoomSlider: boolean;
59
+ showShapeSelector: boolean;
60
+ showRotateButton: boolean;
61
+ showFlipButton: boolean;
62
+ showBleedMargin: boolean;
63
+ enableAnimations: boolean;
64
+ keyboard: boolean;
65
+ pinchZoom: boolean;
66
+ wheelZoom: boolean;
67
+ availableShapes: CropShapeName[] | string;
68
+ initialCrop: CropRect | string | null;
69
+ /**
70
+ * Per-slot icon overrides. Values are raw SVG strings injected via
71
+ * `unsafeHTML` — same trust model as the library's built-in icons
72
+ * (static, author-trusted). Omit any slot to keep the default.
73
+ * Not an HTML attribute; set via DOM property only.
74
+ */
75
+ icons: CropIconOverrides;
76
+ private loading;
77
+ private errorMessage;
78
+ private canvasHost;
79
+ private toolbarHost?;
80
+ private containerEl;
81
+ private controller;
82
+ private currentImage;
83
+ private parentResizeObserver;
84
+ firstUpdated(): Promise<void>;
85
+ /** @see LIVE_CONFIG_KEYS for the exact set forwarded to `controller.update()`. */
86
+ updated(changed: PropertyValues): void;
87
+ disconnectedCallback(): void;
88
+ render(): unknown;
89
+ private onToolbarCommand;
90
+ loadImage(src: string): Promise<void>;
91
+ getTransformState(): TransformState;
92
+ setCropShape(shape: CropShapeName): void;
93
+ setCropRect(rect: CropRect): void;
94
+ getCropRect(): CropRect;
95
+ rotateLeft(): void;
96
+ flipHorizontal(): void;
97
+ setRotation(deg: number): void;
98
+ setScale(scale: number): void;
99
+ reset(): void;
100
+ toCanvas(): HTMLCanvasElement;
101
+ toBlob(type?: string, quality?: number): Promise<Blob>;
102
+ toDataURL(type?: string, quality?: number): string;
103
+ toTransformParams(): TransformParams;
104
+ save(type?: string, quality?: number): Promise<void>;
105
+ cancel(): void;
106
+ private ensure;
107
+ /**
108
+ * Size both the canvas host (to the image's display rect, no letterbox)
109
+ * and the outer `<sfx-crop>` host (photo rect + measured toolbar stack).
110
+ *
111
+ * Bound order for `max-width` / `max-height`:
112
+ * 1. Consumer-provided values on the host's own CSS / inline style.
113
+ * 2. Parent's client rect as a fallback.
114
+ * 3. Viewport size as a last resort.
115
+ *
116
+ * Called on image-load, on state changes (90° rotation swaps aspect),
117
+ * and whenever the parent resizes.
118
+ */
119
+ private fitHostToImage;
120
+ /**
121
+ * Aspect ratio (W/H) of the fixed-variant editor box for the current shape.
122
+ * `free` fills the host (uses the available-area aspect); `square` / `circle`
123
+ * / `rounded-rect` are square; ratio shapes use their parsed ratio.
124
+ */
125
+ private fixedFrameAspect;
126
+ private dispatch;
127
+ private parseInitialCrop;
128
+ private buildConfig;
129
+ }
130
+ declare global {
131
+ interface HTMLElementTagNameMap {
132
+ 'sfx-crop': SfxCropElement;
133
+ }
134
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `<sfx-crop>` host + container + loading/error overlay.
3
+ *
4
+ * Visual language tracks `@scaleflex/uploader`: Inter typography, 16px outer
5
+ * radius, deep soft shadow, primary-blue accents for spinners and focus.
6
+ * Works embedded on any page surface (light or dark); `--sfx-cr-*` tokens
7
+ * swap automatically when the host carries `theme="dark"`.
8
+ */
9
+ export declare const sfxCropStyles: import('lit').CSSResult;
@@ -0,0 +1,19 @@
1
+ import { TransformState, TransformParams } from '../core/types';
2
+ /** Calculate transform params in original image pixel coordinates. */
3
+ export declare function getTransformParams(state: TransformState, imageWidth: number, imageHeight: number): TransformParams;
4
+ /** Render the cropped/transformed image to a new canvas. */
5
+ export declare function renderToCanvas(image: HTMLImageElement, state: TransformState, maxWidth: number, maxHeight: number, cropShape?: string, borderRadius?: number,
6
+ /**
7
+ * Width (CSS px) of the editor's layout container at the moment of export.
8
+ * The renderer stores `state.panX/panY` in container CSS pixels; we draw
9
+ * at iw×ih image pixels, so pan must be rescaled by iw/containerWidth or
10
+ * the exported framing drifts under any non-zero pan (e.g. after zoom).
11
+ * Falls back to iw (no rescaling) when callers don't have the dim.
12
+ */
13
+ containerWidth?: number,
14
+ /** Editor box height (CSS px) — required for the fixed variant's cover math. */
15
+ containerHeight?: number,
16
+ /** Display variant — `'fixed'` switches to frame-box + cover export. */
17
+ variant?: 'classic' | 'fixed'): HTMLCanvasElement;
18
+ /** Export canvas as Blob. */
19
+ export declare function canvasToBlob(canvas: HTMLCanvasElement, type: string, quality: number): Promise<Blob>;
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("./chunks/sfx-crop-1LGASewd.cjs");function g(){return[1,0,0,1,0,0]}function a(t,n){return[t[0]*n[0]+t[2]*n[1],t[1]*n[0]+t[3]*n[1],t[0]*n[2]+t[2]*n[3],t[1]*n[2]+t[3]*n[3],t[0]*n[4]+t[2]*n[5]+t[4],t[1]*n[4]+t[3]*n[5]+t[5]]}function x(t,n){return[1,0,0,1,t,n]}function m(t,n){return[t,0,0,n,0,0]}function C(t){const n=s.degreesToRadians(t),e=Math.cos(n),i=Math.sin(n);return[e,i,-i,e,0,0]}function y(t,n,e,i,c,o,u,f){let r=g();r=a(r,x(t,n)),o!==1&&(r=a(r,m(o,o))),(u!==0||f!==0)&&(r=a(r,x(u,f)));const l=e+i;return l!==0&&(r=a(r,C(l))),c<0&&(r=a(r,m(-1,1))),r}function M(t,n,e){const i=n.width/2,c=n.height/2,o=y(i,c,t.quarterTurns,t.rotation,t.flipH?-1:1,t.scale,t.panX,t.panY);return new DOMMatrix([o[0],o[1],o[2],o[3],o[4],o[5]])}function T(t,n){const e=n.transformPoint({x:t.x,y:t.y});return{x:e.x,y:e.y}}function R(t,n){const i=n.inverse().transformPoint({x:t.x,y:t.y});return{x:i.x,y:i.y}}exports.DEFAULT_CONFIG=s.DEFAULT_CONFIG;exports.SfxCropElement=s.SfxCropElement;exports.TOOLBAR_RESERVE_PX=s.TOOLBAR_RESERVE_PX;exports.createCropController=s.createCropController;exports.getAspectRatio=s.getAspectRatio;exports.mergeConfig=s.mergeConfig;exports.parseRatio=s.parseRatio;exports.buildTransformMatrix=M;exports.canvasToImage=R;exports.imageToCanvas=T;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `@scaleflex/crop` — public, side-effect-free entry.
3
+ *
4
+ * Three tiers of adoption live here:
5
+ * 1. Ready component — `<sfx-crop>` via `@scaleflex/crop/define`.
6
+ * 2. (reserved for future sub-element assembly.)
7
+ * 3. Headless controller — `createCropController({ canvas, host, config })`
8
+ * drives pointer, keyboard, render, and export against a consumer-owned
9
+ * `<canvas>` with zero built-in UI. The custom element below is thin
10
+ * sugar on top of this same factory.
11
+ *
12
+ * No side effects — importing this module does not register any custom
13
+ * element. Import `@scaleflex/crop/define` for that. React consumers can
14
+ * import from `@scaleflex/crop/react` for a `<SfxCrop>` component + hooks.
15
+ */
16
+ export { SfxCropElement } from './elements/sfx-crop';
17
+ export { createCropController } from './core/crop-controller';
18
+ export type { CropController, CropControllerOptions, CropControllerCallbacks, } from './core/crop-controller';
19
+ export { mergeConfig, DEFAULT_CONFIG, TOOLBAR_RESERVE_PX } from './core/config';
20
+ export type { SfxCropConfig, TransformState, TransformParams, CropShapeName, CropShapeBuiltin, CropShape, CropShapeConfig, CropRect, NormalizedRect, DisplayState, HitTarget, HandlePosition, Point, Size, SpringConfig, LerpConfig, CropIconOverrides, } from './core/types';
21
+ export { parseRatio, getAspectRatio } from './transforms/constrain';
22
+ export { imageToCanvas, canvasToImage, buildTransformMatrix } from './transforms/matrix';
package/dist/index.js ADDED
@@ -0,0 +1,65 @@
1
+ import { d as l } from "./chunks/sfx-crop-CEe6OfTZ.js";
2
+ import { D as h, S as E, T as D, c as O, g as _, m as A, p as P } from "./chunks/sfx-crop-CEe6OfTZ.js";
3
+ function y() {
4
+ return [1, 0, 0, 1, 0, 0];
5
+ }
6
+ function s(t, n) {
7
+ return [
8
+ t[0] * n[0] + t[2] * n[1],
9
+ t[1] * n[0] + t[3] * n[1],
10
+ t[0] * n[2] + t[2] * n[3],
11
+ t[1] * n[2] + t[3] * n[3],
12
+ t[0] * n[4] + t[2] * n[5] + t[4],
13
+ t[1] * n[4] + t[3] * n[5] + t[5]
14
+ ];
15
+ }
16
+ function x(t, n) {
17
+ return [1, 0, 0, 1, t, n];
18
+ }
19
+ function m(t, n) {
20
+ return [t, 0, 0, n, 0, 0];
21
+ }
22
+ function M(t) {
23
+ const n = l(t), i = Math.cos(n), e = Math.sin(n);
24
+ return [i, e, -e, i, 0, 0];
25
+ }
26
+ function g(t, n, i, e, c, o, a, u) {
27
+ let r = y();
28
+ r = s(r, x(t, n)), o !== 1 && (r = s(r, m(o, o))), (a !== 0 || u !== 0) && (r = s(r, x(a, u)));
29
+ const f = i + e;
30
+ return f !== 0 && (r = s(r, M(f))), c < 0 && (r = s(r, m(-1, 1))), r;
31
+ }
32
+ function p(t, n, i) {
33
+ const e = n.width / 2, c = n.height / 2, o = g(
34
+ e,
35
+ c,
36
+ t.quarterTurns,
37
+ t.rotation,
38
+ t.flipH ? -1 : 1,
39
+ t.scale,
40
+ t.panX,
41
+ t.panY
42
+ );
43
+ return new DOMMatrix([o[0], o[1], o[2], o[3], o[4], o[5]]);
44
+ }
45
+ function C(t, n) {
46
+ const i = n.transformPoint({ x: t.x, y: t.y });
47
+ return { x: i.x, y: i.y };
48
+ }
49
+ function T(t, n) {
50
+ const e = n.inverse().transformPoint({ x: t.x, y: t.y });
51
+ return { x: e.x, y: e.y };
52
+ }
53
+ export {
54
+ h as DEFAULT_CONFIG,
55
+ E as SfxCropElement,
56
+ D as TOOLBAR_RESERVE_PX,
57
+ p as buildTransformMatrix,
58
+ T as canvasToImage,
59
+ O as createCropController,
60
+ _ as getAspectRatio,
61
+ C as imageToCanvas,
62
+ A as mergeConfig,
63
+ P as parseRatio
64
+ };
65
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,10 @@
1
+ import { CropRect } from '../core/types';
2
+ export interface DragCropState {
3
+ startCrop: CropRect;
4
+ startX: number;
5
+ startY: number;
6
+ }
7
+ /** Start dragging the crop area. */
8
+ export declare function startDragCrop(crop: CropRect, pointerX: number, pointerY: number): DragCropState;
9
+ /** Update crop position during drag. Returns new normalized crop rect. */
10
+ export declare function updateDragCrop(drag: DragCropState, pointerX: number, pointerY: number, imageWidth: number, imageHeight: number): CropRect;