@radix-ng/primitives 1.0.0-beta.0 → 1.0.0-beta.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/fesm2022/radix-ng-primitives-accordion.mjs +2 -2
- package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-calendar.mjs +109 -84
- package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-checkbox.mjs +2 -2
- package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-collapsible.mjs +1 -1
- package/fesm2022/radix-ng-primitives-collapsible.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-combobox.mjs +1923 -0
- package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-context-menu.mjs +1 -1
- package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +591 -470
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-cropper.mjs +287 -308
- package/fesm2022/radix-ng-primitives-cropper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +66 -15
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-dialog.mjs +1 -1
- package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-drawer.mjs +7 -106
- package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-editable.mjs +305 -24
- package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-field.mjs +86 -6
- package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-fieldset.mjs +1 -1
- package/fesm2022/radix-ng-primitives-fieldset.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-focus-scope.mjs +1 -1
- package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-form.mjs +207 -0
- package/fesm2022/radix-ng-primitives-form.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-input.mjs +85 -4
- package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +413 -5
- package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menubar.mjs +1 -1
- package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-meter.mjs +1 -1
- package/fesm2022/radix-ng-primitives-meter.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +1 -1
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-number-field.mjs +2 -2
- package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +22 -5
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-preview-card.mjs +1 -1
- package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-progress.mjs +1 -1
- package/fesm2022/radix-ng-primitives-progress.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-roving-focus.mjs +1 -1
- package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-scroll-area.mjs +923 -0
- package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-select.mjs +421 -224
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-switch.mjs +3 -2
- package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tabs.mjs +12 -3
- package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-time-field.mjs +27 -3
- package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toast.mjs +839 -0
- package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-toggle-group.mjs +1 -1
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toolbar.mjs +2 -2
- package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tooltip.mjs +11 -3
- package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
- package/package.json +18 -2
- package/schematics/ng-add/index.js +57 -0
- package/schematics/ng-add/index.js.map +1 -1
- package/schematics/ng-add/schema.d.ts +1 -0
- package/schematics/ng-add/schema.json +6 -0
- package/types/radix-ng-primitives-accordion.d.ts +3 -2
- package/types/radix-ng-primitives-calendar.d.ts +38 -18
- package/types/radix-ng-primitives-checkbox.d.ts +5 -5
- package/types/radix-ng-primitives-collapsible.d.ts +2 -1
- package/types/radix-ng-primitives-combobox.d.ts +1265 -0
- package/types/radix-ng-primitives-context-menu.d.ts +3 -2
- package/types/radix-ng-primitives-core.d.ts +187 -56
- package/types/radix-ng-primitives-cropper.d.ts +89 -56
- package/types/radix-ng-primitives-date-field.d.ts +11 -5
- package/types/radix-ng-primitives-dialog.d.ts +2 -1
- package/types/radix-ng-primitives-drawer.d.ts +5 -27
- package/types/radix-ng-primitives-editable.d.ts +90 -13
- package/types/radix-ng-primitives-field.d.ts +74 -4
- package/types/radix-ng-primitives-fieldset.d.ts +3 -2
- package/types/radix-ng-primitives-focus-scope.d.ts +2 -1
- package/types/radix-ng-primitives-form.d.ts +124 -0
- package/types/radix-ng-primitives-input.d.ts +75 -5
- package/types/radix-ng-primitives-menu.d.ts +16 -4
- package/types/radix-ng-primitives-menubar.d.ts +2 -1
- package/types/radix-ng-primitives-meter.d.ts +3 -2
- package/types/radix-ng-primitives-navigation-menu.d.ts +1 -1
- package/types/radix-ng-primitives-number-field.d.ts +6 -6
- package/types/radix-ng-primitives-popover.d.ts +2 -1
- package/types/radix-ng-primitives-popper.d.ts +19 -2
- package/types/radix-ng-primitives-preview-card.d.ts +1 -1
- package/types/radix-ng-primitives-progress.d.ts +3 -2
- package/types/radix-ng-primitives-roving-focus.d.ts +4 -3
- package/types/radix-ng-primitives-scroll-area.d.ts +253 -0
- package/types/radix-ng-primitives-select.d.ts +296 -136
- package/types/radix-ng-primitives-slider.d.ts +1 -1
- package/types/radix-ng-primitives-switch.d.ts +1 -1
- package/types/radix-ng-primitives-tabs.d.ts +1 -1
- package/types/radix-ng-primitives-toast.d.ts +378 -0
- package/types/radix-ng-primitives-toggle-group.d.ts +2 -1
- package/types/radix-ng-primitives-toolbar.d.ts +3 -2
- package/types/radix-ng-primitives-tooltip.d.ts +3 -2
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
import { clamp,
|
|
2
|
+
import { Directive, input, Component, inject, ElementRef, Injector, PLATFORM_ID, numberAttribute, booleanAttribute, output, signal, computed, afterNextRender, effect, untracked, NgModule } from '@angular/core';
|
|
3
|
+
import { createContext, clamp, injectId, resizeEffect } from '@radix-ng/primitives/core';
|
|
4
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
-
function injectCropperRootContext() {
|
|
7
|
-
return inject(CROPPER_ROOT_CONTEXT);
|
|
8
|
-
}
|
|
6
|
+
const [injectCropperRootContext, provideCropperRootContext] = createContext('CropperRoot', 'components/cropper');
|
|
9
7
|
|
|
10
8
|
class RdxCropperCropAreaDirective {
|
|
11
9
|
constructor() {
|
|
12
10
|
this.rootContext = injectCropperRootContext();
|
|
13
11
|
}
|
|
14
12
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCropperCropAreaDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
15
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxCropperCropAreaDirective, isStandalone: true, selector: "[rdxCropperCropArea]", host: { properties: { "style": "rootContext.
|
|
13
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxCropperCropAreaDirective, isStandalone: true, selector: "[rdxCropperCropArea]", host: { properties: { "style": "rootContext.cropAreaStyle()" } }, ngImport: i0 }); }
|
|
16
14
|
}
|
|
17
15
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCropperCropAreaDirective, decorators: [{
|
|
18
16
|
type: Directive,
|
|
19
17
|
args: [{
|
|
20
18
|
selector: '[rdxCropperCropArea]',
|
|
21
19
|
host: {
|
|
22
|
-
'[style]': 'rootContext.
|
|
20
|
+
'[style]': 'rootContext.cropAreaStyle()'
|
|
23
21
|
}
|
|
24
22
|
}]
|
|
25
23
|
}] });
|
|
@@ -29,14 +27,14 @@ class RdxCropperDescriptionDirective {
|
|
|
29
27
|
this.rootContext = injectCropperRootContext();
|
|
30
28
|
}
|
|
31
29
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCropperDescriptionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
32
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxCropperDescriptionDirective, isStandalone: true, selector: "[rdxCropperDescription]", host: { properties: { "attr.id": "rootContext.descriptionId
|
|
30
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxCropperDescriptionDirective, isStandalone: true, selector: "[rdxCropperDescription]", host: { properties: { "attr.id": "rootContext.descriptionId" } }, ngImport: i0 }); }
|
|
33
31
|
}
|
|
34
32
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCropperDescriptionDirective, decorators: [{
|
|
35
33
|
type: Directive,
|
|
36
34
|
args: [{
|
|
37
35
|
selector: '[rdxCropperDescription]',
|
|
38
36
|
host: {
|
|
39
|
-
'[attr.id]': 'rootContext.descriptionId
|
|
37
|
+
'[attr.id]': 'rootContext.descriptionId'
|
|
40
38
|
}
|
|
41
39
|
}]
|
|
42
40
|
}] });
|
|
@@ -46,18 +44,21 @@ class RdxCropperImageComponent {
|
|
|
46
44
|
this.rootContext = injectCropperRootContext();
|
|
47
45
|
this.imgClass = input(...(ngDevMode ? [undefined, { debugName: "imgClass" }] : /* istanbul ignore next */ []));
|
|
48
46
|
this.imgStyles = input(...(ngDevMode ? [undefined, { debugName: "imgStyles" }] : /* istanbul ignore next */ []));
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
/**
|
|
48
|
+
* `alt` text for the rendered image. Defaults to `''` (decorative — screen readers skip it, since
|
|
49
|
+
* the cropper widget describes itself via the root's label/description). Set a non-empty value to
|
|
50
|
+
* give the image a meaningful accessible name.
|
|
51
|
+
*/
|
|
52
|
+
this.imgAlt = input('', ...(ngDevMode ? [{ debugName: "imgAlt" }] : /* istanbul ignore next */ []));
|
|
51
53
|
}
|
|
52
54
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCropperImageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
53
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: RdxCropperImageComponent, isStandalone: true, selector: "[rdxCropperImage]", inputs: { imgClass: { classPropertyName: "imgClass", publicName: "imgClass", isSignal: true, isRequired: false, transformFunction: null }, imgStyles: { classPropertyName: "imgStyles", publicName: "imgStyles", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style": "rootContext.
|
|
55
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: RdxCropperImageComponent, isStandalone: true, selector: "[rdxCropperImage]", inputs: { imgClass: { classPropertyName: "imgClass", publicName: "imgClass", isSignal: true, isRequired: false, transformFunction: null }, imgStyles: { classPropertyName: "imgStyles", publicName: "imgStyles", isSignal: true, isRequired: false, transformFunction: null }, imgAlt: { classPropertyName: "imgAlt", publicName: "imgAlt", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style": "rootContext.imageWrapperStyle()" } }, ngImport: i0, template: `
|
|
54
56
|
<img
|
|
55
|
-
[class]="
|
|
56
|
-
[src]="rootContext.
|
|
57
|
-
[
|
|
58
|
-
[draggable]="
|
|
59
|
-
[
|
|
60
|
-
aria-hidden="true"
|
|
57
|
+
[class]="imgClass()"
|
|
58
|
+
[src]="rootContext.image()"
|
|
59
|
+
[style]="imgStyles()"
|
|
60
|
+
[draggable]="false"
|
|
61
|
+
[alt]="imgAlt()"
|
|
61
62
|
/>
|
|
62
63
|
`, isInline: true }); }
|
|
63
64
|
}
|
|
@@ -66,25 +67,97 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
66
67
|
args: [{
|
|
67
68
|
selector: '[rdxCropperImage]',
|
|
68
69
|
host: {
|
|
69
|
-
'[style]': 'rootContext.
|
|
70
|
+
'[style]': 'rootContext.imageWrapperStyle()'
|
|
70
71
|
},
|
|
71
72
|
template: `
|
|
72
73
|
<img
|
|
73
|
-
[class]="
|
|
74
|
-
[src]="rootContext.
|
|
75
|
-
[
|
|
76
|
-
[draggable]="
|
|
77
|
-
[
|
|
78
|
-
aria-hidden="true"
|
|
74
|
+
[class]="imgClass()"
|
|
75
|
+
[src]="rootContext.image()"
|
|
76
|
+
[style]="imgStyles()"
|
|
77
|
+
[draggable]="false"
|
|
78
|
+
[alt]="imgAlt()"
|
|
79
79
|
/>
|
|
80
80
|
`
|
|
81
81
|
}]
|
|
82
|
-
}], propDecorators: { imgClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "imgClass", required: false }] }], imgStyles: [{ type: i0.Input, args: [{ isSignal: true, alias: "imgStyles", required: false }] }] } });
|
|
82
|
+
}], propDecorators: { imgClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "imgClass", required: false }] }], imgStyles: [{ type: i0.Input, args: [{ isSignal: true, alias: "imgStyles", required: false }] }], imgAlt: [{ type: i0.Input, args: [{ isSignal: true, alias: "imgAlt", required: false }] }] } });
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Clamp a pan offset so the zoomed image always fully covers the crop window — the user can never
|
|
86
|
+
* drag an image edge inside the crop frame. The offset is measured from the centered position, so the
|
|
87
|
+
* allowed travel on each axis is half the overflow of the scaled wrapper past the crop window.
|
|
88
|
+
*
|
|
89
|
+
* Returns `{ x: 0, y: 0 }` when the geometry is not ready yet (any dimension `<= 0`).
|
|
90
|
+
*/
|
|
91
|
+
function restrictOffset(offsetX, offsetY, zoom, geometry) {
|
|
92
|
+
const { wrapperWidth, wrapperHeight, cropWidth, cropHeight } = geometry;
|
|
93
|
+
if (wrapperWidth <= 0 || wrapperHeight <= 0 || cropWidth <= 0 || cropHeight <= 0) {
|
|
94
|
+
return { x: 0, y: 0 };
|
|
95
|
+
}
|
|
96
|
+
const maxDragX = Math.max(0, (wrapperWidth * zoom - cropWidth) / 2);
|
|
97
|
+
const maxDragY = Math.max(0, (wrapperHeight * zoom - cropHeight) / 2);
|
|
98
|
+
return {
|
|
99
|
+
x: clamp(offsetX, -maxDragX, maxDragX),
|
|
100
|
+
y: clamp(offsetY, -maxDragY, maxDragY)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Project the current pan/zoom into a crop rectangle in the source image's natural pixels.
|
|
105
|
+
*
|
|
106
|
+
* The wrapper is centered in the crop window and scaled by `zoom`; `baseScale` converts rendered px
|
|
107
|
+
* back to natural px. The result is rounded and clamped to the image bounds.
|
|
108
|
+
*
|
|
109
|
+
* Returns `null` when the geometry/image is not ready (any dimension `<= 0` or no image), or when the
|
|
110
|
+
* computed crop collapses to zero area.
|
|
111
|
+
*/
|
|
112
|
+
function calculateCropData(offsetX, offsetY, zoom, geometry, image) {
|
|
113
|
+
const { wrapperWidth, wrapperHeight, cropWidth, cropHeight } = geometry;
|
|
114
|
+
const imgW = image.width;
|
|
115
|
+
const imgH = image.height;
|
|
116
|
+
if (!imgW || !imgH || wrapperWidth <= 0 || wrapperHeight <= 0 || cropWidth <= 0 || cropHeight <= 0) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const scaledWrapperWidth = wrapperWidth * zoom;
|
|
120
|
+
const scaledWrapperHeight = wrapperHeight * zoom;
|
|
121
|
+
const topLeftOffsetX = offsetX + (cropWidth - scaledWrapperWidth) / 2;
|
|
122
|
+
const topLeftOffsetY = offsetY + (cropHeight - scaledWrapperHeight) / 2;
|
|
123
|
+
const baseScale = imgW / wrapperWidth;
|
|
124
|
+
if (isNaN(baseScale) || baseScale === 0) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const sx = (-topLeftOffsetX * baseScale) / zoom;
|
|
128
|
+
const sy = (-topLeftOffsetY * baseScale) / zoom;
|
|
129
|
+
const sWidth = (cropWidth * baseScale) / zoom;
|
|
130
|
+
const sHeight = (cropHeight * baseScale) / zoom;
|
|
131
|
+
const finalX = clamp(Math.round(sx), 0, imgW);
|
|
132
|
+
const finalY = clamp(Math.round(sy), 0, imgH);
|
|
133
|
+
const finalWidth = clamp(Math.round(sWidth), 0, imgW - finalX);
|
|
134
|
+
const finalHeight = clamp(Math.round(sHeight), 0, imgH - finalY);
|
|
135
|
+
if (finalWidth <= 0 || finalHeight <= 0) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return { x: finalX, y: finalY, width: finalWidth, height: finalHeight };
|
|
139
|
+
}
|
|
83
140
|
|
|
84
141
|
// Based on https://github.com/origin-space/image-cropper/blob/main/src/Cropper.tsx
|
|
142
|
+
/** Value equality for an `Area` so the crop `computed` only notifies when the rectangle changes. */
|
|
143
|
+
const areaEqual = (a, b) => a === b || (!!a && !!b && a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height);
|
|
144
|
+
/** Value equality for an `{x,y}` point so the clamped-offset `computed` only notifies on real moves. */
|
|
145
|
+
const pointEqual = (a, b) => a.x === b.x && a.y === b.y;
|
|
146
|
+
/** Exposes the root's public state to the child parts (image, crop-area, description). */
|
|
147
|
+
const rootContext = () => {
|
|
148
|
+
const instance = inject(RdxCropperRootDirective);
|
|
149
|
+
return {
|
|
150
|
+
image: instance.image,
|
|
151
|
+
imageWrapperStyle: instance.imageWrapperStyle,
|
|
152
|
+
cropAreaStyle: instance.cropAreaStyle,
|
|
153
|
+
descriptionId: instance.descriptionId
|
|
154
|
+
};
|
|
155
|
+
};
|
|
85
156
|
class RdxCropperRootDirective {
|
|
86
157
|
constructor() {
|
|
87
158
|
this.elementRef = inject((ElementRef));
|
|
159
|
+
this.injector = inject(Injector);
|
|
160
|
+
this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
88
161
|
this.CROPPER_DESC_WARN_MESSAGE = `Warning: \`Cropper\` requires a description element for accessibility.`;
|
|
89
162
|
this.image = input.required(...(ngDevMode ? [{ debugName: "image" }] : /* istanbul ignore next */ []));
|
|
90
163
|
this.cropPadding = input(25, { ...(ngDevMode ? { debugName: "cropPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
@@ -92,49 +165,119 @@ class RdxCropperRootDirective {
|
|
|
92
165
|
this.minZoom = input(1, { ...(ngDevMode ? { debugName: "minZoom" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
93
166
|
this.maxZoom = input(3, { ...(ngDevMode ? { debugName: "maxZoom" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
94
167
|
this.zoomSensitivity = input(0.005, { ...(ngDevMode ? { debugName: "zoomSensitivity" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
168
|
+
/** Pan distance (px) per arrow-key press. */
|
|
95
169
|
this.keyboardStep = input(10, { ...(ngDevMode ? { debugName: "keyboardStep" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
170
|
+
/** Zoom delta per `+` / `-` / `PageUp` / `PageDown` press. */
|
|
171
|
+
this.zoomKeyboardStep = input(0.1, { ...(ngDevMode ? { debugName: "zoomKeyboardStep" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
96
172
|
this.zoom = input(undefined, { ...(ngDevMode ? { debugName: "zoom" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
173
|
+
/** Accessible name for the cropper widget. */
|
|
174
|
+
this.ariaLabel = input('Interactive image cropper', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
175
|
+
/** Disables all interaction (drag, wheel/pinch zoom, keyboard); exposed as `data-disabled`. */
|
|
176
|
+
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
97
177
|
this.onCropChange = output();
|
|
98
178
|
this.onZoomChange = output();
|
|
99
179
|
// State signals
|
|
100
180
|
this.imgWidth = signal(null, ...(ngDevMode ? [{ debugName: "imgWidth" }] : /* istanbul ignore next */ []));
|
|
101
181
|
this.imgHeight = signal(null, ...(ngDevMode ? [{ debugName: "imgHeight" }] : /* istanbul ignore next */ []));
|
|
102
|
-
|
|
103
|
-
this.
|
|
182
|
+
/** Raw content-box size (px) of the root, fed by the ResizeObserver / initial measure. */
|
|
183
|
+
this.containerSize = signal({ width: 0, height: 0 }, ...(ngDevMode ? [{ debugName: "containerSize" }] : /* istanbul ignore next */ []));
|
|
184
|
+
/**
|
|
185
|
+
* Crop-area size derived from the container minus padding, fitted to `aspectRatio`. A `computed`
|
|
186
|
+
* (not a written signal) so it stays reactive to `aspectRatio` / `cropPadding` changes, not only
|
|
187
|
+
* to container resizes — previously these inputs were read inside the ResizeObserver closure and
|
|
188
|
+
* never recomputed until the next resize.
|
|
189
|
+
*/
|
|
190
|
+
this.cropAreaSize = computed(() => {
|
|
191
|
+
const { width, height } = this.containerSize();
|
|
192
|
+
if (width <= 0 || height <= 0) {
|
|
193
|
+
return { width: 0, height: 0 };
|
|
194
|
+
}
|
|
195
|
+
const maxPossibleWidth = Math.max(0, width - this.cropPadding() * 2);
|
|
196
|
+
const maxPossibleHeight = Math.max(0, height - this.cropPadding() * 2);
|
|
197
|
+
const aspectRatio = this.aspectRatio();
|
|
198
|
+
if (maxPossibleWidth / aspectRatio >= maxPossibleHeight) {
|
|
199
|
+
return { width: maxPossibleHeight * aspectRatio, height: maxPossibleHeight };
|
|
200
|
+
}
|
|
201
|
+
return { width: maxPossibleWidth, height: maxPossibleWidth / aspectRatio };
|
|
202
|
+
}, ...(ngDevMode ? [{ debugName: "cropAreaSize" }] : /* istanbul ignore next */ []));
|
|
203
|
+
this.cropAreaWidth = computed(() => this.cropAreaSize().width, ...(ngDevMode ? [{ debugName: "cropAreaWidth" }] : /* istanbul ignore next */ []));
|
|
204
|
+
this.cropAreaHeight = computed(() => this.cropAreaSize().height, ...(ngDevMode ? [{ debugName: "cropAreaHeight" }] : /* istanbul ignore next */ []));
|
|
104
205
|
this.imageWrapperWidth = signal(0, ...(ngDevMode ? [{ debugName: "imageWrapperWidth" }] : /* istanbul ignore next */ []));
|
|
105
206
|
this.imageWrapperHeight = signal(0, ...(ngDevMode ? [{ debugName: "imageWrapperHeight" }] : /* istanbul ignore next */ []));
|
|
106
|
-
|
|
107
|
-
this.
|
|
207
|
+
/** Raw (unclamped) pan-offset intent (px) written by gestures; clamping lives in `clampedOffset`. */
|
|
208
|
+
this.offset = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
|
|
108
209
|
this.internalZoom = signal(this.minZoom(), ...(ngDevMode ? [{ debugName: "internalZoom" }] : /* istanbul ignore next */ []));
|
|
109
210
|
this.isDragging = signal(false, ...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ []));
|
|
110
|
-
|
|
211
|
+
// SSR-stable, deterministic id (the project's CDK-free `_IdGenerator` replacement) so the
|
|
212
|
+
// `aria-describedby` reference matches between server and client renders.
|
|
213
|
+
this.descriptionId = injectId('rdx-cropper-description-');
|
|
111
214
|
this.isZoomControlled = computed(() => this.zoom() !== undefined, ...(ngDevMode ? [{ debugName: "isZoomControlled" }] : /* istanbul ignore next */ []));
|
|
112
215
|
this.effectiveZoom = computed(() => (this.isZoomControlled() ? this.zoom() : this.internalZoom()), ...(ngDevMode ? [{ debugName: "effectiveZoom" }] : /* istanbul ignore next */ []));
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
216
|
+
/**
|
|
217
|
+
* The applied pan offset (px): the raw intent clamped to keep the image covering the crop window
|
|
218
|
+
* at the current geometry/zoom. Derived (not an effect) so it self-corrects when the container
|
|
219
|
+
* resizes or the zoom/aspect-ratio changes — no write-back, no `untracked` re-entrancy. This is the
|
|
220
|
+
* value the view renders and the crop math reads.
|
|
221
|
+
*/
|
|
222
|
+
this.clampedOffset = computed(() => this.restrictOffset(this.offset().x, this.offset().y, this.effectiveZoom()), { ...(ngDevMode ? { debugName: "clampedOffset" } : /* istanbul ignore next */ {}), equal: pointEqual });
|
|
223
|
+
/**
|
|
224
|
+
* Crop rectangle derived from the rendered pan/zoom — the single source of truth for emission.
|
|
225
|
+
* `onCropChange` fires from one effect watching this, so interactions/handlers never emit directly
|
|
226
|
+
* (which previously double-emitted). Value equality keeps it from notifying on equal results.
|
|
227
|
+
*/
|
|
228
|
+
this.cropData = computed(() => calculateCropData(this.clampedOffset().x, this.clampedOffset().y, this.effectiveZoom(), this.geometry(), {
|
|
229
|
+
width: this.imgWidth(),
|
|
230
|
+
height: this.imgHeight()
|
|
231
|
+
}), { ...(ngDevMode ? { debugName: "cropData" } : /* istanbul ignore next */ {}), equal: areaEqual });
|
|
117
232
|
// Refs
|
|
118
233
|
this.dragStartPoint = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "dragStartPoint" }] : /* istanbul ignore next */ []));
|
|
119
234
|
this.dragStartOffset = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "dragStartOffset" }] : /* istanbul ignore next */ []));
|
|
120
|
-
this.latestRestrictedOffset = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "latestRestrictedOffset" }] : /* istanbul ignore next */ []));
|
|
121
235
|
this.latestZoom = signal(this.minZoom(), ...(ngDevMode ? [{ debugName: "latestZoom" }] : /* istanbul ignore next */ []));
|
|
122
|
-
this.isInitialSetupDone = signal(false, ...(ngDevMode ? [{ debugName: "isInitialSetupDone" }] : /* istanbul ignore next */ []));
|
|
123
236
|
this.initialPinchDistance = signal(0, ...(ngDevMode ? [{ debugName: "initialPinchDistance" }] : /* istanbul ignore next */ []));
|
|
124
237
|
this.initialPinchZoom = signal(1, ...(ngDevMode ? [{ debugName: "initialPinchZoom" }] : /* istanbul ignore next */ []));
|
|
125
238
|
this.isPinching = signal(false, ...(ngDevMode ? [{ debugName: "isPinching" }] : /* istanbul ignore next */ []));
|
|
126
239
|
this.hasWarned = signal(false, ...(ngDevMode ? [{ debugName: "hasWarned" }] : /* istanbul ignore next */ []));
|
|
240
|
+
/**
|
|
241
|
+
* Inline style for the image wrapper: measured size, centered in the root, then translated and
|
|
242
|
+
* scaled by the current pan offset and zoom. A `computed` (not a method) so the `[style]` binding
|
|
243
|
+
* only re-applies when an input actually changes — a per-change-detection method call would
|
|
244
|
+
* allocate a new object every tick and force a constant re-bind.
|
|
245
|
+
*/
|
|
246
|
+
this.imageWrapperStyle = computed(() => {
|
|
247
|
+
const wrapperW = this.imageWrapperWidth();
|
|
248
|
+
const wrapperH = this.imageWrapperHeight();
|
|
249
|
+
const { x: offsetX, y: offsetY } = this.clampedOffset();
|
|
250
|
+
const zoom = this.effectiveZoom();
|
|
251
|
+
return {
|
|
252
|
+
width: `${wrapperW}px`,
|
|
253
|
+
height: `${wrapperH}px`,
|
|
254
|
+
transform: `translate3d(${offsetX}px, ${offsetY}px, 0px) scale(${zoom})`,
|
|
255
|
+
position: 'absolute',
|
|
256
|
+
left: `calc(50% - ${wrapperW / 2}px)`,
|
|
257
|
+
top: `calc(50% - ${wrapperH / 2}px)`,
|
|
258
|
+
willChange: 'transform'
|
|
259
|
+
};
|
|
260
|
+
}, ...(ngDevMode ? [{ debugName: "imageWrapperStyle" }] : /* istanbul ignore next */ []));
|
|
261
|
+
/** Inline style for the crop-area overlay (its measured width/height). */
|
|
262
|
+
this.cropAreaStyle = computed(() => ({
|
|
263
|
+
width: `${this.cropAreaWidth()}px`,
|
|
264
|
+
height: `${this.cropAreaHeight()}px`
|
|
265
|
+
}), ...(ngDevMode ? [{ debugName: "cropAreaStyle" }] : /* istanbul ignore next */ []));
|
|
127
266
|
afterNextRender(() => {
|
|
128
267
|
this.initializeContainerDimensions();
|
|
129
268
|
});
|
|
130
269
|
this.setupImageLoadEffect();
|
|
131
270
|
this.setupDimensionsEffects();
|
|
132
|
-
this.setupCropCalculationEffect();
|
|
133
271
|
this.setupAccessibilityWarningEffect();
|
|
134
272
|
this.setupEventListenersEffect();
|
|
135
273
|
effect(() => {
|
|
136
274
|
this.latestZoom.set(this.effectiveZoom());
|
|
137
275
|
});
|
|
276
|
+
// Single source of crop emission: emit whenever the derived crop rectangle changes.
|
|
277
|
+
effect(() => {
|
|
278
|
+
const data = this.cropData();
|
|
279
|
+
untracked(() => this.onCropChange.emit(data));
|
|
280
|
+
});
|
|
138
281
|
}
|
|
139
282
|
updateZoom(newZoomValue) {
|
|
140
283
|
const clampedZoom = clamp(newZoomValue, this.minZoom(), this.maxZoom());
|
|
@@ -147,20 +290,18 @@ class RdxCropperRootDirective {
|
|
|
147
290
|
initializeContainerDimensions() {
|
|
148
291
|
const element = this.elementRef.nativeElement;
|
|
149
292
|
if (element && element.clientWidth > 0 && element.clientHeight > 0) {
|
|
150
|
-
|
|
151
|
-
this.
|
|
293
|
+
// Seed the size for the first paint; the ResizeObserver keeps it in sync afterwards.
|
|
294
|
+
this.containerSize.set({ width: element.clientWidth, height: element.clientHeight });
|
|
152
295
|
}
|
|
153
296
|
}
|
|
154
297
|
setupImageLoadEffect() {
|
|
155
298
|
effect(() => {
|
|
156
299
|
const image = this.image();
|
|
157
|
-
this.
|
|
158
|
-
this.offsetY.set(0);
|
|
300
|
+
this.offset.set({ x: 0, y: 0 });
|
|
159
301
|
if (!this.isZoomControlled()) {
|
|
160
302
|
this.internalZoom.set(this.minZoom());
|
|
161
303
|
}
|
|
162
|
-
this.
|
|
163
|
-
if (!image) {
|
|
304
|
+
if (!image || !this.isBrowser) {
|
|
164
305
|
this.imgWidth.set(null);
|
|
165
306
|
this.imgHeight.set(null);
|
|
166
307
|
return;
|
|
@@ -186,44 +327,20 @@ class RdxCropperRootDirective {
|
|
|
186
327
|
});
|
|
187
328
|
}
|
|
188
329
|
setupDimensionsEffects() {
|
|
189
|
-
effect
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
const maxPossibleWidth = Math.max(0, width - this.cropPadding() * 2);
|
|
200
|
-
const maxPossibleHeight = Math.max(0, height - this.cropPadding() * 2);
|
|
201
|
-
let targetCropW, targetCropH;
|
|
202
|
-
if (maxPossibleWidth / this.aspectRatio() >= maxPossibleHeight) {
|
|
203
|
-
targetCropH = maxPossibleHeight;
|
|
204
|
-
targetCropW = targetCropH * this.aspectRatio();
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
targetCropW = maxPossibleWidth;
|
|
208
|
-
targetCropH = targetCropW / this.aspectRatio();
|
|
209
|
-
}
|
|
210
|
-
this.cropAreaWidth.set(targetCropW);
|
|
211
|
-
this.cropAreaHeight.set(targetCropH);
|
|
212
|
-
};
|
|
213
|
-
const observer = new ResizeObserver((entries) => {
|
|
330
|
+
// Track the container's content-box size via the shared resize-observer effect; `cropAreaSize`
|
|
331
|
+
// derives crop dimensions from it reactively (so changing `aspectRatio` / `cropPadding`
|
|
332
|
+
// recomputes without waiting for a resize). `element` is null on the server, so no observer is
|
|
333
|
+
// constructed (SSR-safe).
|
|
334
|
+
resizeEffect({
|
|
335
|
+
injector: this.injector,
|
|
336
|
+
element: computed(() => (this.isBrowser ? this.elementRef.nativeElement : null)),
|
|
337
|
+
onResize: (entries) => {
|
|
214
338
|
for (const entry of entries) {
|
|
215
339
|
const { width, height } = entry.contentRect;
|
|
216
340
|
if (width > 0 && height > 0)
|
|
217
|
-
|
|
341
|
+
this.containerSize.set({ width, height });
|
|
218
342
|
}
|
|
219
|
-
});
|
|
220
|
-
observer.observe(element);
|
|
221
|
-
const initialWidth = element.clientWidth;
|
|
222
|
-
const initialHeight = element.clientHeight;
|
|
223
|
-
if (initialWidth > 0 && initialHeight > 0) {
|
|
224
|
-
updateDimensions(initialWidth, initialHeight);
|
|
225
343
|
}
|
|
226
|
-
return () => observer.disconnect();
|
|
227
344
|
});
|
|
228
345
|
// Update image wrapper dimensions when crop area or image dimensions change
|
|
229
346
|
effect(() => {
|
|
@@ -251,110 +368,25 @@ class RdxCropperRootDirective {
|
|
|
251
368
|
this.imageWrapperHeight.set(targetWrapperHeight);
|
|
252
369
|
});
|
|
253
370
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const wrapperH = this.imageWrapperHeight();
|
|
257
|
-
const cropW = this.cropAreaWidth();
|
|
258
|
-
const cropH = this.cropAreaHeight();
|
|
259
|
-
if (wrapperW <= 0 || wrapperH <= 0 || cropW <= 0 || cropH <= 0) {
|
|
260
|
-
return { x: 0, y: 0 };
|
|
261
|
-
}
|
|
262
|
-
const effectiveWrapperWidth = wrapperW * currentZoom;
|
|
263
|
-
const effectiveWrapperHeight = wrapperH * currentZoom;
|
|
264
|
-
const maxDragX = Math.max(0, (effectiveWrapperWidth - cropW) / 2);
|
|
265
|
-
const maxDragY = Math.max(0, (effectiveWrapperHeight - cropH) / 2);
|
|
371
|
+
/** Current rendered geometry the crop math operates on, read from the state signals. */
|
|
372
|
+
geometry() {
|
|
266
373
|
return {
|
|
267
|
-
|
|
268
|
-
|
|
374
|
+
wrapperWidth: this.imageWrapperWidth(),
|
|
375
|
+
wrapperHeight: this.imageWrapperHeight(),
|
|
376
|
+
cropWidth: this.cropAreaWidth(),
|
|
377
|
+
cropHeight: this.cropAreaHeight()
|
|
269
378
|
};
|
|
270
379
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const currentOffsetY = finalOffsetY ?? this.latestRestrictedOffset().y;
|
|
274
|
-
const currentZoom = finalZoom ?? this.effectiveZoom();
|
|
275
|
-
const imgW = this.imgWidth();
|
|
276
|
-
const imgH = this.imgHeight();
|
|
277
|
-
const wrapperW = this.imageWrapperWidth();
|
|
278
|
-
const wrapperH = this.imageWrapperHeight();
|
|
279
|
-
const cropW = this.cropAreaWidth();
|
|
280
|
-
const cropH = this.cropAreaHeight();
|
|
281
|
-
if (!imgW || !imgH || wrapperW <= 0 || wrapperH <= 0 || cropW <= 0 || cropH <= 0) {
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
const scaledWrapperWidth = wrapperW * currentZoom;
|
|
285
|
-
const scaledWrapperHeight = wrapperH * currentZoom;
|
|
286
|
-
const topLeftOffsetX = currentOffsetX + (cropW - scaledWrapperWidth) / 2;
|
|
287
|
-
const topLeftOffsetY = currentOffsetY + (cropH - scaledWrapperHeight) / 2;
|
|
288
|
-
const baseScale = imgW / wrapperW;
|
|
289
|
-
if (isNaN(baseScale) || baseScale === 0) {
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
|
-
const sx = (-topLeftOffsetX * baseScale) / currentZoom;
|
|
293
|
-
const sy = (-topLeftOffsetY * baseScale) / currentZoom;
|
|
294
|
-
const sWidth = (cropW * baseScale) / currentZoom;
|
|
295
|
-
const sHeight = (cropH * baseScale) / currentZoom;
|
|
296
|
-
const finalX = clamp(Math.round(sx), 0, imgW);
|
|
297
|
-
const finalY = clamp(Math.round(sy), 0, imgH);
|
|
298
|
-
const finalWidth = clamp(Math.round(sWidth), 0, imgW - finalX);
|
|
299
|
-
const finalHeight = clamp(Math.round(sHeight), 0, imgH - finalY);
|
|
300
|
-
if (finalWidth <= 0 || finalHeight <= 0) {
|
|
301
|
-
return null;
|
|
302
|
-
}
|
|
303
|
-
return { x: finalX, y: finalY, width: finalWidth, height: finalHeight };
|
|
304
|
-
}
|
|
305
|
-
setupCropCalculationEffect() {
|
|
306
|
-
effect(() => {
|
|
307
|
-
const wrapperW = this.imageWrapperWidth();
|
|
308
|
-
const wrapperH = this.imageWrapperHeight();
|
|
309
|
-
const cropW = this.cropAreaWidth();
|
|
310
|
-
const cropH = this.cropAreaHeight();
|
|
311
|
-
const currentZoom = this.effectiveZoom();
|
|
312
|
-
if (wrapperW > 0 && wrapperH > 0 && cropW > 0 && cropH > 0) {
|
|
313
|
-
if (!this.isInitialSetupDone()) {
|
|
314
|
-
const restrictedInitial = this.restrictOffset(0, 0, currentZoom);
|
|
315
|
-
this.offsetX.set(restrictedInitial.x);
|
|
316
|
-
this.offsetY.set(restrictedInitial.y);
|
|
317
|
-
if (!this.isZoomControlled()) {
|
|
318
|
-
this.internalZoom.set(currentZoom);
|
|
319
|
-
}
|
|
320
|
-
this.dragStartOffset.set(restrictedInitial);
|
|
321
|
-
this.latestRestrictedOffset.set(restrictedInitial);
|
|
322
|
-
this.latestZoom.set(currentZoom);
|
|
323
|
-
this.onCropChange.emit(this.calculateCropData(restrictedInitial.x, restrictedInitial.y, currentZoom));
|
|
324
|
-
this.isInitialSetupDone.set(true);
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
const currentX = this.latestRestrictedOffset().x;
|
|
328
|
-
const currentY = this.latestRestrictedOffset().y;
|
|
329
|
-
const restrictedCurrent = this.restrictOffset(currentX, currentY, currentZoom);
|
|
330
|
-
if (restrictedCurrent.x !== currentX || restrictedCurrent.y !== currentY) {
|
|
331
|
-
this.offsetX.set(restrictedCurrent.x);
|
|
332
|
-
this.offsetY.set(restrictedCurrent.y);
|
|
333
|
-
this.latestRestrictedOffset.set(restrictedCurrent);
|
|
334
|
-
this.dragStartOffset.set(restrictedCurrent);
|
|
335
|
-
}
|
|
336
|
-
this.onCropChange.emit(this.calculateCropData(restrictedCurrent.x, restrictedCurrent.y, currentZoom));
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
this.isInitialSetupDone.set(false);
|
|
341
|
-
this.offsetX.set(0);
|
|
342
|
-
this.offsetY.set(0);
|
|
343
|
-
if (!this.isZoomControlled()) {
|
|
344
|
-
this.internalZoom.set(this.minZoom());
|
|
345
|
-
}
|
|
346
|
-
this.dragStartOffset.set({ x: 0, y: 0 });
|
|
347
|
-
this.latestRestrictedOffset.set({ x: 0, y: 0 });
|
|
348
|
-
this.latestZoom.set(currentZoom);
|
|
349
|
-
this.onCropChange.emit(null);
|
|
350
|
-
}
|
|
351
|
-
});
|
|
380
|
+
restrictOffset(dragOffsetX, dragOffsetY, currentZoom) {
|
|
381
|
+
return restrictOffset(dragOffsetX, dragOffsetY, currentZoom, this.geometry());
|
|
352
382
|
}
|
|
353
383
|
setupAccessibilityWarningEffect() {
|
|
384
|
+
if (!this.isBrowser)
|
|
385
|
+
return;
|
|
354
386
|
effect(() => {
|
|
355
387
|
const checkTimeout = setTimeout(() => {
|
|
356
388
|
if (this.elementRef.nativeElement && !this.hasWarned()) {
|
|
357
|
-
const hasDescription = document.getElementById(this.descriptionId
|
|
389
|
+
const hasDescription = document.getElementById(this.descriptionId);
|
|
358
390
|
if (!hasDescription) {
|
|
359
391
|
console.warn(this.CROPPER_DESC_WARN_MESSAGE);
|
|
360
392
|
this.hasWarned.set(true);
|
|
@@ -364,22 +396,36 @@ class RdxCropperRootDirective {
|
|
|
364
396
|
return () => clearTimeout(checkTimeout);
|
|
365
397
|
});
|
|
366
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Single attachment point for every interaction listener. Re-runs on `disabled()`, so a disabled
|
|
401
|
+
* cropper has NO interaction listeners bound at all — there is no per-handler `disabled` check to
|
|
402
|
+
* forget, and a new gesture path can't accidentally bypass the gate. Uses `{ passive: false }` so
|
|
403
|
+
* the handlers can `preventDefault()` (wheel/touch scrolling, arrow-key page scroll).
|
|
404
|
+
*/
|
|
367
405
|
setupEventListenersEffect() {
|
|
406
|
+
if (!this.isBrowser)
|
|
407
|
+
return;
|
|
368
408
|
effect((onCleanup) => {
|
|
369
409
|
const node = this.elementRef.nativeElement;
|
|
370
|
-
if (!node)
|
|
410
|
+
if (!node || this.disabled())
|
|
371
411
|
return;
|
|
372
412
|
const options = { passive: false };
|
|
413
|
+
const mouseDownHandler = (e) => this.onMouseDown(e);
|
|
414
|
+
const keyDownHandler = (e) => this.onKeyDown(e);
|
|
373
415
|
const wheelHandler = (e) => this.handleWheel(e);
|
|
374
416
|
const touchStartHandler = (e) => this.handleTouchStart(e);
|
|
375
417
|
const touchMoveHandler = (e) => this.handleTouchMove(e);
|
|
376
418
|
const touchEndHandler = (e) => this.handleTouchEnd(e);
|
|
419
|
+
node.addEventListener('mousedown', mouseDownHandler, options);
|
|
420
|
+
node.addEventListener('keydown', keyDownHandler, options);
|
|
377
421
|
node.addEventListener('wheel', wheelHandler, options);
|
|
378
422
|
node.addEventListener('touchstart', touchStartHandler, options);
|
|
379
423
|
node.addEventListener('touchmove', touchMoveHandler, options);
|
|
380
424
|
node.addEventListener('touchend', touchEndHandler, options);
|
|
381
425
|
node.addEventListener('touchcancel', touchEndHandler, options);
|
|
382
426
|
onCleanup(() => {
|
|
427
|
+
node.removeEventListener('mousedown', mouseDownHandler, options);
|
|
428
|
+
node.removeEventListener('keydown', keyDownHandler, options);
|
|
383
429
|
node.removeEventListener('wheel', wheelHandler, options);
|
|
384
430
|
node.removeEventListener('touchstart', touchStartHandler, options);
|
|
385
431
|
node.removeEventListener('touchmove', touchMoveHandler, options);
|
|
@@ -388,13 +434,6 @@ class RdxCropperRootDirective {
|
|
|
388
434
|
});
|
|
389
435
|
});
|
|
390
436
|
}
|
|
391
|
-
handleInteractionEnd() {
|
|
392
|
-
const finalData = this.calculateCropData(this.latestRestrictedOffset().x, this.latestRestrictedOffset().y, this.effectiveZoom());
|
|
393
|
-
this.onCropChange.emit(finalData);
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* @ignore
|
|
397
|
-
*/
|
|
398
437
|
onMouseDown(e) {
|
|
399
438
|
if (e.button !== 0)
|
|
400
439
|
return;
|
|
@@ -402,52 +441,49 @@ class RdxCropperRootDirective {
|
|
|
402
441
|
this.isDragging.set(true);
|
|
403
442
|
this.isPinching.set(false);
|
|
404
443
|
this.dragStartPoint.set({ x: e.clientX, y: e.clientY });
|
|
405
|
-
this.dragStartOffset.set(this.
|
|
444
|
+
this.dragStartOffset.set(this.clampedOffset());
|
|
406
445
|
const handleMouseMove = (ev) => {
|
|
407
446
|
const deltaX = ev.clientX - this.dragStartPoint().x;
|
|
408
447
|
const deltaY = ev.clientY - this.dragStartPoint().y;
|
|
409
|
-
|
|
410
|
-
const targetOffsetY = this.dragStartOffset().y + deltaY;
|
|
411
|
-
const restricted = this.restrictOffset(targetOffsetX, targetOffsetY, this.effectiveZoom());
|
|
412
|
-
this.latestRestrictedOffset.set(restricted);
|
|
413
|
-
this.offsetX.set(restricted.x);
|
|
414
|
-
this.offsetY.set(restricted.y);
|
|
448
|
+
this.offset.set({ x: this.dragStartOffset().x + deltaX, y: this.dragStartOffset().y + deltaY });
|
|
415
449
|
};
|
|
416
450
|
const handleMouseUp = () => {
|
|
417
451
|
this.isDragging.set(false);
|
|
418
452
|
window.removeEventListener('mousemove', handleMouseMove);
|
|
419
453
|
window.removeEventListener('mouseup', handleMouseUp);
|
|
420
|
-
this.handleInteractionEnd();
|
|
421
454
|
};
|
|
422
455
|
window.addEventListener('mousemove', handleMouseMove);
|
|
423
456
|
window.addEventListener('mouseup', handleMouseUp);
|
|
424
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Zoom toward an anchor point (coordinates relative to the root's center). `fromZoom`/`fromOffset`
|
|
460
|
+
* are the zoom/offset the anchor is measured against — the live state for wheel/keyboard, the
|
|
461
|
+
* gesture-start baseline for pinch. Emits the zoom request via {@link updateZoom}, then re-anchors
|
|
462
|
+
* the pan offset **only when the zoom is uncontrolled**: in controlled mode the rendered zoom does
|
|
463
|
+
* not change until the parent writes `zoom` back, so writing an offset for a not-yet-applied zoom
|
|
464
|
+
* would pan the image without rescaling it (`clampedOffset` re-derives once the new zoom applies).
|
|
465
|
+
*/
|
|
466
|
+
zoomToPoint(pointerX, pointerY, targetZoom, fromZoom, fromOffset) {
|
|
467
|
+
const clampedZoom = clamp(targetZoom, this.minZoom(), this.maxZoom());
|
|
468
|
+
if (clampedZoom === this.effectiveZoom())
|
|
469
|
+
return;
|
|
470
|
+
const imagePointX = (pointerX - fromOffset.x) / fromZoom;
|
|
471
|
+
const imagePointY = (pointerY - fromOffset.y) / fromZoom;
|
|
472
|
+
const finalNewZoom = this.updateZoom(clampedZoom);
|
|
473
|
+
if (this.isZoomControlled())
|
|
474
|
+
return;
|
|
475
|
+
this.offset.set({ x: pointerX - imagePointX * finalNewZoom, y: pointerY - imagePointY * finalNewZoom });
|
|
476
|
+
}
|
|
425
477
|
handleWheel(e) {
|
|
426
478
|
e.preventDefault();
|
|
427
479
|
e.stopPropagation();
|
|
428
480
|
if (!this.elementRef.nativeElement || this.imageWrapperWidth() <= 0 || this.imageWrapperHeight() <= 0)
|
|
429
481
|
return;
|
|
430
|
-
const currentZoom = this.latestZoom();
|
|
431
|
-
const currentOffsetX = this.latestRestrictedOffset().x;
|
|
432
|
-
const currentOffsetY = this.latestRestrictedOffset().y;
|
|
433
482
|
const delta = e.deltaY * -this.zoomSensitivity();
|
|
434
|
-
const targetZoom = currentZoom + delta;
|
|
435
|
-
if (clamp(targetZoom, this.minZoom(), this.maxZoom()) === currentZoom)
|
|
436
|
-
return;
|
|
437
483
|
const rect = this.elementRef.nativeElement.getBoundingClientRect();
|
|
438
484
|
const pointerX = e.clientX - rect.left - rect.width / 2;
|
|
439
485
|
const pointerY = e.clientY - rect.top - rect.height / 2;
|
|
440
|
-
|
|
441
|
-
const imagePointY = (pointerY - currentOffsetY) / currentZoom;
|
|
442
|
-
const finalNewZoom = this.updateZoom(targetZoom);
|
|
443
|
-
const newOffsetX = pointerX - imagePointX * finalNewZoom;
|
|
444
|
-
const newOffsetY = pointerY - imagePointY * finalNewZoom;
|
|
445
|
-
const restrictedNewOffset = this.restrictOffset(newOffsetX, newOffsetY, finalNewZoom);
|
|
446
|
-
this.offsetX.set(restrictedNewOffset.x);
|
|
447
|
-
this.offsetY.set(restrictedNewOffset.y);
|
|
448
|
-
this.latestRestrictedOffset.set(restrictedNewOffset);
|
|
449
|
-
const finalData = this.calculateCropData(restrictedNewOffset.x, restrictedNewOffset.y, finalNewZoom);
|
|
450
|
-
this.onCropChange.emit(finalData);
|
|
486
|
+
this.zoomToPoint(pointerX, pointerY, this.effectiveZoom() + delta, this.effectiveZoom(), this.clampedOffset());
|
|
451
487
|
}
|
|
452
488
|
getPinchDistance(touches) {
|
|
453
489
|
return Math.sqrt(Math.pow(touches[1].clientX - touches[0].clientX, 2) + Math.pow(touches[1].clientY - touches[0].clientY, 2));
|
|
@@ -467,14 +503,14 @@ class RdxCropperRootDirective {
|
|
|
467
503
|
this.isDragging.set(true);
|
|
468
504
|
this.isPinching.set(false);
|
|
469
505
|
this.dragStartPoint.set({ x: touches[0].clientX, y: touches[0].clientY });
|
|
470
|
-
this.dragStartOffset.set(this.
|
|
506
|
+
this.dragStartOffset.set(this.clampedOffset());
|
|
471
507
|
}
|
|
472
508
|
else if (touches.length === 2) {
|
|
473
509
|
this.isDragging.set(false);
|
|
474
510
|
this.isPinching.set(true);
|
|
475
511
|
this.initialPinchDistance.set(this.getPinchDistance(touches));
|
|
476
512
|
this.initialPinchZoom.set(this.latestZoom());
|
|
477
|
-
this.dragStartOffset.set(this.
|
|
513
|
+
this.dragStartOffset.set(this.clampedOffset());
|
|
478
514
|
}
|
|
479
515
|
}
|
|
480
516
|
handleTouchMove(e) {
|
|
@@ -485,37 +521,16 @@ class RdxCropperRootDirective {
|
|
|
485
521
|
if (touches.length === 1 && this.isDragging() && !this.isPinching()) {
|
|
486
522
|
const deltaX = touches[0].clientX - this.dragStartPoint().x;
|
|
487
523
|
const deltaY = touches[0].clientY - this.dragStartPoint().y;
|
|
488
|
-
|
|
489
|
-
const targetOffsetY = this.dragStartOffset().y + deltaY;
|
|
490
|
-
const restricted = this.restrictOffset(targetOffsetX, targetOffsetY, this.effectiveZoom());
|
|
491
|
-
this.latestRestrictedOffset.set(restricted);
|
|
492
|
-
this.offsetX.set(restricted.x);
|
|
493
|
-
this.offsetY.set(restricted.y);
|
|
524
|
+
this.offset.set({ x: this.dragStartOffset().x + deltaX, y: this.dragStartOffset().y + deltaY });
|
|
494
525
|
}
|
|
495
526
|
else if (touches.length === 2 && this.isPinching()) {
|
|
496
|
-
const
|
|
497
|
-
const scale = currentPinchDistance / this.initialPinchDistance();
|
|
498
|
-
const currentZoom = this.initialPinchZoom();
|
|
499
|
-
const targetZoom = currentZoom * scale;
|
|
500
|
-
if (clamp(targetZoom, this.minZoom(), this.maxZoom()) === this.latestZoom())
|
|
501
|
-
return;
|
|
527
|
+
const scale = this.getPinchDistance(touches) / this.initialPinchDistance();
|
|
502
528
|
const pinchCenter = this.getPinchCenter(touches);
|
|
503
529
|
const rect = this.elementRef.nativeElement.getBoundingClientRect();
|
|
504
530
|
const pinchCenterX = pinchCenter.x - rect.left - rect.width / 2;
|
|
505
531
|
const pinchCenterY = pinchCenter.y - rect.top - rect.height / 2;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const imagePointX = (pinchCenterX - currentOffsetX) / currentZoom;
|
|
509
|
-
const imagePointY = (pinchCenterY - currentOffsetY) / currentZoom;
|
|
510
|
-
const finalNewZoom = this.updateZoom(targetZoom);
|
|
511
|
-
const newOffsetX = pinchCenterX - imagePointX * finalNewZoom;
|
|
512
|
-
const newOffsetY = pinchCenterY - imagePointY * finalNewZoom;
|
|
513
|
-
const restrictedNewOffset = this.restrictOffset(newOffsetX, newOffsetY, finalNewZoom);
|
|
514
|
-
this.offsetX.set(restrictedNewOffset.x);
|
|
515
|
-
this.offsetY.set(restrictedNewOffset.y);
|
|
516
|
-
this.latestRestrictedOffset.set(restrictedNewOffset);
|
|
517
|
-
const finalData = this.calculateCropData(restrictedNewOffset.x, restrictedNewOffset.y, finalNewZoom);
|
|
518
|
-
this.onCropChange.emit(finalData);
|
|
532
|
+
// Pinch is baseline-relative: anchor against the zoom/offset captured at gesture start.
|
|
533
|
+
this.zoomToPoint(pinchCenterX, pinchCenterY, this.initialPinchZoom() * scale, this.initialPinchZoom(), this.dragStartOffset());
|
|
519
534
|
}
|
|
520
535
|
}
|
|
521
536
|
handleTouchEnd(e) {
|
|
@@ -526,26 +541,22 @@ class RdxCropperRootDirective {
|
|
|
526
541
|
if (touches.length === 1) {
|
|
527
542
|
this.isDragging.set(true);
|
|
528
543
|
this.dragStartPoint.set({ x: touches[0].clientX, y: touches[0].clientY });
|
|
529
|
-
this.dragStartOffset.set(this.
|
|
544
|
+
this.dragStartOffset.set(this.clampedOffset());
|
|
530
545
|
}
|
|
531
546
|
else {
|
|
532
547
|
this.isDragging.set(false);
|
|
533
|
-
this.handleInteractionEnd();
|
|
534
548
|
}
|
|
535
549
|
}
|
|
536
550
|
else if (this.isDragging() && touches.length === 0) {
|
|
537
551
|
this.isDragging.set(false);
|
|
538
|
-
this.handleInteractionEnd();
|
|
539
552
|
}
|
|
540
553
|
}
|
|
541
|
-
/**
|
|
542
|
-
* @ignore
|
|
543
|
-
*/
|
|
544
554
|
onKeyDown(e) {
|
|
545
555
|
if (this.imageWrapperWidth() <= 0)
|
|
546
556
|
return;
|
|
547
|
-
|
|
548
|
-
let
|
|
557
|
+
const base = this.clampedOffset();
|
|
558
|
+
let targetOffsetX = base.x;
|
|
559
|
+
let targetOffsetY = base.y;
|
|
549
560
|
// eslint-disable-next-line no-useless-assignment
|
|
550
561
|
let moved = false;
|
|
551
562
|
switch (e.key) {
|
|
@@ -565,89 +576,57 @@ class RdxCropperRootDirective {
|
|
|
565
576
|
targetOffsetX -= this.keyboardStep();
|
|
566
577
|
moved = true;
|
|
567
578
|
break;
|
|
579
|
+
case '+':
|
|
580
|
+
case '=':
|
|
581
|
+
case 'PageUp':
|
|
582
|
+
e.preventDefault();
|
|
583
|
+
this.zoomFromCenter(this.zoomKeyboardStep());
|
|
584
|
+
return;
|
|
585
|
+
case '-':
|
|
586
|
+
case '_':
|
|
587
|
+
case 'PageDown':
|
|
588
|
+
e.preventDefault();
|
|
589
|
+
this.zoomFromCenter(-this.zoomKeyboardStep());
|
|
590
|
+
return;
|
|
568
591
|
default:
|
|
569
592
|
return;
|
|
570
593
|
}
|
|
571
594
|
if (moved) {
|
|
572
595
|
e.preventDefault();
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
this.latestRestrictedOffset.set(restricted);
|
|
576
|
-
this.offsetX.set(restricted.x);
|
|
577
|
-
this.offsetY.set(restricted.y);
|
|
578
|
-
}
|
|
596
|
+
// Write the raw target; `clampedOffset` clamps it (and dedups a no-op at the boundary).
|
|
597
|
+
this.offset.set({ x: targetOffsetX, y: targetOffsetY });
|
|
579
598
|
}
|
|
580
599
|
}
|
|
581
|
-
/**
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
/**
|
|
590
|
-
* @ignore
|
|
591
|
-
*/
|
|
592
|
-
getImageProps() {
|
|
593
|
-
return {
|
|
594
|
-
src: this.image(),
|
|
595
|
-
alt: 'Image being cropped',
|
|
596
|
-
draggable: false,
|
|
597
|
-
'aria-hidden': true
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* @ignore
|
|
602
|
-
*/
|
|
603
|
-
getImageWrapperStyle() {
|
|
604
|
-
const wrapperW = this.imageWrapperWidth();
|
|
605
|
-
const wrapperH = this.imageWrapperHeight();
|
|
606
|
-
const offsetX = this.offsetX();
|
|
607
|
-
const offsetY = this.offsetY();
|
|
608
|
-
const zoom = this.effectiveZoom();
|
|
609
|
-
return {
|
|
610
|
-
width: `${wrapperW}px`,
|
|
611
|
-
height: `${wrapperH}px`,
|
|
612
|
-
transform: `translate3d(${offsetX}px, ${offsetY}px, 0px) scale(${zoom})`,
|
|
613
|
-
position: 'absolute',
|
|
614
|
-
left: `calc(50% - ${wrapperW / 2}px)`,
|
|
615
|
-
top: `calc(50% - ${wrapperH / 2}px)`,
|
|
616
|
-
willChange: 'transform'
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* @ignore
|
|
621
|
-
*/
|
|
622
|
-
getCropAreaStyle() {
|
|
623
|
-
return {
|
|
624
|
-
width: `${this.cropAreaWidth()}px`,
|
|
625
|
-
height: `${this.cropAreaHeight()}px`
|
|
626
|
-
};
|
|
600
|
+
/** Zoom by `delta` keeping the crop center fixed (the keyboard counterpart of wheel/pinch zoom). */
|
|
601
|
+
zoomFromCenter(delta) {
|
|
602
|
+
if (this.imageWrapperWidth() <= 0 || this.imageWrapperHeight() <= 0)
|
|
603
|
+
return;
|
|
604
|
+
// Anchor at the crop center (pointer 0,0 relative to center) against the live zoom/offset.
|
|
605
|
+
this.zoomToPoint(0, 0, this.effectiveZoom() + delta, this.effectiveZoom(), this.clampedOffset());
|
|
627
606
|
}
|
|
628
607
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCropperRootDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
629
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxCropperRootDirective, isStandalone: true, selector: "[rdxCropperRoot]", inputs: { image: { classPropertyName: "image", publicName: "image", isSignal: true, isRequired: true, transformFunction: null }, cropPadding: { classPropertyName: "cropPadding", publicName: "cropPadding", isSignal: true, isRequired: false, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, minZoom: { classPropertyName: "minZoom", publicName: "minZoom", isSignal: true, isRequired: false, transformFunction: null }, maxZoom: { classPropertyName: "maxZoom", publicName: "maxZoom", isSignal: true, isRequired: false, transformFunction: null }, zoomSensitivity: { classPropertyName: "zoomSensitivity", publicName: "zoomSensitivity", isSignal: true, isRequired: false, transformFunction: null }, keyboardStep: { classPropertyName: "keyboardStep", publicName: "keyboardStep", isSignal: true, isRequired: false, transformFunction: null },
|
|
608
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxCropperRootDirective, isStandalone: true, selector: "[rdxCropperRoot]", inputs: { image: { classPropertyName: "image", publicName: "image", isSignal: true, isRequired: true, transformFunction: null }, cropPadding: { classPropertyName: "cropPadding", publicName: "cropPadding", isSignal: true, isRequired: false, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, minZoom: { classPropertyName: "minZoom", publicName: "minZoom", isSignal: true, isRequired: false, transformFunction: null }, maxZoom: { classPropertyName: "maxZoom", publicName: "maxZoom", isSignal: true, isRequired: false, transformFunction: null }, zoomSensitivity: { classPropertyName: "zoomSensitivity", publicName: "zoomSensitivity", isSignal: true, isRequired: false, transformFunction: null }, keyboardStep: { classPropertyName: "keyboardStep", publicName: "keyboardStep", isSignal: true, isRequired: false, transformFunction: null }, zoomKeyboardStep: { classPropertyName: "zoomKeyboardStep", publicName: "zoomKeyboardStep", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onCropChange: "onCropChange", onZoomChange: "onZoomChange" }, host: { attributes: { "role": "application" }, properties: { "attr.tabindex": "disabled() ? -1 : 0", "attr.aria-label": "ariaLabel()", "attr.aria-describedby": "descriptionId", "attr.aria-disabled": "disabled() ? \"true\" : undefined", "attr.data-disabled": "disabled() ? \"\" : undefined", "attr.data-dragging": "isDragging() ? \"\" : undefined" } }, providers: [provideCropperRootContext(rootContext)], ngImport: i0 }); }
|
|
630
609
|
}
|
|
631
610
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCropperRootDirective, decorators: [{
|
|
632
611
|
type: Directive,
|
|
633
612
|
args: [{
|
|
634
613
|
selector: '[rdxCropperRoot]',
|
|
635
|
-
providers: [
|
|
614
|
+
providers: [provideCropperRootContext(rootContext)],
|
|
636
615
|
host: {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
'[attr.aria-valuenow]': 'effectiveZoom()',
|
|
642
|
-
'[attr.aria-valuetext]': 'zoomValueText()',
|
|
643
|
-
tabindex: '0',
|
|
616
|
+
// `application` so a screen reader passes arrow/+/- keys straight to our handlers (pan + zoom)
|
|
617
|
+
// instead of intercepting them for browse-mode navigation; instructions come via the
|
|
618
|
+
// description element referenced by `aria-describedby`. The slider-style `aria-value*`
|
|
619
|
+
// attributes were removed — they are only honored on range roles, so they were dead here.
|
|
644
620
|
role: 'application',
|
|
645
|
-
'
|
|
646
|
-
'
|
|
647
|
-
'
|
|
621
|
+
'[attr.tabindex]': 'disabled() ? -1 : 0',
|
|
622
|
+
'[attr.aria-label]': 'ariaLabel()',
|
|
623
|
+
'[attr.aria-describedby]': 'descriptionId',
|
|
624
|
+
'[attr.aria-disabled]': 'disabled() ? "true" : undefined',
|
|
625
|
+
'[attr.data-disabled]': 'disabled() ? "" : undefined',
|
|
626
|
+
'[attr.data-dragging]': 'isDragging() ? "" : undefined'
|
|
648
627
|
}
|
|
649
628
|
}]
|
|
650
|
-
}], ctorParameters: () => [], propDecorators: { image: [{ type: i0.Input, args: [{ isSignal: true, alias: "image", required: true }] }], cropPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "cropPadding", required: false }] }], aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], minZoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "minZoom", required: false }] }], maxZoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxZoom", required: false }] }], zoomSensitivity: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoomSensitivity", required: false }] }], keyboardStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardStep", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], onCropChange: [{ type: i0.Output, args: ["onCropChange"] }], onZoomChange: [{ type: i0.Output, args: ["onZoomChange"] }] } });
|
|
629
|
+
}], ctorParameters: () => [], propDecorators: { image: [{ type: i0.Input, args: [{ isSignal: true, alias: "image", required: true }] }], cropPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "cropPadding", required: false }] }], aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], minZoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "minZoom", required: false }] }], maxZoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxZoom", required: false }] }], zoomSensitivity: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoomSensitivity", required: false }] }], keyboardStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardStep", required: false }] }], zoomKeyboardStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoomKeyboardStep", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], onCropChange: [{ type: i0.Output, args: ["onCropChange"] }], onZoomChange: [{ type: i0.Output, args: ["onZoomChange"] }] } });
|
|
651
630
|
|
|
652
631
|
const _imports = [
|
|
653
632
|
RdxCropperRootDirective,
|
|
@@ -678,5 +657,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
678
657
|
* Generated bundle index. Do not edit.
|
|
679
658
|
*/
|
|
680
659
|
|
|
681
|
-
export {
|
|
660
|
+
export { RdxCropperCropAreaDirective, RdxCropperDescriptionDirective, RdxCropperImageComponent, RdxCropperModule, RdxCropperRootDirective, injectCropperRootContext, provideCropperRootContext };
|
|
682
661
|
//# sourceMappingURL=radix-ng-primitives-cropper.mjs.map
|