@radix-ng/primitives 0.51.0 → 1.0.0-beta.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.
- package/fesm2022/radix-ng-primitives-accordion.mjs +105 -38
- package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs +221 -129
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-arrow.mjs +20 -4
- package/fesm2022/radix-ng-primitives-arrow.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-aspect-ratio.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-avatar.mjs +54 -61
- package/fesm2022/radix-ng-primitives-avatar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-button.mjs +123 -0
- package/fesm2022/radix-ng-primitives-button.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-calendar.mjs +95 -83
- package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-checkbox.mjs +378 -54
- package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-collapsible.mjs +182 -81
- package/fesm2022/radix-ng-primitives-collapsible.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-collection.mjs +40 -57
- package/fesm2022/radix-ng-primitives-collection.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-config.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-context-menu.mjs +140 -424
- package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +845 -744
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-cropper.mjs +288 -308
- package/fesm2022/radix-ng-primitives-cropper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +104 -58
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-dialog.mjs +655 -327
- package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-dismissable-layer.mjs +70 -46
- package/fesm2022/radix-ng-primitives-dismissable-layer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-drawer.mjs +960 -0
- package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-editable.mjs +304 -23
- package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-field.mjs +363 -0
- package/fesm2022/radix-ng-primitives-field.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-fieldset.mjs +79 -0
- package/fesm2022/radix-ng-primitives-fieldset.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-focus-scope.mjs +23 -8
- package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-input.mjs +172 -0
- package/fesm2022/radix-ng-primitives-input.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-label.mjs +6 -6
- package/fesm2022/radix-ng-primitives-label.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +1907 -363
- package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menubar.mjs +290 -162
- package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-meter.mjs +271 -0
- package/fesm2022/radix-ng-primitives-meter.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +1052 -1553
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-number-field.mjs +1102 -367
- package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-pagination.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs +978 -989
- package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +111 -44
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-portal.mjs +34 -10
- package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-presence.mjs +134 -246
- package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-preview-card.mjs +997 -0
- package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-progress.mjs +223 -84
- package/fesm2022/radix-ng-primitives-progress.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-radio.mjs +191 -51
- package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-roving-focus.mjs +96 -50
- 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 +791 -509
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-separator.mjs +12 -35
- package/fesm2022/radix-ng-primitives-separator.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs +969 -717
- package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-stepper.mjs +15 -19
- package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-switch.mjs +125 -113
- package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tabs.mjs +390 -108
- package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-time-field.mjs +55 -46
- 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 +121 -247
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle.mjs +98 -61
- package/fesm2022/radix-ng-primitives-toggle.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toolbar.mjs +303 -92
- package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tooltip.mjs +699 -1072
- package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-visually-hidden.mjs +25 -66
- package/fesm2022/radix-ng-primitives-visually-hidden.mjs.map +1 -1
- package/meter/README.md +3 -0
- package/navigation-menu/README.md +2 -1
- package/package.json +39 -18
- package/portal/README.md +2 -0
- package/preview-card/README.md +3 -0
- package/schematics/collection.json +1 -0
- package/schematics/ng-add/index.d.ts +3 -2
- package/schematics/ng-add/index.js +62 -31
- package/schematics/ng-add/index.js.map +1 -1
- package/schematics/ng-add/package-config.d.ts +4 -2
- package/schematics/ng-add/package-config.js +10 -2
- package/schematics/ng-add/package-config.js.map +1 -1
- package/schematics/ng-add/schema.d.ts +3 -0
- package/schematics/ng-add/schema.js +3 -0
- package/schematics/ng-add/schema.js.map +1 -0
- package/schematics/ng-add/schema.json +14 -0
- package/select/README.md +2 -0
- package/types/radix-ng-primitives-accordion.d.ts +51 -16
- package/types/radix-ng-primitives-alert-dialog.d.ts +95 -38
- package/types/radix-ng-primitives-arrow.d.ts +1 -1
- package/types/radix-ng-primitives-aspect-ratio.d.ts +1 -1
- package/types/radix-ng-primitives-avatar.d.ts +7 -11
- package/types/radix-ng-primitives-button.d.ts +73 -0
- package/types/radix-ng-primitives-calendar.d.ts +39 -20
- package/types/radix-ng-primitives-checkbox.d.ts +204 -35
- package/types/radix-ng-primitives-collapsible.d.ts +114 -40
- package/types/radix-ng-primitives-collection.d.ts +38 -34
- package/types/radix-ng-primitives-config.d.ts +1 -1
- package/types/radix-ng-primitives-context-menu.d.ts +61 -116
- package/types/radix-ng-primitives-core.d.ts +345 -235
- package/types/radix-ng-primitives-cropper.d.ts +89 -56
- package/types/radix-ng-primitives-date-field.d.ts +49 -28
- package/types/radix-ng-primitives-dialog.d.ts +283 -165
- package/types/radix-ng-primitives-dismissable-layer.d.ts +15 -7
- package/types/radix-ng-primitives-drawer.d.ts +426 -0
- package/types/radix-ng-primitives-editable.d.ts +91 -14
- package/types/radix-ng-primitives-field.d.ts +374 -0
- package/types/radix-ng-primitives-fieldset.d.ts +49 -0
- package/types/radix-ng-primitives-focus-scope.d.ts +15 -6
- package/types/radix-ng-primitives-input.d.ts +87 -0
- package/types/radix-ng-primitives-label.d.ts +0 -1
- package/types/radix-ng-primitives-menu.d.ts +584 -99
- package/types/radix-ng-primitives-menubar.d.ts +61 -50
- package/types/radix-ng-primitives-meter.d.ts +194 -0
- package/types/radix-ng-primitives-navigation-menu.d.ts +422 -340
- package/types/radix-ng-primitives-number-field.d.ts +405 -145
- package/types/radix-ng-primitives-pagination.d.ts +2 -2
- package/types/radix-ng-primitives-popover.d.ts +366 -351
- package/types/radix-ng-primitives-popper.d.ts +68 -11
- package/types/radix-ng-primitives-portal.d.ts +14 -6
- package/types/radix-ng-primitives-presence.d.ts +28 -76
- package/types/radix-ng-primitives-preview-card.d.ts +359 -0
- package/types/radix-ng-primitives-progress.d.ts +175 -48
- package/types/radix-ng-primitives-radio.d.ts +55 -25
- package/types/radix-ng-primitives-roving-focus.d.ts +33 -23
- package/types/radix-ng-primitives-scroll-area.d.ts +253 -0
- package/types/radix-ng-primitives-select.d.ts +475 -177
- package/types/radix-ng-primitives-separator.d.ts +7 -32
- package/types/radix-ng-primitives-slider.d.ts +315 -201
- package/types/radix-ng-primitives-stepper.d.ts +5 -7
- package/types/radix-ng-primitives-switch.d.ts +86 -71
- package/types/radix-ng-primitives-tabs.d.ts +213 -79
- package/types/radix-ng-primitives-time-field.d.ts +42 -27
- package/types/radix-ng-primitives-toast.d.ts +378 -0
- package/types/radix-ng-primitives-toggle-group.d.ts +86 -164
- package/types/radix-ng-primitives-toggle.d.ts +43 -53
- package/types/radix-ng-primitives-toolbar.d.ts +164 -38
- package/types/radix-ng-primitives-tooltip.d.ts +348 -384
- package/types/radix-ng-primitives-visually-hidden.d.ts +19 -19
- package/dropdown-menu/README.md +0 -1
- package/fesm2022/radix-ng-primitives-dropdown-menu.mjs +0 -581
- package/fesm2022/radix-ng-primitives-dropdown-menu.mjs.map +0 -1
- package/fesm2022/radix-ng-primitives-hover-card.mjs +0 -1238
- package/fesm2022/radix-ng-primitives-hover-card.mjs.map +0 -1
- package/fesm2022/radix-ng-primitives-select2.mjs +0 -897
- package/fesm2022/radix-ng-primitives-select2.mjs.map +0 -1
- package/fesm2022/radix-ng-primitives-tooltip2.mjs +0 -735
- package/fesm2022/radix-ng-primitives-tooltip2.mjs.map +0 -1
- package/hover-card/README.md +0 -3
- package/select2/README.md +0 -3
- package/tooltip2/README.md +0 -3
- package/types/radix-ng-primitives-dropdown-menu.d.ts +0 -171
- package/types/radix-ng-primitives-hover-card.d.ts +0 -471
- package/types/radix-ng-primitives-select2.d.ts +0 -511
- package/types/radix-ng-primitives-tooltip2.d.ts +0 -325
|
@@ -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');
|
|
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,24 +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
|
|
|
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
|
+
};
|
|
84
156
|
class RdxCropperRootDirective {
|
|
85
157
|
constructor() {
|
|
86
158
|
this.elementRef = inject((ElementRef));
|
|
159
|
+
this.injector = inject(Injector);
|
|
160
|
+
this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
87
161
|
this.CROPPER_DESC_WARN_MESSAGE = `Warning: \`Cropper\` requires a description element for accessibility.`;
|
|
88
162
|
this.image = input.required(...(ngDevMode ? [{ debugName: "image" }] : /* istanbul ignore next */ []));
|
|
89
163
|
this.cropPadding = input(25, { ...(ngDevMode ? { debugName: "cropPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
@@ -91,49 +165,119 @@ class RdxCropperRootDirective {
|
|
|
91
165
|
this.minZoom = input(1, { ...(ngDevMode ? { debugName: "minZoom" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
92
166
|
this.maxZoom = input(3, { ...(ngDevMode ? { debugName: "maxZoom" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
93
167
|
this.zoomSensitivity = input(0.005, { ...(ngDevMode ? { debugName: "zoomSensitivity" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
168
|
+
/** Pan distance (px) per arrow-key press. */
|
|
94
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 });
|
|
95
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 });
|
|
96
177
|
this.onCropChange = output();
|
|
97
178
|
this.onZoomChange = output();
|
|
98
179
|
// State signals
|
|
99
180
|
this.imgWidth = signal(null, ...(ngDevMode ? [{ debugName: "imgWidth" }] : /* istanbul ignore next */ []));
|
|
100
181
|
this.imgHeight = signal(null, ...(ngDevMode ? [{ debugName: "imgHeight" }] : /* istanbul ignore next */ []));
|
|
101
|
-
|
|
102
|
-
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 */ []));
|
|
103
205
|
this.imageWrapperWidth = signal(0, ...(ngDevMode ? [{ debugName: "imageWrapperWidth" }] : /* istanbul ignore next */ []));
|
|
104
206
|
this.imageWrapperHeight = signal(0, ...(ngDevMode ? [{ debugName: "imageWrapperHeight" }] : /* istanbul ignore next */ []));
|
|
105
|
-
|
|
106
|
-
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 */ []));
|
|
107
209
|
this.internalZoom = signal(this.minZoom(), ...(ngDevMode ? [{ debugName: "internalZoom" }] : /* istanbul ignore next */ []));
|
|
108
210
|
this.isDragging = signal(false, ...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ []));
|
|
109
|
-
|
|
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-');
|
|
110
214
|
this.isZoomControlled = computed(() => this.zoom() !== undefined, ...(ngDevMode ? [{ debugName: "isZoomControlled" }] : /* istanbul ignore next */ []));
|
|
111
215
|
this.effectiveZoom = computed(() => (this.isZoomControlled() ? this.zoom() : this.internalZoom()), ...(ngDevMode ? [{ debugName: "effectiveZoom" }] : /* istanbul ignore next */ []));
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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 });
|
|
116
232
|
// Refs
|
|
117
233
|
this.dragStartPoint = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "dragStartPoint" }] : /* istanbul ignore next */ []));
|
|
118
234
|
this.dragStartOffset = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "dragStartOffset" }] : /* istanbul ignore next */ []));
|
|
119
|
-
this.latestRestrictedOffset = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "latestRestrictedOffset" }] : /* istanbul ignore next */ []));
|
|
120
235
|
this.latestZoom = signal(this.minZoom(), ...(ngDevMode ? [{ debugName: "latestZoom" }] : /* istanbul ignore next */ []));
|
|
121
|
-
this.isInitialSetupDone = signal(false, ...(ngDevMode ? [{ debugName: "isInitialSetupDone" }] : /* istanbul ignore next */ []));
|
|
122
236
|
this.initialPinchDistance = signal(0, ...(ngDevMode ? [{ debugName: "initialPinchDistance" }] : /* istanbul ignore next */ []));
|
|
123
237
|
this.initialPinchZoom = signal(1, ...(ngDevMode ? [{ debugName: "initialPinchZoom" }] : /* istanbul ignore next */ []));
|
|
124
238
|
this.isPinching = signal(false, ...(ngDevMode ? [{ debugName: "isPinching" }] : /* istanbul ignore next */ []));
|
|
125
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 */ []));
|
|
126
266
|
afterNextRender(() => {
|
|
127
267
|
this.initializeContainerDimensions();
|
|
128
268
|
});
|
|
129
269
|
this.setupImageLoadEffect();
|
|
130
270
|
this.setupDimensionsEffects();
|
|
131
|
-
this.setupCropCalculationEffect();
|
|
132
271
|
this.setupAccessibilityWarningEffect();
|
|
133
272
|
this.setupEventListenersEffect();
|
|
134
273
|
effect(() => {
|
|
135
274
|
this.latestZoom.set(this.effectiveZoom());
|
|
136
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
|
+
});
|
|
137
281
|
}
|
|
138
282
|
updateZoom(newZoomValue) {
|
|
139
283
|
const clampedZoom = clamp(newZoomValue, this.minZoom(), this.maxZoom());
|
|
@@ -146,20 +290,18 @@ class RdxCropperRootDirective {
|
|
|
146
290
|
initializeContainerDimensions() {
|
|
147
291
|
const element = this.elementRef.nativeElement;
|
|
148
292
|
if (element && element.clientWidth > 0 && element.clientHeight > 0) {
|
|
149
|
-
|
|
150
|
-
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 });
|
|
151
295
|
}
|
|
152
296
|
}
|
|
153
297
|
setupImageLoadEffect() {
|
|
154
298
|
effect(() => {
|
|
155
299
|
const image = this.image();
|
|
156
|
-
this.
|
|
157
|
-
this.offsetY.set(0);
|
|
300
|
+
this.offset.set({ x: 0, y: 0 });
|
|
158
301
|
if (!this.isZoomControlled()) {
|
|
159
302
|
this.internalZoom.set(this.minZoom());
|
|
160
303
|
}
|
|
161
|
-
this.
|
|
162
|
-
if (!image) {
|
|
304
|
+
if (!image || !this.isBrowser) {
|
|
163
305
|
this.imgWidth.set(null);
|
|
164
306
|
this.imgHeight.set(null);
|
|
165
307
|
return;
|
|
@@ -185,44 +327,20 @@ class RdxCropperRootDirective {
|
|
|
185
327
|
});
|
|
186
328
|
}
|
|
187
329
|
setupDimensionsEffects() {
|
|
188
|
-
effect
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
const maxPossibleWidth = Math.max(0, width - this.cropPadding() * 2);
|
|
199
|
-
const maxPossibleHeight = Math.max(0, height - this.cropPadding() * 2);
|
|
200
|
-
let targetCropW, targetCropH;
|
|
201
|
-
if (maxPossibleWidth / this.aspectRatio() >= maxPossibleHeight) {
|
|
202
|
-
targetCropH = maxPossibleHeight;
|
|
203
|
-
targetCropW = targetCropH * this.aspectRatio();
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
targetCropW = maxPossibleWidth;
|
|
207
|
-
targetCropH = targetCropW / this.aspectRatio();
|
|
208
|
-
}
|
|
209
|
-
this.cropAreaWidth.set(targetCropW);
|
|
210
|
-
this.cropAreaHeight.set(targetCropH);
|
|
211
|
-
};
|
|
212
|
-
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) => {
|
|
213
338
|
for (const entry of entries) {
|
|
214
339
|
const { width, height } = entry.contentRect;
|
|
215
340
|
if (width > 0 && height > 0)
|
|
216
|
-
|
|
341
|
+
this.containerSize.set({ width, height });
|
|
217
342
|
}
|
|
218
|
-
});
|
|
219
|
-
observer.observe(element);
|
|
220
|
-
const initialWidth = element.clientWidth;
|
|
221
|
-
const initialHeight = element.clientHeight;
|
|
222
|
-
if (initialWidth > 0 && initialHeight > 0) {
|
|
223
|
-
updateDimensions(initialWidth, initialHeight);
|
|
224
343
|
}
|
|
225
|
-
return () => observer.disconnect();
|
|
226
344
|
});
|
|
227
345
|
// Update image wrapper dimensions when crop area or image dimensions change
|
|
228
346
|
effect(() => {
|
|
@@ -250,110 +368,25 @@ class RdxCropperRootDirective {
|
|
|
250
368
|
this.imageWrapperHeight.set(targetWrapperHeight);
|
|
251
369
|
});
|
|
252
370
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const wrapperH = this.imageWrapperHeight();
|
|
256
|
-
const cropW = this.cropAreaWidth();
|
|
257
|
-
const cropH = this.cropAreaHeight();
|
|
258
|
-
if (wrapperW <= 0 || wrapperH <= 0 || cropW <= 0 || cropH <= 0) {
|
|
259
|
-
return { x: 0, y: 0 };
|
|
260
|
-
}
|
|
261
|
-
const effectiveWrapperWidth = wrapperW * currentZoom;
|
|
262
|
-
const effectiveWrapperHeight = wrapperH * currentZoom;
|
|
263
|
-
const maxDragX = Math.max(0, (effectiveWrapperWidth - cropW) / 2);
|
|
264
|
-
const maxDragY = Math.max(0, (effectiveWrapperHeight - cropH) / 2);
|
|
371
|
+
/** Current rendered geometry the crop math operates on, read from the state signals. */
|
|
372
|
+
geometry() {
|
|
265
373
|
return {
|
|
266
|
-
|
|
267
|
-
|
|
374
|
+
wrapperWidth: this.imageWrapperWidth(),
|
|
375
|
+
wrapperHeight: this.imageWrapperHeight(),
|
|
376
|
+
cropWidth: this.cropAreaWidth(),
|
|
377
|
+
cropHeight: this.cropAreaHeight()
|
|
268
378
|
};
|
|
269
379
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const currentOffsetY = finalOffsetY ?? this.latestRestrictedOffset().y;
|
|
273
|
-
const currentZoom = finalZoom ?? this.effectiveZoom();
|
|
274
|
-
const imgW = this.imgWidth();
|
|
275
|
-
const imgH = this.imgHeight();
|
|
276
|
-
const wrapperW = this.imageWrapperWidth();
|
|
277
|
-
const wrapperH = this.imageWrapperHeight();
|
|
278
|
-
const cropW = this.cropAreaWidth();
|
|
279
|
-
const cropH = this.cropAreaHeight();
|
|
280
|
-
if (!imgW || !imgH || wrapperW <= 0 || wrapperH <= 0 || cropW <= 0 || cropH <= 0) {
|
|
281
|
-
return null;
|
|
282
|
-
}
|
|
283
|
-
const scaledWrapperWidth = wrapperW * currentZoom;
|
|
284
|
-
const scaledWrapperHeight = wrapperH * currentZoom;
|
|
285
|
-
const topLeftOffsetX = currentOffsetX + (cropW - scaledWrapperWidth) / 2;
|
|
286
|
-
const topLeftOffsetY = currentOffsetY + (cropH - scaledWrapperHeight) / 2;
|
|
287
|
-
const baseScale = imgW / wrapperW;
|
|
288
|
-
if (isNaN(baseScale) || baseScale === 0) {
|
|
289
|
-
return null;
|
|
290
|
-
}
|
|
291
|
-
const sx = (-topLeftOffsetX * baseScale) / currentZoom;
|
|
292
|
-
const sy = (-topLeftOffsetY * baseScale) / currentZoom;
|
|
293
|
-
const sWidth = (cropW * baseScale) / currentZoom;
|
|
294
|
-
const sHeight = (cropH * baseScale) / currentZoom;
|
|
295
|
-
const finalX = clamp(Math.round(sx), 0, imgW);
|
|
296
|
-
const finalY = clamp(Math.round(sy), 0, imgH);
|
|
297
|
-
const finalWidth = clamp(Math.round(sWidth), 0, imgW - finalX);
|
|
298
|
-
const finalHeight = clamp(Math.round(sHeight), 0, imgH - finalY);
|
|
299
|
-
if (finalWidth <= 0 || finalHeight <= 0) {
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
302
|
-
return { x: finalX, y: finalY, width: finalWidth, height: finalHeight };
|
|
303
|
-
}
|
|
304
|
-
setupCropCalculationEffect() {
|
|
305
|
-
effect(() => {
|
|
306
|
-
const wrapperW = this.imageWrapperWidth();
|
|
307
|
-
const wrapperH = this.imageWrapperHeight();
|
|
308
|
-
const cropW = this.cropAreaWidth();
|
|
309
|
-
const cropH = this.cropAreaHeight();
|
|
310
|
-
const currentZoom = this.effectiveZoom();
|
|
311
|
-
if (wrapperW > 0 && wrapperH > 0 && cropW > 0 && cropH > 0) {
|
|
312
|
-
if (!this.isInitialSetupDone()) {
|
|
313
|
-
const restrictedInitial = this.restrictOffset(0, 0, currentZoom);
|
|
314
|
-
this.offsetX.set(restrictedInitial.x);
|
|
315
|
-
this.offsetY.set(restrictedInitial.y);
|
|
316
|
-
if (!this.isZoomControlled()) {
|
|
317
|
-
this.internalZoom.set(currentZoom);
|
|
318
|
-
}
|
|
319
|
-
this.dragStartOffset.set(restrictedInitial);
|
|
320
|
-
this.latestRestrictedOffset.set(restrictedInitial);
|
|
321
|
-
this.latestZoom.set(currentZoom);
|
|
322
|
-
this.onCropChange.emit(this.calculateCropData(restrictedInitial.x, restrictedInitial.y, currentZoom));
|
|
323
|
-
this.isInitialSetupDone.set(true);
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
const currentX = this.latestRestrictedOffset().x;
|
|
327
|
-
const currentY = this.latestRestrictedOffset().y;
|
|
328
|
-
const restrictedCurrent = this.restrictOffset(currentX, currentY, currentZoom);
|
|
329
|
-
if (restrictedCurrent.x !== currentX || restrictedCurrent.y !== currentY) {
|
|
330
|
-
this.offsetX.set(restrictedCurrent.x);
|
|
331
|
-
this.offsetY.set(restrictedCurrent.y);
|
|
332
|
-
this.latestRestrictedOffset.set(restrictedCurrent);
|
|
333
|
-
this.dragStartOffset.set(restrictedCurrent);
|
|
334
|
-
}
|
|
335
|
-
this.onCropChange.emit(this.calculateCropData(restrictedCurrent.x, restrictedCurrent.y, currentZoom));
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
this.isInitialSetupDone.set(false);
|
|
340
|
-
this.offsetX.set(0);
|
|
341
|
-
this.offsetY.set(0);
|
|
342
|
-
if (!this.isZoomControlled()) {
|
|
343
|
-
this.internalZoom.set(this.minZoom());
|
|
344
|
-
}
|
|
345
|
-
this.dragStartOffset.set({ x: 0, y: 0 });
|
|
346
|
-
this.latestRestrictedOffset.set({ x: 0, y: 0 });
|
|
347
|
-
this.latestZoom.set(currentZoom);
|
|
348
|
-
this.onCropChange.emit(null);
|
|
349
|
-
}
|
|
350
|
-
});
|
|
380
|
+
restrictOffset(dragOffsetX, dragOffsetY, currentZoom) {
|
|
381
|
+
return restrictOffset(dragOffsetX, dragOffsetY, currentZoom, this.geometry());
|
|
351
382
|
}
|
|
352
383
|
setupAccessibilityWarningEffect() {
|
|
384
|
+
if (!this.isBrowser)
|
|
385
|
+
return;
|
|
353
386
|
effect(() => {
|
|
354
387
|
const checkTimeout = setTimeout(() => {
|
|
355
388
|
if (this.elementRef.nativeElement && !this.hasWarned()) {
|
|
356
|
-
const hasDescription = document.getElementById(this.descriptionId
|
|
389
|
+
const hasDescription = document.getElementById(this.descriptionId);
|
|
357
390
|
if (!hasDescription) {
|
|
358
391
|
console.warn(this.CROPPER_DESC_WARN_MESSAGE);
|
|
359
392
|
this.hasWarned.set(true);
|
|
@@ -363,22 +396,36 @@ class RdxCropperRootDirective {
|
|
|
363
396
|
return () => clearTimeout(checkTimeout);
|
|
364
397
|
});
|
|
365
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
|
+
*/
|
|
366
405
|
setupEventListenersEffect() {
|
|
406
|
+
if (!this.isBrowser)
|
|
407
|
+
return;
|
|
367
408
|
effect((onCleanup) => {
|
|
368
409
|
const node = this.elementRef.nativeElement;
|
|
369
|
-
if (!node)
|
|
410
|
+
if (!node || this.disabled())
|
|
370
411
|
return;
|
|
371
412
|
const options = { passive: false };
|
|
413
|
+
const mouseDownHandler = (e) => this.onMouseDown(e);
|
|
414
|
+
const keyDownHandler = (e) => this.onKeyDown(e);
|
|
372
415
|
const wheelHandler = (e) => this.handleWheel(e);
|
|
373
416
|
const touchStartHandler = (e) => this.handleTouchStart(e);
|
|
374
417
|
const touchMoveHandler = (e) => this.handleTouchMove(e);
|
|
375
418
|
const touchEndHandler = (e) => this.handleTouchEnd(e);
|
|
419
|
+
node.addEventListener('mousedown', mouseDownHandler, options);
|
|
420
|
+
node.addEventListener('keydown', keyDownHandler, options);
|
|
376
421
|
node.addEventListener('wheel', wheelHandler, options);
|
|
377
422
|
node.addEventListener('touchstart', touchStartHandler, options);
|
|
378
423
|
node.addEventListener('touchmove', touchMoveHandler, options);
|
|
379
424
|
node.addEventListener('touchend', touchEndHandler, options);
|
|
380
425
|
node.addEventListener('touchcancel', touchEndHandler, options);
|
|
381
426
|
onCleanup(() => {
|
|
427
|
+
node.removeEventListener('mousedown', mouseDownHandler, options);
|
|
428
|
+
node.removeEventListener('keydown', keyDownHandler, options);
|
|
382
429
|
node.removeEventListener('wheel', wheelHandler, options);
|
|
383
430
|
node.removeEventListener('touchstart', touchStartHandler, options);
|
|
384
431
|
node.removeEventListener('touchmove', touchMoveHandler, options);
|
|
@@ -387,13 +434,6 @@ class RdxCropperRootDirective {
|
|
|
387
434
|
});
|
|
388
435
|
});
|
|
389
436
|
}
|
|
390
|
-
handleInteractionEnd() {
|
|
391
|
-
const finalData = this.calculateCropData(this.latestRestrictedOffset().x, this.latestRestrictedOffset().y, this.effectiveZoom());
|
|
392
|
-
this.onCropChange.emit(finalData);
|
|
393
|
-
}
|
|
394
|
-
/**
|
|
395
|
-
* @ignore
|
|
396
|
-
*/
|
|
397
437
|
onMouseDown(e) {
|
|
398
438
|
if (e.button !== 0)
|
|
399
439
|
return;
|
|
@@ -401,52 +441,49 @@ class RdxCropperRootDirective {
|
|
|
401
441
|
this.isDragging.set(true);
|
|
402
442
|
this.isPinching.set(false);
|
|
403
443
|
this.dragStartPoint.set({ x: e.clientX, y: e.clientY });
|
|
404
|
-
this.dragStartOffset.set(this.
|
|
444
|
+
this.dragStartOffset.set(this.clampedOffset());
|
|
405
445
|
const handleMouseMove = (ev) => {
|
|
406
446
|
const deltaX = ev.clientX - this.dragStartPoint().x;
|
|
407
447
|
const deltaY = ev.clientY - this.dragStartPoint().y;
|
|
408
|
-
|
|
409
|
-
const targetOffsetY = this.dragStartOffset().y + deltaY;
|
|
410
|
-
const restricted = this.restrictOffset(targetOffsetX, targetOffsetY, this.effectiveZoom());
|
|
411
|
-
this.latestRestrictedOffset.set(restricted);
|
|
412
|
-
this.offsetX.set(restricted.x);
|
|
413
|
-
this.offsetY.set(restricted.y);
|
|
448
|
+
this.offset.set({ x: this.dragStartOffset().x + deltaX, y: this.dragStartOffset().y + deltaY });
|
|
414
449
|
};
|
|
415
450
|
const handleMouseUp = () => {
|
|
416
451
|
this.isDragging.set(false);
|
|
417
452
|
window.removeEventListener('mousemove', handleMouseMove);
|
|
418
453
|
window.removeEventListener('mouseup', handleMouseUp);
|
|
419
|
-
this.handleInteractionEnd();
|
|
420
454
|
};
|
|
421
455
|
window.addEventListener('mousemove', handleMouseMove);
|
|
422
456
|
window.addEventListener('mouseup', handleMouseUp);
|
|
423
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
|
+
}
|
|
424
477
|
handleWheel(e) {
|
|
425
478
|
e.preventDefault();
|
|
426
479
|
e.stopPropagation();
|
|
427
480
|
if (!this.elementRef.nativeElement || this.imageWrapperWidth() <= 0 || this.imageWrapperHeight() <= 0)
|
|
428
481
|
return;
|
|
429
|
-
const currentZoom = this.latestZoom();
|
|
430
|
-
const currentOffsetX = this.latestRestrictedOffset().x;
|
|
431
|
-
const currentOffsetY = this.latestRestrictedOffset().y;
|
|
432
482
|
const delta = e.deltaY * -this.zoomSensitivity();
|
|
433
|
-
const targetZoom = currentZoom + delta;
|
|
434
|
-
if (clamp(targetZoom, this.minZoom(), this.maxZoom()) === currentZoom)
|
|
435
|
-
return;
|
|
436
483
|
const rect = this.elementRef.nativeElement.getBoundingClientRect();
|
|
437
484
|
const pointerX = e.clientX - rect.left - rect.width / 2;
|
|
438
485
|
const pointerY = e.clientY - rect.top - rect.height / 2;
|
|
439
|
-
|
|
440
|
-
const imagePointY = (pointerY - currentOffsetY) / currentZoom;
|
|
441
|
-
const finalNewZoom = this.updateZoom(targetZoom);
|
|
442
|
-
const newOffsetX = pointerX - imagePointX * finalNewZoom;
|
|
443
|
-
const newOffsetY = pointerY - imagePointY * finalNewZoom;
|
|
444
|
-
const restrictedNewOffset = this.restrictOffset(newOffsetX, newOffsetY, finalNewZoom);
|
|
445
|
-
this.offsetX.set(restrictedNewOffset.x);
|
|
446
|
-
this.offsetY.set(restrictedNewOffset.y);
|
|
447
|
-
this.latestRestrictedOffset.set(restrictedNewOffset);
|
|
448
|
-
const finalData = this.calculateCropData(restrictedNewOffset.x, restrictedNewOffset.y, finalNewZoom);
|
|
449
|
-
this.onCropChange.emit(finalData);
|
|
486
|
+
this.zoomToPoint(pointerX, pointerY, this.effectiveZoom() + delta, this.effectiveZoom(), this.clampedOffset());
|
|
450
487
|
}
|
|
451
488
|
getPinchDistance(touches) {
|
|
452
489
|
return Math.sqrt(Math.pow(touches[1].clientX - touches[0].clientX, 2) + Math.pow(touches[1].clientY - touches[0].clientY, 2));
|
|
@@ -466,14 +503,14 @@ class RdxCropperRootDirective {
|
|
|
466
503
|
this.isDragging.set(true);
|
|
467
504
|
this.isPinching.set(false);
|
|
468
505
|
this.dragStartPoint.set({ x: touches[0].clientX, y: touches[0].clientY });
|
|
469
|
-
this.dragStartOffset.set(this.
|
|
506
|
+
this.dragStartOffset.set(this.clampedOffset());
|
|
470
507
|
}
|
|
471
508
|
else if (touches.length === 2) {
|
|
472
509
|
this.isDragging.set(false);
|
|
473
510
|
this.isPinching.set(true);
|
|
474
511
|
this.initialPinchDistance.set(this.getPinchDistance(touches));
|
|
475
512
|
this.initialPinchZoom.set(this.latestZoom());
|
|
476
|
-
this.dragStartOffset.set(this.
|
|
513
|
+
this.dragStartOffset.set(this.clampedOffset());
|
|
477
514
|
}
|
|
478
515
|
}
|
|
479
516
|
handleTouchMove(e) {
|
|
@@ -484,37 +521,16 @@ class RdxCropperRootDirective {
|
|
|
484
521
|
if (touches.length === 1 && this.isDragging() && !this.isPinching()) {
|
|
485
522
|
const deltaX = touches[0].clientX - this.dragStartPoint().x;
|
|
486
523
|
const deltaY = touches[0].clientY - this.dragStartPoint().y;
|
|
487
|
-
|
|
488
|
-
const targetOffsetY = this.dragStartOffset().y + deltaY;
|
|
489
|
-
const restricted = this.restrictOffset(targetOffsetX, targetOffsetY, this.effectiveZoom());
|
|
490
|
-
this.latestRestrictedOffset.set(restricted);
|
|
491
|
-
this.offsetX.set(restricted.x);
|
|
492
|
-
this.offsetY.set(restricted.y);
|
|
524
|
+
this.offset.set({ x: this.dragStartOffset().x + deltaX, y: this.dragStartOffset().y + deltaY });
|
|
493
525
|
}
|
|
494
526
|
else if (touches.length === 2 && this.isPinching()) {
|
|
495
|
-
const
|
|
496
|
-
const scale = currentPinchDistance / this.initialPinchDistance();
|
|
497
|
-
const currentZoom = this.initialPinchZoom();
|
|
498
|
-
const targetZoom = currentZoom * scale;
|
|
499
|
-
if (clamp(targetZoom, this.minZoom(), this.maxZoom()) === this.latestZoom())
|
|
500
|
-
return;
|
|
527
|
+
const scale = this.getPinchDistance(touches) / this.initialPinchDistance();
|
|
501
528
|
const pinchCenter = this.getPinchCenter(touches);
|
|
502
529
|
const rect = this.elementRef.nativeElement.getBoundingClientRect();
|
|
503
530
|
const pinchCenterX = pinchCenter.x - rect.left - rect.width / 2;
|
|
504
531
|
const pinchCenterY = pinchCenter.y - rect.top - rect.height / 2;
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const imagePointX = (pinchCenterX - currentOffsetX) / currentZoom;
|
|
508
|
-
const imagePointY = (pinchCenterY - currentOffsetY) / currentZoom;
|
|
509
|
-
const finalNewZoom = this.updateZoom(targetZoom);
|
|
510
|
-
const newOffsetX = pinchCenterX - imagePointX * finalNewZoom;
|
|
511
|
-
const newOffsetY = pinchCenterY - imagePointY * finalNewZoom;
|
|
512
|
-
const restrictedNewOffset = this.restrictOffset(newOffsetX, newOffsetY, finalNewZoom);
|
|
513
|
-
this.offsetX.set(restrictedNewOffset.x);
|
|
514
|
-
this.offsetY.set(restrictedNewOffset.y);
|
|
515
|
-
this.latestRestrictedOffset.set(restrictedNewOffset);
|
|
516
|
-
const finalData = this.calculateCropData(restrictedNewOffset.x, restrictedNewOffset.y, finalNewZoom);
|
|
517
|
-
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());
|
|
518
534
|
}
|
|
519
535
|
}
|
|
520
536
|
handleTouchEnd(e) {
|
|
@@ -525,26 +541,22 @@ class RdxCropperRootDirective {
|
|
|
525
541
|
if (touches.length === 1) {
|
|
526
542
|
this.isDragging.set(true);
|
|
527
543
|
this.dragStartPoint.set({ x: touches[0].clientX, y: touches[0].clientY });
|
|
528
|
-
this.dragStartOffset.set(this.
|
|
544
|
+
this.dragStartOffset.set(this.clampedOffset());
|
|
529
545
|
}
|
|
530
546
|
else {
|
|
531
547
|
this.isDragging.set(false);
|
|
532
|
-
this.handleInteractionEnd();
|
|
533
548
|
}
|
|
534
549
|
}
|
|
535
550
|
else if (this.isDragging() && touches.length === 0) {
|
|
536
551
|
this.isDragging.set(false);
|
|
537
|
-
this.handleInteractionEnd();
|
|
538
552
|
}
|
|
539
553
|
}
|
|
540
|
-
/**
|
|
541
|
-
* @ignore
|
|
542
|
-
*/
|
|
543
554
|
onKeyDown(e) {
|
|
544
555
|
if (this.imageWrapperWidth() <= 0)
|
|
545
556
|
return;
|
|
546
|
-
|
|
547
|
-
let
|
|
557
|
+
const base = this.clampedOffset();
|
|
558
|
+
let targetOffsetX = base.x;
|
|
559
|
+
let targetOffsetY = base.y;
|
|
548
560
|
// eslint-disable-next-line no-useless-assignment
|
|
549
561
|
let moved = false;
|
|
550
562
|
switch (e.key) {
|
|
@@ -564,89 +576,57 @@ class RdxCropperRootDirective {
|
|
|
564
576
|
targetOffsetX -= this.keyboardStep();
|
|
565
577
|
moved = true;
|
|
566
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;
|
|
567
591
|
default:
|
|
568
592
|
return;
|
|
569
593
|
}
|
|
570
594
|
if (moved) {
|
|
571
595
|
e.preventDefault();
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
this.latestRestrictedOffset.set(restricted);
|
|
575
|
-
this.offsetX.set(restricted.x);
|
|
576
|
-
this.offsetY.set(restricted.y);
|
|
577
|
-
}
|
|
596
|
+
// Write the raw target; `clampedOffset` clamps it (and dedups a no-op at the boundary).
|
|
597
|
+
this.offset.set({ x: targetOffsetX, y: targetOffsetY });
|
|
578
598
|
}
|
|
579
599
|
}
|
|
580
|
-
/**
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
/**
|
|
589
|
-
* @ignore
|
|
590
|
-
*/
|
|
591
|
-
getImageProps() {
|
|
592
|
-
return {
|
|
593
|
-
src: this.image(),
|
|
594
|
-
alt: 'Image being cropped',
|
|
595
|
-
draggable: false,
|
|
596
|
-
'aria-hidden': true
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* @ignore
|
|
601
|
-
*/
|
|
602
|
-
getImageWrapperStyle() {
|
|
603
|
-
const wrapperW = this.imageWrapperWidth();
|
|
604
|
-
const wrapperH = this.imageWrapperHeight();
|
|
605
|
-
const offsetX = this.offsetX();
|
|
606
|
-
const offsetY = this.offsetY();
|
|
607
|
-
const zoom = this.effectiveZoom();
|
|
608
|
-
return {
|
|
609
|
-
width: `${wrapperW}px`,
|
|
610
|
-
height: `${wrapperH}px`,
|
|
611
|
-
transform: `translate3d(${offsetX}px, ${offsetY}px, 0px) scale(${zoom})`,
|
|
612
|
-
position: 'absolute',
|
|
613
|
-
left: `calc(50% - ${wrapperW / 2}px)`,
|
|
614
|
-
top: `calc(50% - ${wrapperH / 2}px)`,
|
|
615
|
-
willChange: 'transform'
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* @ignore
|
|
620
|
-
*/
|
|
621
|
-
getCropAreaStyle() {
|
|
622
|
-
return {
|
|
623
|
-
width: `${this.cropAreaWidth()}px`,
|
|
624
|
-
height: `${this.cropAreaHeight()}px`
|
|
625
|
-
};
|
|
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());
|
|
626
606
|
}
|
|
627
607
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCropperRootDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
628
|
-
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 }); }
|
|
629
609
|
}
|
|
630
610
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxCropperRootDirective, decorators: [{
|
|
631
611
|
type: Directive,
|
|
632
612
|
args: [{
|
|
633
613
|
selector: '[rdxCropperRoot]',
|
|
634
|
-
providers: [
|
|
614
|
+
providers: [provideCropperRootContext(rootContext)],
|
|
635
615
|
host: {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
'[attr.aria-valuenow]': 'effectiveZoom()',
|
|
641
|
-
'[attr.aria-valuetext]': 'zoomValueText()',
|
|
642
|
-
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.
|
|
643
620
|
role: 'application',
|
|
644
|
-
'
|
|
645
|
-
'
|
|
646
|
-
'
|
|
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'
|
|
647
627
|
}
|
|
648
628
|
}]
|
|
649
|
-
}], 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"] }] } });
|
|
650
630
|
|
|
651
631
|
const _imports = [
|
|
652
632
|
RdxCropperRootDirective,
|
|
@@ -677,5 +657,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
677
657
|
* Generated bundle index. Do not edit.
|
|
678
658
|
*/
|
|
679
659
|
|
|
680
|
-
export {
|
|
660
|
+
export { RdxCropperCropAreaDirective, RdxCropperDescriptionDirective, RdxCropperImageComponent, RdxCropperModule, RdxCropperRootDirective, injectCropperRootContext, provideCropperRootContext };
|
|
681
661
|
//# sourceMappingURL=radix-ng-primitives-cropper.mjs.map
|