{"name":"image-cropper","title":"Image Cropper","description":"An image editor with a draggable, resizable crop region, aspect-ratio lock, zoom, rotation and flip.","installCommand":"bunx marko-ui add image-cropper -y","usageTags":"<ImageCropper>","importSnippet":"import ImageCropper from \"@/components/ui/image-cropper/image-cropper.marko\";","usageSnippet":"static const imageUrl = \"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=800\";\n\n<ImageCropper src=imageUrl alt=\"Photo to crop\"/>","tags":["ImageCropper"],"parts":[],"props":[{"name":"alt","type":"string","required":false,"description":"Accessible description of the image being cropped."},{"name":"aspectRatio","type":"number | undefined","required":false,"description":"The aspect ratio to maintain for the crop area (width / height).\nFor example, an aspect ratio of 16 / 9 will maintain a width to height ratio of 16:9.\nIf not provided, the crop area can be freely resized."},{"name":"cropShape","type":"'circle' | 'rectangle' | undefined","required":false,"default":"rectangle","description":"The shape of the crop area."},{"name":"defaultFlip","type":"FlipState | undefined","required":false,"description":"The initial flip state to apply to the image."},{"name":"defaultRotation","type":"number | undefined","required":false,"default":"0","description":"The initial rotation to apply to the image in degrees."},{"name":"defaultZoom","type":"number | undefined","required":false,"default":"1","description":"The initial zoom factor to apply to the image."},{"name":"dir","type":"'ltr' | 'rtl' | undefined","required":false,"default":"ltr","description":"The document's text/writing direction."},{"name":"fixedCropArea","type":"boolean | undefined","required":false,"default":"false","description":"Whether the crop area is fixed in size and position."},{"name":"flip","type":"FlipState | undefined","required":false,"description":"The controlled flip state of the image."},{"name":"getRootNode","type":"(() => ShadowRoot | Document | Node) | undefined","required":false,"description":"A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron."},{"name":"hideGrid","type":"boolean","required":false,"description":"Hide the rule-of-thirds grid drawn inside the crop selection."},{"name":"ids","type":"ElementIds | undefined","required":false,"description":"The ids of the image cropper elements"},{"name":"initialCrop","type":"Rect | undefined","required":false,"description":"The initial rectangle of the crop area.\nIf not provided, a smart default will be computed based on viewport size and aspect ratio."},{"name":"maxHeight","type":"number | undefined","required":false,"default":"Infinity","description":"The maximum height of the crop area"},{"name":"maxWidth","type":"number | undefined","required":false,"default":"Infinity","description":"The maximum width of the crop area"},{"name":"maxZoom","type":"number | undefined","required":false,"default":"5","description":"The maximum zoom factor allowed."},{"name":"minHeight","type":"number | undefined","required":false,"default":"40","description":"The minimum height of the crop area"},{"name":"minWidth","type":"number | undefined","required":false,"default":"40","description":"The minimum width of the crop area"},{"name":"minZoom","type":"number | undefined","required":false,"default":"1","description":"The minimum zoom factor allowed."},{"name":"nudgeStep","type":"number | undefined","required":false,"default":"1","description":"The base nudge step for keyboard arrow keys (in pixels)."},{"name":"nudgeStepCtrl","type":"number | undefined","required":false,"default":"50","description":"The nudge step when Ctrl/Cmd key is held (in pixels)."},{"name":"nudgeStepShift","type":"number | undefined","required":false,"default":"10","description":"The nudge step when Shift key is held (in pixels)."},{"name":"rotation","type":"number | undefined","required":false,"description":"The controlled rotation of the image in degrees (0 - 360)."},{"name":"src","type":"string","required":false,"description":"Source of the image to crop."},{"name":"translations","type":"IntlTranslations | undefined","required":false,"description":"Specifies the localized strings that identify accessibility elements and their states."},{"name":"zoom","type":"number | undefined","required":false,"description":"The controlled zoom level of the image."},{"name":"zoomSensitivity","type":"number | undefined","required":false,"default":"2","description":"Controls how responsive pinch-to-zoom is."},{"name":"zoomStep","type":"number | undefined","required":false,"default":"0.1","description":"The amount of zoom applied per wheel step."}],"events":[{"name":"cropChange","arg":"imageCropperMachine.CropChangeDetails['crop']","description":"Sugar for onCropChange — receives the plain crop rect."},{"name":"flipChange","arg":"imageCropperMachine.FlipState","description":"Sugar for onFlipChange — receives the plain flip state."},{"name":"onCropChange","arg":"CropChangeDetails","description":"Callback fired when the crop area changes."},{"name":"onFlipChange","arg":"FlipChangeDetails","description":"Callback fired when the flip state changes."},{"name":"onRotationChange","arg":"RotationChangeDetails","description":"Callback fired when the rotation changes."},{"name":"onZoomChange","arg":"ZoomChangeDetails","description":"Callback fired when the zoom level changes."},{"name":"rotationChange","arg":"number","description":"Sugar for onRotationChange — receives the plain rotation in degrees."},{"name":"zoomChange","arg":"number","description":"Sugar for onZoomChange — receives the plain zoom, enables `zoom:=state`."}],"keyboard":[],"accessibilityNotes":[],"examples":[{"id":"image-cropper-demo","title":"Default","source":"import ImageCropper from \"@marko-ui/shadcn/ui/image-cropper/image-cropper.marko\";\n\nstatic const SAMPLE_IMAGE =\n  \"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='640' height='480' viewBox='0 0 640 480'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23f59e0b'/%3E%3Cstop offset='0.5' stop-color='%23ec4899'/%3E%3Cstop offset='1' stop-color='%236366f1'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='640' height='480' fill='url(%23g)'/%3E%3Ccircle cx='160' cy='130' r='70' fill='%23fef3c7' opacity='0.9'/%3E%3Cpath d='M0 480 L180 260 L320 400 L470 210 L640 480 Z' fill='%23134e4a' opacity='0.75'/%3E%3Cpath d='M0 480 L140 330 L300 480 Z' fill='%23042f2e' opacity='0.7'/%3E%3Cg fill='%23ffffff' opacity='0.85'%3E%3Crect x='30' y='30' width='60' height='4'/%3E%3Crect x='30' y='30' width='4' height='60'/%3E%3Crect x='550' y='446' width='60' height='4'/%3E%3Crect x='606' y='390' width='4' height='60'/%3E%3C/g%3E%3C/svg%3E\";\n\n<ImageCropper\n  class=\"max-w-md h-72\"\n  src=SAMPLE_IMAGE\n  alt=\"Sample landscape\"\n  initialCrop={ x: 60, y: 50, width: 220, height: 160 }\n/>","description":"A freely resizable crop region with the rule-of-thirds grid shown."},{"id":"image-cropper-circle-aspect-ratio","title":"Circle crop, locked 1:1","source":"import ImageCropper from \"@marko-ui/shadcn/ui/image-cropper/image-cropper.marko\";\n\nstatic const SAMPLE_IMAGE =\n  \"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='640' height='480' viewBox='0 0 640 480'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23f59e0b'/%3E%3Cstop offset='0.5' stop-color='%23ec4899'/%3E%3Cstop offset='1' stop-color='%236366f1'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='640' height='480' fill='url(%23g)'/%3E%3Ccircle cx='160' cy='130' r='70' fill='%23fef3c7' opacity='0.9'/%3E%3Cpath d='M0 480 L180 260 L320 400 L470 210 L640 480 Z' fill='%23134e4a' opacity='0.75'/%3E%3Cpath d='M0 480 L140 330 L300 480 Z' fill='%23042f2e' opacity='0.7'/%3E%3Cg fill='%23ffffff' opacity='0.85'%3E%3Crect x='30' y='30' width='60' height='4'/%3E%3Crect x='30' y='30' width='4' height='60'/%3E%3Crect x='550' y='446' width='60' height='4'/%3E%3Crect x='606' y='390' width='4' height='60'/%3E%3C/g%3E%3C/svg%3E\";\n\n<ImageCropper\n  class=\"max-w-md h-72\"\n  src=SAMPLE_IMAGE\n  alt=\"Sample landscape\"\n  aspectRatio=1\n  cropShape=\"circle\"\n  hideGrid\n  initialCrop={ x: 90, y: 40, width: 180, height: 180 }\n/>","description":"Pass `aspectRatio` to lock the crop region's proportions, and `cropShape=\"circle\"` for an avatar-style mask."},{"id":"image-cropper-fixed-crop-area","title":"Fixed crop area","source":"import ImageCropper from \"@marko-ui/shadcn/ui/image-cropper/image-cropper.marko\";\n\nstatic const SAMPLE_IMAGE =\n  \"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='640' height='480' viewBox='0 0 640 480'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23f59e0b'/%3E%3Cstop offset='0.5' stop-color='%23ec4899'/%3E%3Cstop offset='1' stop-color='%236366f1'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='640' height='480' fill='url(%23g)'/%3E%3Ccircle cx='160' cy='130' r='70' fill='%23fef3c7' opacity='0.9'/%3E%3Cpath d='M0 480 L180 260 L320 400 L470 210 L640 480 Z' fill='%23134e4a' opacity='0.75'/%3E%3Cpath d='M0 480 L140 330 L300 480 Z' fill='%23042f2e' opacity='0.7'/%3E%3Cg fill='%23ffffff' opacity='0.85'%3E%3Crect x='30' y='30' width='60' height='4'/%3E%3Crect x='30' y='30' width='4' height='60'/%3E%3Crect x='550' y='446' width='60' height='4'/%3E%3Crect x='606' y='390' width='4' height='60'/%3E%3C/g%3E%3C/svg%3E\";\n\n<ImageCropper\n  class=\"max-w-md h-72\"\n  src=SAMPLE_IMAGE\n  alt=\"Sample landscape\"\n  fixedCropArea\n  hideGrid\n  initialCrop={ x: 80, y: 50, width: 240, height: 160 }\n/>","description":"`fixedCropArea` pins the selection's size and position — the user can only pan and zoom the image underneath it."},{"id":"image-cropper-controlled","title":"Controlled zoom and rotation","source":"import ImageCropper from \"@marko-ui/shadcn/ui/image-cropper/image-cropper.marko\";\n\nstatic const SAMPLE_IMAGE =\n  \"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='640' height='480' viewBox='0 0 640 480'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23f59e0b'/%3E%3Cstop offset='0.5' stop-color='%23ec4899'/%3E%3Cstop offset='1' stop-color='%236366f1'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='640' height='480' fill='url(%23g)'/%3E%3Ccircle cx='160' cy='130' r='70' fill='%23fef3c7' opacity='0.9'/%3E%3Cpath d='M0 480 L180 260 L320 400 L470 210 L640 480 Z' fill='%23134e4a' opacity='0.75'/%3E%3Cpath d='M0 480 L140 330 L300 480 Z' fill='%23042f2e' opacity='0.7'/%3E%3Cg fill='%23ffffff' opacity='0.85'%3E%3Crect x='30' y='30' width='60' height='4'/%3E%3Crect x='30' y='30' width='4' height='60'/%3E%3Crect x='550' y='446' width='60' height='4'/%3E%3Crect x='606' y='390' width='4' height='60'/%3E%3C/g%3E%3C/svg%3E\";\n\nstatic const BUTTON_CLASS =\n  \"border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex h-8 items-center rounded-md border px-3 text-sm font-medium transition-colors\";\n\n<let/zoom=1/>\n<let/rotation=0/>\n\n<div class=\"flex w-full flex-col gap-3\">\n  <ImageCropper\n    class=\"max-w-md h-72\"\n    src=SAMPLE_IMAGE\n    alt=\"Sample landscape\"\n    zoom=zoom\n    rotation=rotation\n    zoomChange(next) { zoom = next; }\n    rotationChange(next) { rotation = next; }\n    initialCrop={ x: 60, y: 50, width: 220, height: 160 }\n  />\n  <div class=\"flex flex-wrap items-center gap-2\">\n    <button type=\"button\"\n      id=\"zoom-in\"\n      class=BUTTON_CLASS\n      onClick() { zoom = Math.min(5, zoom + 0.25); }\n    >\n      Zoom in\n    </button>\n    <button type=\"button\"\n      id=\"zoom-out\"\n      class=BUTTON_CLASS\n      onClick() { zoom = Math.max(1, zoom - 0.25); }\n    >\n      Zoom out\n    </button>\n    <button type=\"button\"\n      id=\"rotate\"\n      class=BUTTON_CLASS\n      onClick() { rotation = (rotation + 90) % 360; }\n    >\n      Rotate 90°\n    </button>\n    <button type=\"button\"\n      id=\"reset-transform\"\n      class=BUTTON_CLASS\n      onClick() { zoom = 1; rotation = 0; }\n    >\n      Reset\n    </button>\n  </div>\n  <p class=\"text-muted-foreground text-sm\" id=\"transform-readout\">\n    Zoom: ${zoom.toFixed(2)} · Rotation: ${rotation}°\n  </p>\n</div>","description":"Zag machines are controlled: `zoom` and `rotation` props without change handlers never move. Pair them with `zoomChange` and `rotationChange`, or use Marko's bind shorthand `zoom:=state`.","essential":true}],"importInstallCommand":"bun add @marko-ui/shadcn marko-zag","nativeAttributes":"div"}
