Mixins / foldkit-mixins-ui

ImageCropper

namespace · packages/mixins-ui/src/imageCropper.ts:1

export * as ImageCropper from './imageCropper.js'

In the guides

Members

ImageCropper.crop

function · packages/mixins-ui/src/imageCropper.ts:404

function crop(model: Model): Option.Option<Rect>

The crop in whole image pixels, as Export draws it; none before the image loads.

ImageCropper.Export

const · packages/mixins-ui/src/imageCropper.ts:418

const Export: Command.CommandDefinitionWithArgs<
  'ImageCropper.Export',
  {
    image: Schema.String
    natural: Schema.Struct<{
      readonly width: Schema.Number
      readonly height: Schema.Number
    }>
    rotation: Schema.Literals<readonly [0, 90, 180, 270]>
    crop: Schema.Struct<{
      readonly x: Schema.Number
      readonly y: Schema.Number
      readonly width: Schema.Number
      readonly height: Schema.Number
    }>
    round: Schema.Boolean
    type: Schema.Literals<readonly ['image/png', 'image/jpeg', 'image/webp']>
    quality: Schema.Number
  },
  Effect.Effect<
    | { readonly _tag: 'FailedExport'; readonly reason: string }
    | {
        readonly _tag: 'Exported'
        readonly url: string
        readonly width: number
        readonly height: number
      },
    never,
    never
  >
>

Draws the crop of the image the view drew (found by id) on a canvas, turned and, for a round shape, clipped to its ellipse with the corners left clear.

ImageCropper.ImageCropperLabels

interface · packages/mixins-ui/src/imageCropper.ts:791

interface ImageCropperLabels {
  readonly selection: string
  readonly zoom: string
  readonly rotateLeft: string
  readonly rotateRight: string
  readonly reset: string
  readonly crop: (crop: Rect) => string
}

The words a cropper says; each has an English default.

ImageCropper.ImageCropperSlots

const · packages/mixins-ui/src/imageCropper.ts:49

