@muxima-ui/core 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +15 -0
  2. package/fesm2022/muxima-ui-core-carousel.mjs +377 -0
  3. package/fesm2022/muxima-ui-core-carousel.mjs.map +1 -0
  4. package/fesm2022/muxima-ui-core-color-picker.mjs +755 -0
  5. package/fesm2022/muxima-ui-core-color-picker.mjs.map +1 -0
  6. package/fesm2022/muxima-ui-core-data-grid.mjs +1187 -0
  7. package/fesm2022/muxima-ui-core-data-grid.mjs.map +1 -0
  8. package/fesm2022/muxima-ui-core-editor.mjs +575 -0
  9. package/fesm2022/muxima-ui-core-editor.mjs.map +1 -0
  10. package/fesm2022/muxima-ui-core-image-cropper.mjs +568 -0
  11. package/fesm2022/muxima-ui-core-image-cropper.mjs.map +1 -0
  12. package/fesm2022/muxima-ui-core-kanban.mjs +369 -0
  13. package/fesm2022/muxima-ui-core-kanban.mjs.map +1 -0
  14. package/fesm2022/muxima-ui-core-notification-center.mjs +487 -0
  15. package/fesm2022/muxima-ui-core-notification-center.mjs.map +1 -0
  16. package/fesm2022/muxima-ui-core-sidebar.mjs +333 -0
  17. package/fesm2022/muxima-ui-core-sidebar.mjs.map +1 -0
  18. package/fesm2022/muxima-ui-core-timeline.mjs +117 -0
  19. package/fesm2022/muxima-ui-core-timeline.mjs.map +1 -0
  20. package/fesm2022/muxima-ui-core.mjs +1 -1
  21. package/fesm2022/muxima-ui-core.mjs.map +1 -1
  22. package/package.json +57 -2
  23. package/styles/base.css +17 -0
  24. package/styles/components/carousel.css +113 -0
  25. package/styles/components/color-picker.css +207 -0
  26. package/styles/components/data-grid.css +235 -0
  27. package/styles/components/editor.css +187 -0
  28. package/styles/components/image-cropper.css +113 -0
  29. package/styles/components/kanban.css +115 -0
  30. package/styles/components/notification-center.css +220 -0
  31. package/styles/components/sidebar.css +211 -0
  32. package/styles/components/timeline.css +180 -0
  33. package/styles/muxima.css +9 -0
  34. package/types/muxima-ui-core-carousel.d.ts +72 -0
  35. package/types/muxima-ui-core-color-picker.d.ts +153 -0
  36. package/types/muxima-ui-core-data-grid.d.ts +175 -0
  37. package/types/muxima-ui-core-editor.d.ts +104 -0
  38. package/types/muxima-ui-core-image-cropper.d.ts +105 -0
  39. package/types/muxima-ui-core-kanban.d.ts +83 -0
  40. package/types/muxima-ui-core-notification-center.d.ts +91 -0
  41. package/types/muxima-ui-core-sidebar.d.ts +115 -0
  42. package/types/muxima-ui-core-timeline.d.ts +59 -0
  43. package/types/muxima-ui-core.d.ts +1 -1