const ImageCropperSlots: Slots.Contract<{ readonly root: Slot.UnnamedSlot<Capability.Capability<"Container", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly [Attr.AttrToken<"role">], readonly Slot.RequirementName[], boolean>; readonly label: Slot.UnnamedSlot<Capability.Capability<"Container", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>; readonly viewport: Slot.UnnamedSlot<Capability.Capability<"Container", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>; readonly stage: Slot.UnnamedSlot<Capability.Capability<"Container", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>; readonly pan: Slot.UnnamedSlot<Capability.Capability<"Draggable", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>; readonly image: Slot.UnnamedSlot<Capability.Capability<"Base", readonly []>, readonly Slot.EventName[], reado …;

An image cropper after Zag's: the image fitted in a viewport, a crop selection over it that a drag moves and eight handles resize, the rest of the image veiled, zoom (a Slider, the wheel, + and -), quarter turns, an aspect-ratio lock and a round crop. Export draws the crop on a canvas and answers with it as a data URL.

The crop is a small Model of this module's in the image's own pixels, of the image as turned: a 4000 by 3000 photo turned a quarter is 3000 wide, and a crop of its left half is { x: 0, y: 0, width: 1500, height: 4000 }. The view measures the viewport (Resize) and reads the image's natural size when it loads, and every drag reaches update as the Move Mount's distance from where the pointer went down, which update applies to the crop the drag began with, so a drag never drifts.

Zoom keeps the selection where it is on screen and magnifies the image under it, so zooming in crops less of the image. While zoomed in, a drag on the image outside the selection pans: the view and the crop move together over the image. The selection stays inside the image and inside what the viewport shows.

The selection is one focusable role="slider" (Zag's "2D slider"; the APG has no cropper) whose aria-valuetext says the crop: the arrows move it by a pixel of the screen (Shift by ten), Alt with the arrows resizes it from its far edges, and + and - zoom. The keys are physical, as the plane is, in either direction. The handles are for the pointer and hidden from assistive technology.

The viewport carries the geometry for the recipe as custom properties in pixels: --fk-cropper-x, -y, -w, -h (the selection), --fk-cropper-image-x, -y, -w, -h (the turned image's box), --fk-cropper-natural-w, -h (the image element before it is turned) and --fk-cropper-rotation.

ImageCropper.ImageCropperView

interface · packages/mixins-ui/src/imageCropper.ts:811

interface ImageCropperView<Message> {
  readonly model: Model
  readonly onMessage: (message: typeof Message.Type) => NoInfer<Message>
  readonly label: string
  readonly src: string
  readonly crossOrigin?: 'anonymous' | 'use-credentials' | undefined
  readonly controls?:
    | {
        readonly zoom?: boolean
        readonly rotate?: boolean
        readonly reset?: boolean
      }
    | undefined
  readonly labels?: Partial<ImageCropperLabels> | undefined
  readonly disabled?: boolean | undefined
  readonly style?: NamedStyle<typeof ImageCropperSlots> | undefined
  readonly slider?: NamedStyle<typeof SliderSlots> | undefined
  readonly mixins?: MixinList<Message> | undefined
}

ImageCropper.init

function · packages/mixins-ui/src/imageCropper.ts:321

function init(options: {
  readonly id: string
  readonly aspect?: number | undefined
  readonly shape?: Shape | undefined
  readonly crop?: Rect | undefined
  readonly min?: Size | undefined
  readonly maxZoom?: number | undefined
}): Model

ImageCropper.Message

const · packages/mixins-ui/src/imageCropper.ts:352

const Message: MessageUnion<{
  readonly MeasuredViewport: {
    readonly width: Schema.Number
    readonly height: Schema.Number
  }
  readonly LoadedImage: {
    readonly width: Schema.Number
    readonly height: Schema.Number
  }
  readonly StartedDrag: {
    readonly part: Schema.Literals<
      readonly [
        ...('s' | 'w' | 'n' | 'ne' | 'e' | 'se' | 'sw' | 'nw')[],
        'selection',
        'image',
      ]
    >
  }
  readonly Dragged: {
    readonly part: Schema.Literals<
      readonly [
        ...('s' | 'w' | 'n' | 'ne' | 'e' | 'se' | 'sw' | 'nw')[],
        'selection',
        'image',
      ]
    >
    readonly deltaX: Schema.Number
    readonly deltaY: Schema.Number
  }
  readonly EndedDrag: {}
  readonly Wheeled: { readonly deltaY: Schema.Number }
  readonly PressedKey: {
    readonly key: Schema.String
    readonly shift: Schema.Boolean
    readonly alt: Schema.Boolean
  }
  readonly ChangedZoom: { readonly zoom: Schema.Number }
  readonly Turned: { readonly clockwise: Schema.Boolean }
  readonly ChangedAspect: {
    readonly aspect: Schema.OptionFromNullOr<Schema.Number>
  }
  readonly Reset: {}
  readonly RequestedExport: {
    readonly type: Schema.Literals<
      readonly ['image/png', 'image/jpeg', 'image/webp']
    >
    readonly quality: Schema.Number
  }
  readonly Exported: {
    readonly url: Schema.String
    readonly width: Schema.Number
    readonly height: Schema.Number
  }
  readonly FailedExport: { readonly reason: Schema.String }
}>

ImageCropper.Model

const · packages/mixins-ui/src/imageCropper.ts:297

const Model: Schema.Struct<{ readonly id: Schema.String; readonly shape: Schema.Literals<readonly ["rectangle", "round"]>; readonly aspect: Schema.OptionFromNullOr<Schema.Number>; readonly min: Schema.Struct<{ readonly width: Schema.Number; readonly height: Schema.Number; }>; readonly zoom: Schema.Number; readonly maxZoom: Schema.Number; readonly rotation: Schema.Literals<readonly [0, 90, 180, 270]>; readonly initial: Schema.OptionFromNullOr<Schema.Struct<{ readonly x: Schema.Number; readonly y: Schema.Number; readonly width: Schema.Number; readonly height: Schema.Number; }>>; readonly viewport: Schema.OptionFromNullOr<Schema.Struct<{ readonly width: Schema.Number; readonly height: Schema.Number; }>>; readonly image: Schema.OptionFromNullOr<Schema.Struct<{ readonly natural: Schema.Struct<{ readonly width: Schema.Number; readonly height: Schema.Number; }>; readonly crop: Schema.Struct<{ readonly x: Schema.Number; readonly y: Schema.Number; readonly width: Schema.Number; readonly height: Schema.Number; }>; readonly focus: Schema.Struct<{ readonly x: Schema.Number; readonly y: Schema.Number; }>; }>>; readonly drag: Schema.OptionFromNullOr<Schema.Struct<{ readonly part: Schema.Literals<readonly [...("s" | "w" | "n" | "ne" | "e" | "se" | "sw" | "nw")[], "selection", "image"]>; readonly crop: Schema.Struct<{ readonly x: Schema.Number; readonly y: Schema.Number; readonly width: Schema.Number; readonly height: Schema.Number; }>; readonly focus: Schema.Struct<{ readonly x: Schema.Number; readonly  …;

ImageCropper.Rect

const · packages/mixins-ui/src/imageCropper.ts:89

const Rect: Schema.Struct<{
  readonly x: Schema.Number
  readonly y: Schema.Number
  readonly width: Schema.Number
  readonly height: Schema.Number
}>

A rectangle in the turned image's pixels.

ImageCropper.Rotation

const · packages/mixins-ui/src/imageCropper.ts:98

const Rotation: Schema.Literals<readonly [0, 90, 180, 270]>

Quarter turns clockwise, in degrees.

ImageCropper.Shape

const · packages/mixins-ui/src/imageCropper.ts:102

const Shape: Schema.Literals<readonly ['rectangle', 'round']>

round crops the ellipse inside the rectangle, a circle at an aspect of 1.

ImageCropper.Size

const · packages/mixins-ui/src/imageCropper.ts:82

const Size: Schema.Struct<{
  readonly width: Schema.Number
  readonly height: Schema.Number
}>

ImageCropper.update

function · packages/mixins-ui/src/imageCropper.ts:592

function update(model: Model, message: Message): Return<Model, Message>

The cropper after message; the same Model when nothing changes.

ImageCropper.view

function · packages/mixins-ui/src/imageCropper.ts:837

function view<Outer>(
  options: ImageCropperView<Outer>,
  h: HtmlBuilder<Outer>,
): Html