@@ -0,0 +1,568 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, booleanAttribute, numberAttribute, model, output, viewChild, inject, signal, computed, effect, untracked, DestroyRef, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { LiveAnnouncer } from '@angular/cdk/a11y';
4
+ import { mxUniqueId } from '@muxima-ui/core/form-field';
5
+
6
+ /** Mantém o rectângulo dentro dos limites, sem mudar o tamanho (a não ser que não caiba). */
7
+ function mxClampRect(r, bounds) {
8
+ const width = Math.min(r.width, bounds.width);
9
+ const height = Math.min(r.height, bounds.height);
10
+ return {
11
+ width,
12
+ height,
13
+ x: Math.min(Math.max(r.x, bounds.x), bounds.x + bounds.width - width),
14
+ y: Math.min(Math.max(r.y, bounds.y), bounds.y + bounds.height - height),
15
+ };
16
+ }
17
+ /** Maior rectângulo com a proporção dada, centrado nos limites (ocupa `fill` do espaço). */
18
+ function mxCenteredRect(bounds, ratio, fill = 0.8) {
19
+ let width = bounds.width * fill;
20
+ let height = bounds.height * fill;
21
+ if (ratio) {
22
+ if (width / height > ratio)
23
+ width = height * ratio;
24
+ else
25
+ height = width / ratio;
26
+ }
27
+ return {
28
+ x: bounds.x + (bounds.width - width) / 2,
29
+ y: bounds.y + (bounds.height - height) / 2,
30
+ width,
31
+ height,
32
+ };
33
+ }
34
+ /**
35
+ * Redimensiona a partir de um puxador por (dx, dy), respeitando a proporção, o tamanho
36
+ * mínimo e os limites. O lado oposto ao puxador fica parado.
37
+ */
38
+ function mxResizeRect(r, handle, dx, dy, bounds, ratio, min = 24) {
39
+ let { x, y, width, height } = r;
40
+ const right = x + width;
41
+ const bottom = y + height;
42
+ if (handle.includes('e'))
43
+ width = Math.min(Math.max(width + dx, min), bounds.x + bounds.width - x);
44
+ if (handle.includes('w')) {
45
+ const nx = Math.min(Math.max(x + dx, bounds.x), right - min);
46
+ width = right - nx;
47
+ x = nx;
48
+ }
49
+ if (handle.includes('s'))
50
+ height = Math.min(Math.max(height + dy, min), bounds.y + bounds.height - y);
51
+ if (handle.includes('n')) {
52
+ const ny = Math.min(Math.max(y + dy, bounds.y), bottom - min);
53
+ height = bottom - ny;
54
+ y = ny;
55
+ }
56
+ if (ratio) {
57
+ // o eixo que o puxador controla manda; nos cantos, o maior movimento
58
+ const horizontal = handle === 'e' || handle === 'w' || (handle.length === 2 && Math.abs(dx) >= Math.abs(dy));
59
+ if (horizontal)
60
+ height = width / ratio;
61
+ else
62
+ width = height * ratio;
63
+ // não sair dos limites: reduz ambos
64
+ const maxW = handle.includes('w') ? right - bounds.x : bounds.x + bounds.width - x;
65
+ const maxH = handle.includes('n') ? bottom - bounds.y : bounds.y + bounds.height - y;
66
+ const k = Math.min(1, maxW / width, maxH / height);
67
+ width *= k;
68
+ height *= k;
69
+ if (handle.includes('w'))
70
+ x = right - width;
71
+ if (handle.includes('n'))
72
+ y = bottom - height;
73
+ if (handle === 'e' || handle === 'w')
74
+ y = Math.min(Math.max(r.y + (r.height - height) / 2, bounds.y), bounds.y + bounds.height - height);
75
+ if (handle === 'n' || handle === 's')
76
+ x = Math.min(Math.max(r.x + (r.width - width) / 2, bounds.x), bounds.x + bounds.width - width);
77
+ }
78
+ return { x, y, width, height };
79
+ }
80
+
81
+ const HANDLES = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
82
+ /**
83
+ * Recortar uma imagem antes de a enviar: arrastar a área, redimensionar pelos cantos,
84
+ * zoom e rodar. Com teclado: setas movem a área, Alt + setas mudam o tamanho (Shift para
85
+ * passos maiores); cada mudança é anunciada.
86
+ *
87
+ * ```html
88
+ * <mx-image-cropper #c [src]="ficheiro" [aspectRatio]="1" round />
89
+ * <button mxButton (click)="c.toBlob().then(enviar)">Guardar</button>
90
+ * ```
91
+ *
92
+ * `src` aceita um URL ou um File. `toBlob()` devolve a imagem recortada na resolução original.
93
+ */
94
+ class MxImageCropper {
95
+ src = input(null, ...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
96
+ alt = input('Imagem a recortar', ...(ngDevMode ? [{ debugName: "alt" }] : /* istanbul ignore next */ []));
97
+ /** Largura / altura da área (1 = quadrado, 16/9...). null = livre. */
98
+ aspectRatio = input(null, ...(ngDevMode ? [{ debugName: "aspectRatio" }] : /* istanbul ignore next */ []));
99
+ /** Máscara redonda (fotografias de perfil). toBlob devolve PNG com cantos transparentes. */
100
+ round = input(false, { ...(ngDevMode ? { debugName: "round" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
101
+ controls = input(true, { ...(ngDevMode ? { debugName: "controls" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
102
+ height = input('320px', ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
103
+ /** Tamanho mínimo da área no ecrã, em px. */
104
+ minSize = input(40, { ...(ngDevMode ? { debugName: "minSize" } : /* istanbul ignore next */ {}), transform: numberAttribute });
105
+ zoom = model(1, ...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
106
+ rotation = model(0, ...(ngDevMode ? [{ debugName: "rotation" }] : /* istanbul ignore next */ []));
107
+ helpText = input('Setas movem a área. Alt e setas mudam o tamanho. Shift dá passos maiores.', ...(ngDevMode ? [{ debugName: "helpText" }] : /* istanbul ignore next */ []));
108
+ zoomText = input('Zoom', ...(ngDevMode ? [{ debugName: "zoomText" }] : /* istanbul ignore next */ []));
109
+ rotateLeftText = input('Rodar para a esquerda', ...(ngDevMode ? [{ debugName: "rotateLeftText" }] : /* istanbul ignore next */ []));
110
+ rotateRightText = input('Rodar para a direita', ...(ngDevMode ? [{ debugName: "rotateRightText" }] : /* istanbul ignore next */ []));
111
+ resetText = input('Repor', ...(ngDevMode ? [{ debugName: "resetText" }] : /* istanbul ignore next */ []));
112
+ /** Área recortada em píxeis da imagem original. */
113
+ areaChange = output();
114
+ stage = viewChild.required('stage');
115
+ img = viewChild('img', ...(ngDevMode ? [{ debugName: "img" }] : /* istanbul ignore next */ []));
116
+ announcer = inject(LiveAnnouncer);
117
+ helpId = mxUniqueId('mx-cropper-help');
118
+ handles = HANDLES;
119
+ url = signal(null, ...(ngDevMode ? [{ debugName: "url" }] : /* istanbul ignore next */ []));
120
+ natural = signal({ w: 0, h: 0 }, ...(ngDevMode ? [{ debugName: "natural" }] : /* istanbul ignore next */ []));
121
+ stageSize = signal({ w: 0, h: 0 }, ...(ngDevMode ? [{ debugName: "stageSize" }] : /* istanbul ignore next */ []));
122
+ ready = signal(false, ...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
123
+ box = signal({ x: 0, y: 0, width: 0, height: 0 }, ...(ngDevMode ? [{ debugName: "box" }] : /* istanbul ignore next */ []));
124
+ live = signal('', ...(ngDevMode ? [{ debugName: "live" }] : /* istanbul ignore next */ []));
125
+ objectUrl = null;
126
+ liveTimer;
127
+ /** Escala que faz a imagem caber no palco, vezes o zoom. */
128
+ fit = computed(() => {
129
+ const { w, h } = this.natural();
130
+ const s = this.stageSize();
131
+ if (!w || !h || !s.w || !s.h)
132
+ return 1;
133
+ const turned = Math.abs(this.rotation()) % 180 === 90;
134
+ const iw = turned ? h : w;
135
+ const ih = turned ? w : h;
136
+ return Math.min(s.w / iw, s.h / ih);
137
+ }, ...(ngDevMode ? [{ debugName: "fit" }] : /* istanbul ignore next */ []));
138
+ scale = computed(() => this.fit() * this.zoom(), ...(ngDevMode ? [{ debugName: "scale" }] : /* istanbul ignore next */ []));
139
+ zoomPct = computed(() => Math.round(this.zoom() * 100), ...(ngDevMode ? [{ debugName: "zoomPct" }] : /* istanbul ignore next */ []));
140
+ /** Rectângulo da imagem no palco (onde a área pode estar). */
141
+ bounds = computed(() => {
142
+ const { w, h } = this.natural();
143
+ const s = this.stageSize();
144
+ const turned = Math.abs(this.rotation()) % 180 === 90;
145
+ const iw = (turned ? h : w) * this.scale();
146
+ const ih = (turned ? w : h) * this.scale();
147
+ const bw = Math.min(iw, s.w);
148
+ const bh = Math.min(ih, s.h);
149
+ return { x: (s.w - bw) / 2, y: (s.h - bh) / 2, width: bw, height: bh };
150
+ }, ...(ngDevMode ? [{ debugName: "bounds" }] : /* istanbul ignore next */ []));
151
+ /** Área em píxeis da imagem (rodada). */
152
+ area = computed(() => {
153
+ const b = this.box();
154
+ const s = this.stageSize();
155
+ const k = 1 / this.scale();
156
+ const turned = Math.abs(this.rotation()) % 180 === 90;
157
+ const iw = (turned ? this.natural().h : this.natural().w) * this.scale();
158
+ const ih = (turned ? this.natural().w : this.natural().h) * this.scale();
159
+ const ox = (s.w - iw) / 2;
160
+ const oy = (s.h - ih) / 2;
161
+ return {
162
+ x: Math.round((b.x - ox) * k),
163
+ y: Math.round((b.y - oy) * k),
164
+ width: Math.round(b.width * k),
165
+ height: Math.round(b.height * k),
166
+ rotation: this.rotation(),
167
+ };
168
+ }, ...(ngDevMode ? [{ debugName: "area" }] : /* istanbul ignore next */ []));
169
+ boxLabel = computed(() => {
170
+ const a = this.area();
171
+ return `${a.width} por ${a.height} píxeis`;
172
+ }, ...(ngDevMode ? [{ debugName: "boxLabel" }] : /* istanbul ignore next */ []));
173
+ constructor() {
174
+ effect(() => {
175
+ const src = this.src();
176
+ this.ready.set(false);
177
+ if (this.objectUrl)
178
+ URL.revokeObjectURL(this.objectUrl);
179
+ this.objectUrl = null;
180
+ if (!src) {
181
+ this.url.set(null);
182
+ return;
183
+ }
184
+ if (typeof src === 'string')
185
+ this.url.set(src);
186
+ else {
187
+ this.objectUrl = URL.createObjectURL(src);
188
+ this.url.set(this.objectUrl);
189
+ }
190
+ });
191
+ // mantém a área dentro da imagem quando o zoom ou a rotação mudam
192
+ effect(() => {
193
+ const b = this.bounds();
194
+ if (!this.ready())
195
+ return;
196
+ const current = untracked(() => this.box());
197
+ const next = mxClampRect(current, b);
198
+ if (next.x !== current.x ||
199
+ next.y !== current.y ||
200
+ next.width !== current.width ||
201
+ next.height !== current.height) {
202
+ untracked(() => this.box.set(next));
203
+ }
204
+ });
205
+ effect(() => {
206
+ if (this.ready())
207
+ this.areaChange.emit(this.area());
208
+ });
209
+ const ro = typeof ResizeObserver === 'function' ? new ResizeObserver(() => this.measure()) : null;
210
+ queueMicrotask(() => ro?.observe(this.stage().nativeElement));
211
+ inject(DestroyRef).onDestroy(() => {
212
+ ro?.disconnect();
213
+ if (this.objectUrl)
214
+ URL.revokeObjectURL(this.objectUrl);
215
+ clearTimeout(this.liveTimer);
216
+ });
217
+ }
218
+ measure() {
219
+ const r = this.stage().nativeElement.getBoundingClientRect();
220
+ this.stageSize.set({ w: r.width, h: r.height });
221
+ }
222
+ onLoad() {
223
+ const el = this.img().nativeElement;
224
+ this.natural.set({ w: el.naturalWidth, h: el.naturalHeight });
225
+ this.measure();
226
+ this.ready.set(true);
227
+ this.reset();
228
+ }
229
+ /** Volta ao zoom 1, sem rotação, área centrada. */
230
+ reset() {
231
+ this.zoom.set(1);
232
+ this.rotation.set(0);
233
+ queueMicrotask(() => this.box.set(mxCenteredRect(this.bounds(), this.aspectRatio())));
234
+ }
235
+ setZoom(z) {
236
+ const prev = this.box();
237
+ this.zoom.set(Math.min(Math.max(z, 1), 4));
238
+ this.box.set(mxClampRect(prev, this.bounds()));
239
+ }
240
+ rotate(deg) {
241
+ this.rotation.update((r) => (r + deg + 360) % 360);
242
+ this.box.set(mxCenteredRect(this.bounds(), this.aspectRatio()));
243
+ this.say(`Rodada ${this.rotation()} graus`);
244
+ }
245
+ say(text) {
246
+ clearTimeout(this.liveTimer);
247
+ this.liveTimer = setTimeout(() => this.live.set(text), 250);
248
+ }
249
+ onKeydown(e) {
250
+ const step = e.shiftKey ? 10 : 1;
251
+ const d = {
252
+ ArrowLeft: [-step, 0],
253
+ ArrowRight: [step, 0],
254
+ ArrowUp: [0, -step],
255
+ ArrowDown: [0, step],
256
+ };
257
+ const m = d[e.key];
258
+ if (!m)
259
+ return;
260
+ e.preventDefault();
261
+ const b = this.box();
262
+ if (e.altKey) {
263
+ // Alt: muda o tamanho a partir do canto inferior direito
264
+ this.box.set(mxResizeRect(b, 'se', m[0], m[1] || (this.aspectRatio() ? m[0] / this.aspectRatio() : 0), this.bounds(), this.aspectRatio(), this.minSize()));
265
+ }
266
+ else {
267
+ this.box.set(mxClampRect({ ...b, x: b.x + m[0], y: b.y + m[1] }, this.bounds()));
268
+ }
269
+ const a = this.area();
270
+ this.say(`${a.width} por ${a.height} píxeis, a ${a.x} da esquerda e ${a.y} do topo`);
271
+ }
272
+ drag(e, apply) {
273
+ if (e.button !== 0)
274
+ return;
275
+ e.preventDefault();
276
+ e.stopPropagation();
277
+ const start = this.box();
278
+ const sx = e.clientX;
279
+ const sy = e.clientY;
280
+ const target = e.currentTarget;
281
+ target.setPointerCapture?.(e.pointerId);
282
+ const move = (ev) => this.box.set(apply(ev.clientX - sx, ev.clientY - sy, start));
283
+ const up = () => {
284
+ target.removeEventListener('pointermove', move);
285
+ target.removeEventListener('pointerup', up);
286
+ };
287
+ target.addEventListener('pointermove', move);
288
+ target.addEventListener('pointerup', up);
289
+ }
290
+ startMove(e) {
291
+ e.currentTarget.focus();
292
+ this.drag(e, (dx, dy, s) => mxClampRect({ ...s, x: s.x + dx, y: s.y + dy }, this.bounds()));
293
+ }
294
+ startResize(e, h) {
295
+ this.drag(e, (dx, dy, s) => mxResizeRect(s, h, dx, dy, this.bounds(), this.aspectRatio(), this.minSize()));
296
+ }
297
+ /** Desenha o recorte num canvas na resolução original. */
298
+ toCanvas() {
299
+ const img = this.img()?.nativeElement;
300
+ const canvas = document.createElement('canvas');
301
+ if (!img || !this.ready())
302
+ return canvas;
303
+ const b = this.box();
304
+ const s = this.stageSize();
305
+ const k = 1 / this.scale();
306
+ canvas.width = Math.max(1, Math.round(b.width * k));
307
+ canvas.height = Math.max(1, Math.round(b.height * k));
308
+ const ctx = canvas.getContext('2d');
309
+ if (!ctx)
310
+ return canvas;
311
+ if (this.round()) {
312
+ ctx.beginPath();
313
+ ctx.ellipse(canvas.width / 2, canvas.height / 2, canvas.width / 2, canvas.height / 2, 0, 0, Math.PI * 2);
314
+ ctx.clip();
315
+ }
316
+ ctx.scale(k, k);
317
+ ctx.translate(-b.x, -b.y);
318
+ ctx.translate(s.w / 2, s.h / 2);
319
+ ctx.rotate((this.rotation() * Math.PI) / 180);
320
+ ctx.scale(this.scale(), this.scale());
321
+ ctx.drawImage(img, -this.natural().w / 2, -this.natural().h / 2);
322
+ return canvas;
323
+ }
324
+ /** Recorte como Blob (PNG por omissão; com `round` é sempre PNG). */
325
+ toBlob(type = 'image/png', quality = 0.92) {
326
+ const canvas = this.toCanvas();
327
+ return new Promise((resolve) => canvas.toBlob(resolve, this.round() ? 'image/png' : type, quality));
328
+ }
329
+ toDataURL(type = 'image/png', quality = 0.92) {
330
+ return this.toCanvas().toDataURL(this.round() ? 'image/png' : type, quality);
331
+ }
332
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxImageCropper, deps: [], target: i0.ɵɵFactoryTarget.Component });
333
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: MxImageCropper, isStandalone: true, selector: "mx-image-cropper", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, round: { classPropertyName: "round", publicName: "round", isSignal: true, isRequired: false, transformFunction: null }, controls: { classPropertyName: "controls", publicName: "controls", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, minSize: { classPropertyName: "minSize", publicName: "minSize", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, rotation: { classPropertyName: "rotation", publicName: "rotation", isSignal: true, isRequired: false, transformFunction: null }, helpText: { classPropertyName: "helpText", publicName: "helpText", isSignal: true, isRequired: false, transformFunction: null }, zoomText: { classPropertyName: "zoomText", publicName: "zoomText", isSignal: true, isRequired: false, transformFunction: null }, rotateLeftText: { classPropertyName: "rotateLeftText", publicName: "rotateLeftText", isSignal: true, isRequired: false, transformFunction: null }, rotateRightText: { classPropertyName: "rotateRightText", publicName: "rotateRightText", isSignal: true, isRequired: false, transformFunction: null }, resetText: { classPropertyName: "resetText", publicName: "resetText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { zoom: "zoomChange", rotation: "rotationChange", areaChange: "areaChange" }, host: { properties: { "attr.data-round": "round() ? \"\" : null", "attr.aria-label": "null" }, classAttribute: "mx-image-cropper" }, viewQueries: [{ propertyName: "stage", first: true, predicate: ["stage"], descendants: true, isSignal: true }, { propertyName: "img", first: true, predicate: ["img"], descendants: true, isSignal: true }], exportAs: ["mxImageCropper"], ngImport: i0, template: `
334
+ <div class="mx-cropper-stage" #stage [style.height]="height()">
335
+ @if (url()) {
336
+ <img
337
+ #img
338
+ class="mx-cropper-image"
339
+ [src]="url()"
340
+ [alt]="alt()"
341
+ draggable="false"
342
+ [style.width.px]="natural().w"
343
+ [style.height.px]="natural().h"
344
+ [style.transform]="
345
+ 'translate(-50%, -50%) rotate(' + rotation() + 'deg) scale(' + scale() + ')'
346
+ "
347
+ (load)="onLoad()"
348
+ />
349
+ }
350
+ @if (ready()) {
351
+ <div
352
+ class="mx-cropper-box"
353
+ role="group"
354
+ tabindex="0"
355
+ aria-roledescription="área de recorte"
356
+ [attr.aria-label]="boxLabel()"
357
+ [attr.aria-describedby]="helpId"
358
+ [style.left.px]="box().x"
359
+ [style.top.px]="box().y"
360
+ [style.width.px]="box().width"
361
+ [style.height.px]="box().height"
362
+ (pointerdown)="startMove($event)"
363
+ (keydown)="onKeydown($event)"
364
+ >
365
+ <span class="mx-cropper-grid" aria-hidden="true"></span>
366
+ @for (h of handles; track h) {
367
+ <span
368
+ class="mx-cropper-handle"
369
+ [attr.data-handle]="h"
370
+ aria-hidden="true"
371
+ (pointerdown)="startResize($event, h)"
372
+ ></span>
373
+ }
374
+ </div>
375
+ }
376
+ </div>
377
+ <p class="mx-visually-hidden" [id]="helpId">{{ helpText() }}</p>
378
+ @if (controls()) {
379
+ <div class="mx-cropper-controls">
380
+ <label class="mx-cropper-zoom">
381
+ <span>{{ zoomText() }}</span>
382
+ <input
383
+ type="range"
384
+ class="mx-slider"
385
+ min="1"
386
+ max="4"
387
+ step="0.05"
388
+ [value]="zoom()"
389
+ [disabled]="!ready()"
390
+ [attr.aria-valuetext]="zoomPct() + '%'"
391
+ (input)="setZoom($any($event.target).valueAsNumber)"
392
+ />
393
+ </label>
394
+ <button
395
+ type="button"
396
+ class="mx-cropper-button"
397
+ [disabled]="!ready()"
398
+ [attr.aria-label]="rotateLeftText()"
399
+ (click)="rotate(-90)"
400
+ >
401
+ <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
402
+ <path
403
+ d="M3.5 6.5a5 5 0 1 1 .8 5.2M3.5 2.5v4h4"
404
+ stroke="currentColor"
405
+ stroke-width="1.4"
406
+ stroke-linecap="round"
407
+ stroke-linejoin="round"
408
+ />
409
+ </svg>
410
+ </button>
411
+ <button
412
+ type="button"
413
+ class="mx-cropper-button"
414
+ [disabled]="!ready()"
415
+ [attr.aria-label]="rotateRightText()"
416
+ (click)="rotate(90)"
417
+ >
418
+ <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
419
+ <path
420
+ d="M12.5 6.5a5 5 0 1 0-.8 5.2M12.5 2.5v4h-4"
421
+ stroke="currentColor"
422
+ stroke-width="1.4"
423
+ stroke-linecap="round"
424
+ stroke-linejoin="round"
425
+ />
426
+ </svg>
427
+ </button>
428
+ <button
429
+ type="button"
430
+ class="mx-cropper-button mx-cropper-reset"
431
+ [disabled]="!ready()"
432
+ (click)="reset()"
433
+ >
434
+ {{ resetText() }}
435
+ </button>
436
+ </div>
437
+ }
438
+ <span class="mx-visually-hidden" aria-live="polite">{{ live() }}</span>
439
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
440
+ }
441
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxImageCropper, decorators: [{
442
+ type: Component,
443
+ args: [{
444
+ selector: 'mx-image-cropper',
445
+ exportAs: 'mxImageCropper',
446
+ changeDetection: ChangeDetectionStrategy.OnPush,
447
+ encapsulation: ViewEncapsulation.None,
448
+ host: {
449
+ class: 'mx-image-cropper',
450
+ '[attr.data-round]': 'round() ? "" : null',
451
+ '[attr.aria-label]': 'null',
452
+ },
453
+ template: `
454
+ <div class="mx-cropper-stage" #stage [style.height]="height()">
455
+ @if (url()) {
456
+ <img
457
+ #img
458
+ class="mx-cropper-image"
459
+ [src]="url()"
460
+ [alt]="alt()"
461
+ draggable="false"
462
+ [style.width.px]="natural().w"
463
+ [style.height.px]="natural().h"
464
+ [style.transform]="
465
+ 'translate(-50%, -50%) rotate(' + rotation() + 'deg) scale(' + scale() + ')'
466
+ "
467
+ (load)="onLoad()"
468
+ />
469
+ }
470
+ @if (ready()) {
471
+ <div
472
+ class="mx-cropper-box"
473
+ role="group"
474
+ tabindex="0"
475
+ aria-roledescription="área de recorte"
476
+ [attr.aria-label]="boxLabel()"
477
+ [attr.aria-describedby]="helpId"
478
+ [style.left.px]="box().x"
479
+ [style.top.px]="box().y"
480
+ [style.width.px]="box().width"
481
+ [style.height.px]="box().height"
482
+ (pointerdown)="startMove($event)"
483
+ (keydown)="onKeydown($event)"
484
+ >
485
+ <span class="mx-cropper-grid" aria-hidden="true"></span>
486
+ @for (h of handles; track h) {
487
+ <span
488
+ class="mx-cropper-handle"
489
+ [attr.data-handle]="h"
490
+ aria-hidden="true"
491
+ (pointerdown)="startResize($event, h)"
492
+ ></span>
493
+ }
494
+ </div>
495
+ }
496
+ </div>
497
+ <p class="mx-visually-hidden" [id]="helpId">{{ helpText() }}</p>
498
+ @if (controls()) {
499
+ <div class="mx-cropper-controls">
500
+ <label class="mx-cropper-zoom">
501
+ <span>{{ zoomText() }}</span>
502
+ <input
503
+ type="range"
504
+ class="mx-slider"
505
+ min="1"
506
+ max="4"
507
+ step="0.05"
508
+ [value]="zoom()"
509
+ [disabled]="!ready()"
510
+ [attr.aria-valuetext]="zoomPct() + '%'"
511
+ (input)="setZoom($any($event.target).valueAsNumber)"
512
+ />
513
+ </label>
514
+ <button
515
+ type="button"
516
+ class="mx-cropper-button"
517
+ [disabled]="!ready()"
518
+ [attr.aria-label]="rotateLeftText()"
519
+ (click)="rotate(-90)"
520
+ >
521
+ <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
522
+ <path
523
+ d="M3.5 6.5a5 5 0 1 1 .8 5.2M3.5 2.5v4h4"
524
+ stroke="currentColor"
525
+ stroke-width="1.4"
526
+ stroke-linecap="round"
527
+ stroke-linejoin="round"
528
+ />
529
+ </svg>
530
+ </button>
531
+ <button
532
+ type="button"
533
+ class="mx-cropper-button"
534
+ [disabled]="!ready()"
535
+ [attr.aria-label]="rotateRightText()"
536
+ (click)="rotate(90)"
537
+ >
538
+ <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
539
+ <path
540
+ d="M12.5 6.5a5 5 0 1 0-.8 5.2M12.5 2.5v4h-4"
541
+ stroke="currentColor"
542
+ stroke-width="1.4"
543
+ stroke-linecap="round"
544
+ stroke-linejoin="round"
545
+ />
546
+ </svg>
547
+ </button>
548
+ <button
549
+ type="button"
550
+ class="mx-cropper-button mx-cropper-reset"
551
+ [disabled]="!ready()"
552
+ (click)="reset()"
553
+ >
554
+ {{ resetText() }}
555
+ </button>
556
+ </div>
557
+ }
558
+ <span class="mx-visually-hidden" aria-live="polite">{{ live() }}</span>
559
+ `,
560
+ }]
561
+ }], ctorParameters: () => [], propDecorators: { src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], alt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: false }] }], aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], round: [{ type: i0.Input, args: [{ isSignal: true, alias: "round", required: false }] }], controls: [{ type: i0.Input, args: [{ isSignal: true, alias: "controls", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], minSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSize", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }, { type: i0.Output, args: ["zoomChange"] }], rotation: [{ type: i0.Input, args: [{ isSignal: true, alias: "rotation", required: false }] }, { type: i0.Output, args: ["rotationChange"] }], helpText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helpText", required: false }] }], zoomText: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoomText", required: false }] }], rotateLeftText: [{ type: i0.Input, args: [{ isSignal: true, alias: "rotateLeftText", required: false }] }], rotateRightText: [{ type: i0.Input, args: [{ isSignal: true, alias: "rotateRightText", required: false }] }], resetText: [{ type: i0.Input, args: [{ isSignal: true, alias: "resetText", required: false }] }], areaChange: [{ type: i0.Output, args: ["areaChange"] }], stage: [{ type: i0.ViewChild, args: ['stage', { isSignal: true }] }], img: [{ type: i0.ViewChild, args: ['img', { isSignal: true }] }] } });
562
+
563
+ /**
564
+ * Generated bundle index. Do not edit.
565
+ */
566
+
567
+ export { MxImageCropper, mxCenteredRect, mxClampRect, mxResizeRect };
568
+ //# sourceMappingURL=muxima-ui-core-image-cropper.mjs.map