@caribeng/ui 22.2.0 → 22.5.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.
- package/README.md +311 -231
- package/fesm2022/caribeng-ui-accordion.mjs +2 -2
- package/fesm2022/caribeng-ui-account-picker.mjs +533 -0
- package/fesm2022/caribeng-ui-aging.mjs +488 -0
- package/fesm2022/{caribeng-ui-caribe-alert.mjs → caribeng-ui-alert.mjs} +45 -45
- package/fesm2022/caribeng-ui-amount.mjs +249 -0
- package/fesm2022/caribeng-ui-anchor.mjs +2 -2
- package/fesm2022/{caribeng-ui-caribe-animation-lottie.mjs → caribeng-ui-animation-lottie.mjs} +41 -41
- package/fesm2022/caribeng-ui-audit-log.mjs +323 -0
- package/fesm2022/caribeng-ui-avatar.mjs +4 -4
- package/fesm2022/caribeng-ui-badge.mjs +8 -2
- package/fesm2022/caribeng-ui-breadcrumb.mjs +26 -9
- package/fesm2022/{caribeng-ui-caribe-button-group.mjs → caribeng-ui-button-group.mjs} +15 -15
- package/fesm2022/caribeng-ui-button.mjs +2 -2
- package/fesm2022/{caribeng-ui-caribe-card-content.mjs → caribeng-ui-card-content.mjs} +39 -39
- package/fesm2022/{caribeng-ui-caribe-card-move.mjs → caribeng-ui-card-move.mjs} +65 -65
- package/fesm2022/{caribeng-ui-caribe-card-primary.mjs → caribeng-ui-card-primary.mjs} +31 -31
- package/fesm2022/{caribeng-ui-caribe-card-select.mjs → caribeng-ui-card-select.mjs} +67 -67
- package/fesm2022/caribeng-ui-carousel.mjs +2 -2
- package/fesm2022/caribeng-ui-cascadeselect.mjs +45 -9
- package/fesm2022/caribeng-ui-chart.mjs +8 -5
- package/fesm2022/caribeng-ui-chat.mjs +1303 -0
- package/fesm2022/{caribeng-ui-caribe-coachmark.mjs → caribeng-ui-coachmark.mjs} +85 -85
- package/fesm2022/caribeng-ui-code-block.mjs +332 -0
- package/fesm2022/caribeng-ui-colorpicker.mjs +99 -5
- package/fesm2022/caribeng-ui-command-palette.mjs +476 -0
- package/fesm2022/caribeng-ui-comments.mjs +864 -0
- package/fesm2022/caribeng-ui-confirmdialog.mjs +78 -13
- package/fesm2022/caribeng-ui-confirmpopup.mjs +91 -16
- package/fesm2022/caribeng-ui-contextmenu.mjs +8 -4
- package/fesm2022/caribeng-ui-copy-button.mjs +230 -0
- package/fesm2022/caribeng-ui-datagrid.mjs +1635 -0
- package/fesm2022/caribeng-ui-dataview.mjs +6 -3
- package/fesm2022/caribeng-ui-datepicker.mjs +2 -2
- package/fesm2022/caribeng-ui-dock.mjs +52 -12
- package/fesm2022/caribeng-ui-document.mjs +495 -0
- package/fesm2022/{caribeng-ui-caribe-dots-indicator.mjs → caribeng-ui-dots-indicator.mjs} +41 -41
- package/fesm2022/caribeng-ui-drawer.mjs +9 -4
- package/fesm2022/caribeng-ui-dynamic-form.mjs +1581 -0
- package/fesm2022/caribeng-ui-editor.mjs +2324 -1311
- package/fesm2022/{caribeng-ui-caribe-expansion-panel.mjs → caribeng-ui-expansion-panel.mjs} +39 -39
- package/fesm2022/{caribeng-ui-caribe-fab-menu.mjs → caribeng-ui-fab-menu.mjs} +75 -75
- package/fesm2022/caribeng-ui-fieldset.mjs +2 -2
- package/fesm2022/caribeng-ui-file-card.mjs +134 -17
- package/fesm2022/{caribeng-ui-caribe-file-download.mjs → caribeng-ui-file-download.mjs} +63 -63
- package/fesm2022/caribeng-ui-financial-report.mjs +380 -0
- package/fesm2022/{caribeng-ui-caribe-float-menu.mjs → caribeng-ui-float-menu.mjs} +31 -31
- package/fesm2022/caribeng-ui-floatlabel.mjs +2 -2
- package/fesm2022/caribeng-ui-flow.mjs +1508 -0
- package/fesm2022/caribeng-ui-folder-card.mjs +122 -12
- package/fesm2022/{caribeng-ui-caribe-footer.mjs → caribeng-ui-footer.mjs} +19 -19
- package/fesm2022/caribeng-ui-gantt.mjs +1094 -0
- package/fesm2022/{caribeng-ui-caribe-header.mjs → caribeng-ui-header.mjs} +49 -49
- package/fesm2022/{caribeng-ui-caribe-icon-button.mjs → caribeng-ui-icon-button.mjs} +23 -23
- package/fesm2022/caribeng-ui-image-cropper.mjs +671 -0
- package/fesm2022/caribeng-ui-import-wizard.mjs +746 -0
- package/fesm2022/caribeng-ui-infinite-scroll.mjs +226 -0
- package/fesm2022/caribeng-ui-inlinemessage.mjs +2 -2
- package/fesm2022/caribeng-ui-input-number.mjs +22 -6
- package/fesm2022/{caribeng-ui-caribe-input-search.mjs → caribeng-ui-input-search.mjs} +65 -65
- package/fesm2022/{caribeng-ui-caribe-input-token.mjs → caribeng-ui-input-token.mjs} +59 -59
- package/fesm2022/caribeng-ui-inputmask.mjs +8 -2
- package/fesm2022/caribeng-ui-inputotp.mjs +2 -2
- package/fesm2022/caribeng-ui-journal-entry.mjs +340 -0
- package/fesm2022/caribeng-ui-kanban.mjs +4612 -0
- package/fesm2022/caribeng-ui-kbd.mjs +122 -0
- package/fesm2022/caribeng-ui-kpi.mjs +189 -0
- package/fesm2022/{caribeng-ui-caribe-link.mjs → caribeng-ui-link.mjs} +37 -37
- package/fesm2022/caribeng-ui-masonry.mjs +93 -0
- package/fesm2022/caribeng-ui-media-player.mjs +821 -0
- package/fesm2022/caribeng-ui-megamenu.mjs +13 -9
- package/fesm2022/caribeng-ui-mentions.mjs +495 -0
- package/fesm2022/caribeng-ui-menu.mjs +8 -4
- package/fesm2022/caribeng-ui-menubar.mjs +9 -5
- package/fesm2022/caribeng-ui-message.mjs +6 -3
- package/fesm2022/caribeng-ui-notification-center.mjs +636 -0
- package/fesm2022/{caribeng-ui-caribe-off-canvas.mjs → caribeng-ui-off-canvas.mjs} +55 -55
- package/fesm2022/{caribeng-ui-caribe-onboarding.mjs → caribeng-ui-onboarding.mjs} +105 -105
- package/fesm2022/caribeng-ui-orderlist.mjs +2 -2
- package/fesm2022/caribeng-ui-organizationchart.mjs +6 -10
- package/fesm2022/caribeng-ui-panel.mjs +6 -6
- package/fesm2022/caribeng-ui-panelmenu.mjs +9 -6
- package/fesm2022/caribeng-ui-pdfviewer.mjs +1755 -25
- package/fesm2022/caribeng-ui-period-picker.mjs +637 -0
- package/fesm2022/caribeng-ui-phone-input.mjs +688 -0
- package/fesm2022/caribeng-ui-picklist.mjs +2 -2
- package/fesm2022/caribeng-ui-pivot.mjs +419 -0
- package/fesm2022/caribeng-ui-pricing.mjs +263 -69
- package/fesm2022/caribeng-ui-qrcode.mjs +564 -0
- package/fesm2022/caribeng-ui-rating.mjs +14 -17
- package/fesm2022/caribeng-ui-reconciliation.mjs +386 -0
- package/fesm2022/caribeng-ui-ripple.mjs +2 -0
- package/fesm2022/caribeng-ui-scheduler.mjs +1027 -0
- package/fesm2022/caribeng-ui-scrolltop.mjs +3 -1
- package/fesm2022/caribeng-ui-select.mjs +2 -2
- package/fesm2022/{caribeng-ui-caribe-shortcut.mjs → caribeng-ui-shortcut.mjs} +29 -29
- package/fesm2022/caribeng-ui-sidebar.mjs +573 -0
- package/fesm2022/caribeng-ui-signature-pad.mjs +654 -0
- package/fesm2022/{caribeng-ui-caribe-status.mjs → caribeng-ui-status.mjs} +11 -11
- package/fesm2022/{caribeng-ui-caribe-step-indicator.mjs → caribeng-ui-step-indicator.mjs} +53 -53
- package/fesm2022/caribeng-ui-stepper.mjs +12 -5
- package/fesm2022/caribeng-ui-steps.mjs +2 -2
- package/fesm2022/caribeng-ui-table.mjs +333 -19
- package/fesm2022/caribeng-ui-tabmenu.mjs +10 -6
- package/fesm2022/caribeng-ui-tabs.mjs +2 -2
- package/fesm2022/{caribeng-ui-caribe-tag-button.mjs → caribeng-ui-tag-button.mjs} +25 -25
- package/fesm2022/caribeng-ui-tag.mjs +2 -2
- package/fesm2022/caribeng-ui-tieredmenu.mjs +111 -21
- package/fesm2022/{caribeng-ui-caribe-tracker.mjs → caribeng-ui-tracker.mjs} +29 -29
- package/fesm2022/{caribeng-ui-caribe-transaction-status.mjs → caribeng-ui-transaction-status.mjs} +11 -11
- package/fesm2022/caribeng-ui-treeselect.mjs +44 -9
- package/fesm2022/caribeng-ui-treetable.mjs +236 -17
- package/fesm2022/{caribeng-ui-caribe-voucher.mjs → caribeng-ui-voucher.mjs} +45 -45
- package/fesm2022/caribeng-ui-xlsx.mjs +486 -0
- package/fesm2022/caribeng-ui.mjs +1568 -1
- package/package.json +258 -178
- package/schematics/collection.json +5 -0
- package/schematics/migrate-from-primeng/core-icons.json +1 -0
- package/schematics/migrate-from-primeng/icons.js +269 -0
- package/schematics/migrate-from-primeng/index.js +290 -0
- package/schematics/migrate-from-primeng/mappings/button.js +93 -0
- package/schematics/migrate-from-primeng/mappings/card.js +33 -0
- package/schematics/migrate-from-primeng/mappings/checkbox.js +44 -0
- package/schematics/migrate-from-primeng/mappings/confirm.js +221 -0
- package/schematics/migrate-from-primeng/mappings/datepicker.js +121 -0
- package/schematics/migrate-from-primeng/mappings/dialog.js +84 -0
- package/schematics/migrate-from-primeng/mappings/index.js +88 -0
- package/schematics/migrate-from-primeng/mappings/inputnumber.js +62 -0
- package/schematics/migrate-from-primeng/mappings/inputtext.js +60 -0
- package/schematics/migrate-from-primeng/mappings/menu.js +468 -0
- package/schematics/migrate-from-primeng/mappings/multiselect.js +49 -0
- package/schematics/migrate-from-primeng/mappings/radiobutton.js +22 -0
- package/schematics/migrate-from-primeng/mappings/rules.js +206 -0
- package/schematics/migrate-from-primeng/mappings/select.js +97 -0
- package/schematics/migrate-from-primeng/mappings/service-calls.js +100 -0
- package/schematics/migrate-from-primeng/mappings/table.js +613 -0
- package/schematics/migrate-from-primeng/mappings/tabs.js +225 -0
- package/schematics/migrate-from-primeng/mappings/tag.js +29 -0
- package/schematics/migrate-from-primeng/mappings/textarea.js +44 -0
- package/schematics/migrate-from-primeng/mappings/toast.js +173 -0
- package/schematics/migrate-from-primeng/mappings/toggleswitch.js +37 -0
- package/schematics/migrate-from-primeng/mappings/tooltip.js +44 -0
- package/schematics/migrate-from-primeng/mappings/types.js +10 -0
- package/schematics/migrate-from-primeng/report.js +79 -0
- package/schematics/migrate-from-primeng/schema.js +2 -0
- package/schematics/migrate-from-primeng/schema.json +34 -0
- package/schematics/migrate-from-primeng/template.js +387 -0
- package/schematics/migrate-from-primeng/todo.js +5 -0
- package/schematics/migrate-from-primeng/typescript.js +442 -0
- package/schematics/migrate-from-primeng/workspace.js +117 -0
- package/schematics/ng-add/index.js +16 -1
- package/types/caribeng-ui-account-picker.d.ts +142 -0
- package/types/caribeng-ui-aging.d.ts +123 -0
- package/types/{caribeng-ui-caribe-alert.d.ts → caribeng-ui-alert.d.ts} +2 -2
- package/types/caribeng-ui-amount.d.ts +82 -0
- package/types/{caribeng-ui-caribe-animation-lottie.d.ts → caribeng-ui-animation-lottie.d.ts} +2 -2
- package/types/caribeng-ui-audit-log.d.ts +86 -0
- package/types/caribeng-ui-badge.d.ts +3 -1
- package/types/caribeng-ui-breadcrumb.d.ts +9 -1
- package/types/{caribeng-ui-caribe-button-group.d.ts → caribeng-ui-button-group.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-card-content.d.ts → caribeng-ui-card-content.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-card-move.d.ts → caribeng-ui-card-move.d.ts} +1 -1
- package/types/{caribeng-ui-caribe-card-primary.d.ts → caribeng-ui-card-primary.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-card-select.d.ts → caribeng-ui-card-select.d.ts} +2 -2
- package/types/caribeng-ui-cascadeselect.d.ts +6 -0
- package/types/caribeng-ui-chart.d.ts +3 -1
- package/types/caribeng-ui-chat.d.ts +299 -0
- package/types/{caribeng-ui-caribe-coachmark.d.ts → caribeng-ui-coachmark.d.ts} +2 -2
- package/types/caribeng-ui-code-block.d.ts +65 -0
- package/types/caribeng-ui-colorpicker.d.ts +5 -1
- package/types/caribeng-ui-command-palette.d.ts +133 -0
- package/types/caribeng-ui-comments.d.ts +214 -0
- package/types/caribeng-ui-confirmdialog.d.ts +34 -2
- package/types/caribeng-ui-confirmpopup.d.ts +39 -2
- package/types/caribeng-ui-contextmenu.d.ts +7 -0
- package/types/caribeng-ui-copy-button.d.ts +90 -0
- package/types/caribeng-ui-datagrid.d.ts +269 -0
- package/types/caribeng-ui-dataview.d.ts +2 -0
- package/types/caribeng-ui-dock.d.ts +13 -1
- package/types/caribeng-ui-document.d.ts +138 -0
- package/types/{caribeng-ui-caribe-dots-indicator.d.ts → caribeng-ui-dots-indicator.d.ts} +2 -2
- package/types/caribeng-ui-drawer.d.ts +3 -1
- package/types/caribeng-ui-dynamic-form.d.ts +409 -0
- package/types/caribeng-ui-editor.d.ts +110 -12
- package/types/{caribeng-ui-caribe-expansion-panel.d.ts → caribeng-ui-expansion-panel.d.ts} +1 -1
- package/types/{caribeng-ui-caribe-fab-menu.d.ts → caribeng-ui-fab-menu.d.ts} +2 -2
- package/types/caribeng-ui-file-card.d.ts +27 -2
- package/types/{caribeng-ui-caribe-file-download.d.ts → caribeng-ui-file-download.d.ts} +1 -1
- package/types/caribeng-ui-financial-report.d.ts +102 -0
- package/types/{caribeng-ui-caribe-float-menu.d.ts → caribeng-ui-float-menu.d.ts} +2 -2
- package/types/caribeng-ui-flow.d.ts +228 -0
- package/types/caribeng-ui-folder-card.d.ts +27 -5
- package/types/{caribeng-ui-caribe-footer.d.ts → caribeng-ui-footer.d.ts} +2 -2
- package/types/caribeng-ui-gantt.d.ts +244 -0
- package/types/{caribeng-ui-caribe-header.d.ts → caribeng-ui-header.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-icon-button.d.ts → caribeng-ui-icon-button.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-illustration.d.ts → caribeng-ui-illustration.d.ts} +1 -1
- package/types/caribeng-ui-image-cropper.d.ts +169 -0
- package/types/caribeng-ui-import-wizard.d.ts +149 -0
- package/types/caribeng-ui-infinite-scroll.d.ts +75 -0
- package/types/caribeng-ui-input-number.d.ts +1 -1
- package/types/{caribeng-ui-caribe-input-search.d.ts → caribeng-ui-input-search.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-input-token.d.ts → caribeng-ui-input-token.d.ts} +2 -2
- package/types/caribeng-ui-inputmask.d.ts +3 -1
- package/types/caribeng-ui-journal-entry.d.ts +110 -0
- package/types/caribeng-ui-kanban.d.ts +783 -0
- package/types/caribeng-ui-kbd.d.ts +36 -0
- package/types/caribeng-ui-kpi.d.ts +64 -0
- package/types/{caribeng-ui-caribe-link.d.ts → caribeng-ui-link.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-logo.d.ts → caribeng-ui-logo.d.ts} +1 -1
- package/types/caribeng-ui-masonry.d.ts +32 -0
- package/types/caribeng-ui-media-player.d.ts +140 -0
- package/types/caribeng-ui-megamenu.d.ts +11 -0
- package/types/caribeng-ui-mentions.d.ts +127 -0
- package/types/caribeng-ui-menu.d.ts +7 -0
- package/types/caribeng-ui-menubar.d.ts +7 -0
- package/types/caribeng-ui-message.d.ts +2 -0
- package/types/caribeng-ui-notification-center.d.ts +153 -0
- package/types/{caribeng-ui-caribe-off-canvas.d.ts → caribeng-ui-off-canvas.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-onboarding.d.ts → caribeng-ui-onboarding.d.ts} +2 -2
- package/types/caribeng-ui-panelmenu.d.ts +7 -0
- package/types/caribeng-ui-pdfviewer.d.ts +245 -5
- package/types/caribeng-ui-period-picker.d.ts +148 -0
- package/types/caribeng-ui-phone-input.d.ts +168 -0
- package/types/caribeng-ui-pivot.d.ts +116 -0
- package/types/caribeng-ui-pricing.d.ts +41 -4
- package/types/caribeng-ui-qrcode.d.ts +88 -0
- package/types/caribeng-ui-reconciliation.d.ts +109 -0
- package/types/caribeng-ui-scheduler.d.ts +188 -0
- package/types/{caribeng-ui-caribe-shortcut.d.ts → caribeng-ui-shortcut.d.ts} +2 -2
- package/types/caribeng-ui-sidebar.d.ts +167 -0
- package/types/caribeng-ui-signature-pad.d.ts +151 -0
- package/types/{caribeng-ui-caribe-status.d.ts → caribeng-ui-status.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-step-indicator.d.ts → caribeng-ui-step-indicator.d.ts} +2 -2
- package/types/caribeng-ui-stepper.d.ts +2 -0
- package/types/caribeng-ui-table.d.ts +67 -3
- package/types/caribeng-ui-tabmenu.d.ts +7 -0
- package/types/{caribeng-ui-caribe-tag-button.d.ts → caribeng-ui-tag-button.d.ts} +2 -2
- package/types/caribeng-ui-tieredmenu.d.ts +17 -1
- package/types/{caribeng-ui-caribe-tracker.d.ts → caribeng-ui-tracker.d.ts} +2 -2
- package/types/{caribeng-ui-caribe-transaction-status.d.ts → caribeng-ui-transaction-status.d.ts} +2 -2
- package/types/caribeng-ui-treeselect.d.ts +6 -0
- package/types/caribeng-ui-treetable.d.ts +67 -2
- package/types/{caribeng-ui-caribe-voucher.d.ts → caribeng-ui-voucher.d.ts} +2 -2
- package/types/caribeng-ui-xlsx.d.ts +79 -0
- package/types/caribeng-ui.d.ts +985 -2
- package/fesm2022/caribeng-ui-caribe-calendar.mjs +0 -92
- package/fesm2022/caribeng-ui-caribe-circle-loading.mjs +0 -130
- package/fesm2022/caribeng-ui-caribe-data-table.mjs +0 -94
- package/fesm2022/caribeng-ui-caribe-file-select.mjs +0 -88
- package/fesm2022/caribeng-ui-caribe-inline-alert.mjs +0 -100
- package/fesm2022/caribeng-ui-caribe-input-date.mjs +0 -138
- package/fesm2022/caribeng-ui-caribe-input-select.mjs +0 -154
- package/fesm2022/caribeng-ui-caribe-loader.mjs +0 -48
- package/fesm2022/caribeng-ui-caribe-modal.mjs +0 -119
- package/fesm2022/caribeng-ui-caribe-pictogram.mjs +0 -52
- package/fesm2022/caribeng-ui-caribe-pop-over.mjs +0 -104
- package/fesm2022/caribeng-ui-caribe-preloader.mjs +0 -57
- package/fesm2022/caribeng-ui-caribe-progress-bar.mjs +0 -84
- package/fesm2022/caribeng-ui-caribe-stroke.mjs +0 -69
- package/fesm2022/caribeng-ui-caribe-switch.mjs +0 -182
- package/styles/caribeng.css +0 -380
- package/styles/scss/_mixins.scss +0 -46
- package/styles/scss/_overlay.scss +0 -15
- package/styles/scss/_tokens.scss +0 -267
- package/styles/scss/_utilities.scss +0 -73
- package/styles/scss/_variables.scss +0 -33
- package/styles/scss/index.scss +0 -16
- package/types/caribeng-ui-caribe-calendar.d.ts +0 -27
- package/types/caribeng-ui-caribe-circle-loading.d.ts +0 -28
- package/types/caribeng-ui-caribe-data-table.d.ts +0 -26
- package/types/caribeng-ui-caribe-file-select.d.ts +0 -31
- package/types/caribeng-ui-caribe-inline-alert.d.ts +0 -28
- package/types/caribeng-ui-caribe-input-date.d.ts +0 -40
- package/types/caribeng-ui-caribe-input-select.d.ts +0 -47
- package/types/caribeng-ui-caribe-loader.d.ts +0 -22
- package/types/caribeng-ui-caribe-modal.d.ts +0 -21
- package/types/caribeng-ui-caribe-pictogram.d.ts +0 -15
- package/types/caribeng-ui-caribe-pop-over.d.ts +0 -17
- package/types/caribeng-ui-caribe-preloader.d.ts +0 -24
- package/types/caribeng-ui-caribe-progress-bar.d.ts +0 -28
- package/types/caribeng-ui-caribe-stroke.d.ts +0 -17
- package/types/caribeng-ui-caribe-switch.d.ts +0 -55
- /package/fesm2022/{caribeng-ui-caribe-illustration.mjs → caribeng-ui-illustration.mjs} +0 -0
- /package/fesm2022/{caribeng-ui-caribe-logo.mjs → caribeng-ui-logo.mjs} +0 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, Injector, DestroyRef, input, model, booleanAttribute, output, signal, computed, viewChild, effect, untracked, afterNextRender, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { DOCUMENT } from '@angular/common';
|
|
4
|
+
import { injectCbTranslation, cbFormat } from '@caribeng/ui';
|
|
5
|
+
import { CbIconRegistry, CbIcon } from '@caribeng/ui/icon';
|
|
6
|
+
|
|
7
|
+
/** Tamaño mínimo del recuadro (fracción del lado). */
|
|
8
|
+
const MIN = 0.05;
|
|
9
|
+
const clamp = (n, min, max) => Math.min(max, Math.max(min, n));
|
|
10
|
+
/** Mantiene el recuadro dentro de la imagen y con un tamaño mínimo. */
|
|
11
|
+
function clampRect(rect) {
|
|
12
|
+
const w = clamp(rect.w, MIN, 1);
|
|
13
|
+
const h = clamp(rect.h, MIN, 1);
|
|
14
|
+
return { x: clamp(rect.x, 0, 1 - w), y: clamp(rect.y, 0, 1 - h), w, h };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Ajusta el recuadro a una proporción (ancho / alto en píxeles), lo más grande
|
|
18
|
+
* posible dentro del actual y centrado en él. `ratio` null = libre.
|
|
19
|
+
*/
|
|
20
|
+
function fitRatio(rect, ratio, imageWidth, imageHeight) {
|
|
21
|
+
if (!ratio || !imageWidth || !imageHeight)
|
|
22
|
+
return clampRect(rect);
|
|
23
|
+
// Proporción expresada en fracciones de la imagen.
|
|
24
|
+
const r = ratio * (imageHeight / imageWidth);
|
|
25
|
+
let w = rect.w;
|
|
26
|
+
let h = w / r;
|
|
27
|
+
if (h > rect.h) {
|
|
28
|
+
h = rect.h;
|
|
29
|
+
w = h * r;
|
|
30
|
+
}
|
|
31
|
+
if (w > 1) {
|
|
32
|
+
w = 1;
|
|
33
|
+
h = w / r;
|
|
34
|
+
}
|
|
35
|
+
if (h > 1) {
|
|
36
|
+
h = 1;
|
|
37
|
+
w = h * r;
|
|
38
|
+
}
|
|
39
|
+
const cx = rect.x + rect.w / 2;
|
|
40
|
+
const cy = rect.y + rect.h / 2;
|
|
41
|
+
return clampRect({ x: cx - w / 2, y: cy - h / 2, w, h });
|
|
42
|
+
}
|
|
43
|
+
/** Recuadro inicial: el mayor posible con la proporción, centrado. */
|
|
44
|
+
function initialRect(ratio, imageWidth, imageHeight) {
|
|
45
|
+
return fitRatio({ x: 0, y: 0, w: 1, h: 1 }, ratio, imageWidth, imageHeight);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Redimensiona arrastrando una esquina (`nw`, `ne`, `sw`, `se`) un desplazamiento
|
|
49
|
+
* (dx, dy) en fracciones; con proporción, el alto sigue al ancho.
|
|
50
|
+
*/
|
|
51
|
+
function resizeRect(rect, corner, dx, dy, ratio, imageWidth, imageHeight) {
|
|
52
|
+
let { x, y, w, h } = rect;
|
|
53
|
+
const right = x + w;
|
|
54
|
+
const bottom = y + h;
|
|
55
|
+
if (corner.includes('w'))
|
|
56
|
+
x = clamp(x + dx, 0, right - MIN);
|
|
57
|
+
const newRight = corner.includes('e') ? clamp(right + dx, x + MIN, 1) : right;
|
|
58
|
+
if (corner.includes('n'))
|
|
59
|
+
y = clamp(y + dy, 0, bottom - MIN);
|
|
60
|
+
const newBottom = corner.includes('s') ? clamp(bottom + dy, y + MIN, 1) : bottom;
|
|
61
|
+
w = newRight - x;
|
|
62
|
+
h = newBottom - y;
|
|
63
|
+
if (ratio && imageWidth && imageHeight) {
|
|
64
|
+
const r = ratio * (imageHeight / imageWidth);
|
|
65
|
+
h = w / r;
|
|
66
|
+
if (corner.includes('n'))
|
|
67
|
+
y = bottom - h;
|
|
68
|
+
if (y < 0 || y + h > 1) {
|
|
69
|
+
// No cabe: se limita por el alto.
|
|
70
|
+
h = corner.includes('n') ? bottom : 1 - y;
|
|
71
|
+
w = h * r;
|
|
72
|
+
if (corner.includes('w'))
|
|
73
|
+
x = right - w;
|
|
74
|
+
if (corner.includes('n'))
|
|
75
|
+
y = bottom - h;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return clampRect({ x, y, w, h });
|
|
79
|
+
}
|
|
80
|
+
/** Tamaño en píxeles del recorte, reducido para no pasar de `maxWidth` × `maxHeight`. */
|
|
81
|
+
function outputSize(rect, imageWidth, imageHeight, output = {}) {
|
|
82
|
+
const w = Math.max(1, Math.round(rect.w * imageWidth));
|
|
83
|
+
const h = Math.max(1, Math.round(rect.h * imageHeight));
|
|
84
|
+
const scale = Math.min(1, (output.maxWidth ?? Infinity) / w, (output.maxHeight ?? Infinity) / h);
|
|
85
|
+
return { w: Math.max(1, Math.round(w * scale)), h: Math.max(1, Math.round(h * scale)) };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Estilo de una vista previa del recorte hecha con CSS (sin canvas): la imagen como fondo,
|
|
89
|
+
* ampliada y desplazada para que solo se vea el recuadro.
|
|
90
|
+
*/
|
|
91
|
+
function previewStyle(src, rect) {
|
|
92
|
+
const pos = (start, size) => (size >= 1 ? '0%' : `${(start / (1 - size)) * 100}%`);
|
|
93
|
+
return {
|
|
94
|
+
'background-image': `url("${src.replace(/"/g, '%22')}")`,
|
|
95
|
+
'background-size': `${100 / rect.w}% ${100 / rect.h}%`,
|
|
96
|
+
'background-position': `${pos(rect.x, rect.w)} ${pos(rect.y, rect.h)}`,
|
|
97
|
+
'background-repeat': 'no-repeat',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function loadImage(src) {
|
|
101
|
+
const image = new Image();
|
|
102
|
+
if (!/^(data|blob):/i.test(src))
|
|
103
|
+
image.crossOrigin = 'anonymous';
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
image.onload = () => resolve(image);
|
|
106
|
+
image.onerror = () => reject(new Error('No se pudo cargar la imagen'));
|
|
107
|
+
image.src = src;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Recorta los píxeles de la imagen. Las imágenes de otro dominio necesitan que
|
|
112
|
+
* su servidor permita CORS; si no, el navegador bloquea la lectura y se rechaza.
|
|
113
|
+
*/
|
|
114
|
+
async function cropImage(src, rect, doc, output = {}) {
|
|
115
|
+
const image = await loadImage(src);
|
|
116
|
+
const nw = image.naturalWidth;
|
|
117
|
+
const nh = image.naturalHeight;
|
|
118
|
+
const sw = Math.max(1, Math.round(rect.w * nw));
|
|
119
|
+
const sh = Math.max(1, Math.round(rect.h * nh));
|
|
120
|
+
const size = outputSize(rect, nw, nh, output);
|
|
121
|
+
const canvas = doc.createElement('canvas');
|
|
122
|
+
canvas.width = size.w;
|
|
123
|
+
canvas.height = size.h;
|
|
124
|
+
const ctx = canvas.getContext('2d');
|
|
125
|
+
if (!ctx)
|
|
126
|
+
throw new Error('Canvas 2D no disponible');
|
|
127
|
+
if (output.circle) {
|
|
128
|
+
ctx.beginPath();
|
|
129
|
+
ctx.ellipse(size.w / 2, size.h / 2, size.w / 2, size.h / 2, 0, 0, Math.PI * 2);
|
|
130
|
+
ctx.clip();
|
|
131
|
+
}
|
|
132
|
+
ctx.imageSmoothingQuality = 'high';
|
|
133
|
+
ctx.drawImage(image, Math.round(rect.x * nw), Math.round(rect.y * nh), sw, sh, 0, 0, size.w, size.h);
|
|
134
|
+
// Un círculo necesita transparencia: siempre PNG.
|
|
135
|
+
const type = output.circle ? 'image/png' : (output.type ?? (/^data:image\/jpe?g|\.jpe?g(\?|$)/i.test(src) ? 'image/jpeg' : 'image/png'));
|
|
136
|
+
// toBlob lanza SecurityError si el canvas quedó "contaminado" (imagen sin CORS).
|
|
137
|
+
return new Promise((resolve, reject) => canvas.toBlob((blob) => (blob ? resolve(blob) : reject(new Error('Sin datos'))), type, output.quality ?? 0.92));
|
|
138
|
+
}
|
|
139
|
+
/** Gira la imagen un cuarto de vuelta (`1` a la derecha, `-1` a la izquierda) y la devuelve como data URL PNG. */
|
|
140
|
+
async function rotateImage(src, turns, doc) {
|
|
141
|
+
const image = await loadImage(src);
|
|
142
|
+
const canvas = doc.createElement('canvas');
|
|
143
|
+
canvas.width = image.naturalHeight;
|
|
144
|
+
canvas.height = image.naturalWidth;
|
|
145
|
+
const ctx = canvas.getContext('2d');
|
|
146
|
+
if (!ctx)
|
|
147
|
+
throw new Error('Canvas 2D no disponible');
|
|
148
|
+
ctx.translate(canvas.width / 2, canvas.height / 2);
|
|
149
|
+
ctx.rotate((turns * Math.PI) / 2);
|
|
150
|
+
ctx.drawImage(image, -image.naturalWidth / 2, -image.naturalHeight / 2);
|
|
151
|
+
return canvas.toDataURL('image/png');
|
|
152
|
+
}
|
|
153
|
+
/** Lee un archivo como data URL. */
|
|
154
|
+
function readFileAsDataUrl(file) {
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
const reader = new FileReader();
|
|
157
|
+
reader.onload = () => resolve(String(reader.result));
|
|
158
|
+
reader.onerror = () => reject(reader.error);
|
|
159
|
+
reader.readAsDataURL(file);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let nextUid = 0;
|
|
164
|
+
/**
|
|
165
|
+
* Recortador de imagen de CaribeNG: recuadro que se mueve y redimensiona con ratón, dedo o
|
|
166
|
+
* teclado, proporciones fijas o libre, forma circular para fotos de perfil, giro, vista previa
|
|
167
|
+
* y selección de archivo (botón o arrastrar). Devuelve el recorte como `Blob` y data URL.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```html
|
|
171
|
+
* <cb-image-cropper [src]="foto" shape="circle" preview (cropped)="guardar($event.blob)" />
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
class CbImageCropper {
|
|
175
|
+
constructor() {
|
|
176
|
+
this.t = injectCbTranslation();
|
|
177
|
+
this.document = inject(DOCUMENT);
|
|
178
|
+
this.injector = inject(Injector);
|
|
179
|
+
this.destroyRef = inject(DestroyRef);
|
|
180
|
+
this.helpId = `cb-cropper-${++nextUid}-help`;
|
|
181
|
+
/** Imagen a recortar (URL o data URL). Las de otro dominio necesitan CORS para poder recortarse. */
|
|
182
|
+
this.src = input('', /* @ts-ignore */
|
|
183
|
+
...(ngDevMode ? [{ debugName: "src" }] : /* istanbul ignore next */ []));
|
|
184
|
+
/** Recuadro en fracciones de la imagen (0–1). Se inicia al cargar la imagen si no se indica. */
|
|
185
|
+
this.rect = model(null, /* @ts-ignore */
|
|
186
|
+
...(ngDevMode ? [{ debugName: "rect" }] : /* istanbul ignore next */ []));
|
|
187
|
+
/** Proporción ancho / alto (`null` = libre). Con `shape="circle"` siempre es 1. */
|
|
188
|
+
this.ratio = model(null, /* @ts-ignore */
|
|
189
|
+
...(ngDevMode ? [{ debugName: "ratio" }] : /* istanbul ignore next */ []));
|
|
190
|
+
/** Proporciones de la barra (vacío para ocultarla). */
|
|
191
|
+
this.ratios = input([
|
|
192
|
+
{ label: 'free', value: null },
|
|
193
|
+
{ label: '1:1', value: 1 },
|
|
194
|
+
{ label: '4:3', value: 4 / 3 },
|
|
195
|
+
{ label: '16:9', value: 16 / 9 },
|
|
196
|
+
], /* @ts-ignore */
|
|
197
|
+
...(ngDevMode ? [{ debugName: "ratios" }] : /* istanbul ignore next */ []));
|
|
198
|
+
/** Recorte rectangular o circular (PNG con fondo transparente, para fotos de perfil). */
|
|
199
|
+
this.shape = input('rect', /* @ts-ignore */
|
|
200
|
+
...(ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []));
|
|
201
|
+
/** Formato, calidad y tamaño máximo del recorte. */
|
|
202
|
+
this.output = input({}, /* @ts-ignore */
|
|
203
|
+
...(ngDevMode ? [{ debugName: "output" }] : /* istanbul ignore next */ []));
|
|
204
|
+
/** Botones de girar 90°. */
|
|
205
|
+
this.rotatable = input(true, { ...(ngDevMode ? { debugName: "rotatable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
206
|
+
/** Vista previa del recorte. */
|
|
207
|
+
this.preview = input(false, { ...(ngDevMode ? { debugName: "preview" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
208
|
+
/** Botón para elegir otra imagen (y arrastrar archivos al área vacía). */
|
|
209
|
+
this.chooser = input(true, { ...(ngDevMode ? { debugName: "chooser" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
210
|
+
/** Botón Recortar (sin él, recorta llamando a `crop()`). */
|
|
211
|
+
this.showApply = input(true, { ...(ngDevMode ? { debugName: "showApply" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
212
|
+
this.accept = input('image/*', /* @ts-ignore */
|
|
213
|
+
...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
|
|
214
|
+
/** Alto máximo de la imagen en pantalla. */
|
|
215
|
+
this.maxHeight = input('24rem', /* @ts-ignore */
|
|
216
|
+
...(ngDevMode ? [{ debugName: "maxHeight" }] : /* istanbul ignore next */ []));
|
|
217
|
+
this.emptyMessage = input(undefined, /* @ts-ignore */
|
|
218
|
+
...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
|
|
219
|
+
/** Recorte hecho con el botón Recortar o Enter. */
|
|
220
|
+
this.cropped = output();
|
|
221
|
+
/** Imagen elegida por el usuario (archivo). */
|
|
222
|
+
this.fileSelect = output();
|
|
223
|
+
/** No se pudo cargar o recortar la imagen. */
|
|
224
|
+
this.cropError = output();
|
|
225
|
+
this.corners = ['nw', 'ne', 'sw', 'se'];
|
|
226
|
+
/** Imagen en pantalla: `src`, el archivo elegido o la imagen girada. */
|
|
227
|
+
this.displaySrc = signal('', /* @ts-ignore */
|
|
228
|
+
...(ngDevMode ? [{ debugName: "displaySrc" }] : /* istanbul ignore next */ []));
|
|
229
|
+
this.natural = signal(null, /* @ts-ignore */
|
|
230
|
+
...(ngDevMode ? [{ debugName: "natural" }] : /* istanbul ignore next */ []));
|
|
231
|
+
this.busy = signal(false, /* @ts-ignore */
|
|
232
|
+
...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
|
|
233
|
+
this.error = signal('', /* @ts-ignore */
|
|
234
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
235
|
+
this.dragOver = signal(false, /* @ts-ignore */
|
|
236
|
+
...(ngDevMode ? [{ debugName: "dragOver" }] : /* istanbul ignore next */ []));
|
|
237
|
+
this.announcement = signal('', /* @ts-ignore */
|
|
238
|
+
...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
239
|
+
this.effRatio = computed(() => (this.shape() === 'circle' ? 1 : this.ratio()), /* @ts-ignore */
|
|
240
|
+
...(ngDevMode ? [{ debugName: "effRatio" }] : /* istanbul ignore next */ []));
|
|
241
|
+
/** El recuadro vigente (`rect` es null hasta que carga la imagen). */
|
|
242
|
+
this.current = computed(() => this.rect() ?? { x: 0, y: 0, w: 1, h: 1 }, /* @ts-ignore */
|
|
243
|
+
...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
|
|
244
|
+
this.valueText = computed(() => {
|
|
245
|
+
const r = this.current();
|
|
246
|
+
const n = this.natural();
|
|
247
|
+
const pct = (v) => `${Math.round(v * 100)} %`;
|
|
248
|
+
const px = n ? outputSize(r, n.w, n.h, this.output()) : { w: 0, h: 0 };
|
|
249
|
+
return cbFormat(this.t().cropperValue, pct(r.w), pct(r.h), px.w, px.h);
|
|
250
|
+
}, /* @ts-ignore */
|
|
251
|
+
...(ngDevMode ? [{ debugName: "valueText" }] : /* istanbul ignore next */ []));
|
|
252
|
+
this.previewCss = computed(() => {
|
|
253
|
+
const r = this.current();
|
|
254
|
+
const n = this.natural();
|
|
255
|
+
const aspect = n ? (r.w * n.w) / (r.h * n.h) : 1;
|
|
256
|
+
return { ...previewStyle(this.displaySrc(), r), 'aspect-ratio': String(aspect) };
|
|
257
|
+
}, /* @ts-ignore */
|
|
258
|
+
...(ngDevMode ? [{ debugName: "previewCss" }] : /* istanbul ignore next */ []));
|
|
259
|
+
this.stage = viewChild('stage', /* @ts-ignore */
|
|
260
|
+
...(ngDevMode ? [{ debugName: "stage" }] : /* istanbul ignore next */ []));
|
|
261
|
+
this.box = viewChild('box', /* @ts-ignore */
|
|
262
|
+
...(ngDevMode ? [{ debugName: "box" }] : /* istanbul ignore next */ []));
|
|
263
|
+
this.fileInput = viewChild.required('file');
|
|
264
|
+
this.announceFlip = false;
|
|
265
|
+
const registry = inject(CbIconRegistry);
|
|
266
|
+
for (const [name, body] of Object.entries(ICONS)) {
|
|
267
|
+
if (!registry.has(name))
|
|
268
|
+
registry.register({ [name]: body });
|
|
269
|
+
}
|
|
270
|
+
effect(() => {
|
|
271
|
+
const src = this.src();
|
|
272
|
+
untracked(() => this.load(src, false));
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
// --- API pública ---
|
|
276
|
+
/** Recorta y devuelve el resultado (sin emitir `cropped`). */
|
|
277
|
+
async crop() {
|
|
278
|
+
const n = this.natural();
|
|
279
|
+
const src = this.displaySrc();
|
|
280
|
+
if (!n || !src)
|
|
281
|
+
throw new Error('Sin imagen');
|
|
282
|
+
const rect = this.current();
|
|
283
|
+
const out = { ...this.output(), circle: this.shape() === 'circle' || this.output().circle };
|
|
284
|
+
const blob = await cropImage(src, rect, this.document, out);
|
|
285
|
+
const size = outputSize(rect, n.w, n.h, out);
|
|
286
|
+
return { blob, dataUrl: await readFileAsDataUrl(blob), rect, width: size.w, height: size.h };
|
|
287
|
+
}
|
|
288
|
+
/** Abre el selector de archivos. */
|
|
289
|
+
choose() {
|
|
290
|
+
this.fileInput().nativeElement.click();
|
|
291
|
+
}
|
|
292
|
+
/** Proporción fija (ancho / alto) o libre (`null`). */
|
|
293
|
+
setRatio(ratio) {
|
|
294
|
+
this.ratio.set(ratio);
|
|
295
|
+
const n = this.natural();
|
|
296
|
+
if (n)
|
|
297
|
+
this.rect.set(fitRatio(this.current(), this.effRatio(), n.w, n.h));
|
|
298
|
+
}
|
|
299
|
+
// --- Eventos ---
|
|
300
|
+
onLoad(event) {
|
|
301
|
+
const img = event.target;
|
|
302
|
+
const n = { w: img.naturalWidth || img.width || 1, h: img.naturalHeight || img.height || 1 };
|
|
303
|
+
this.natural.set(n);
|
|
304
|
+
this.error.set('');
|
|
305
|
+
const given = this.rect();
|
|
306
|
+
this.rect.set(given ? fitRatio(clampRect(given), this.effRatio(), n.w, n.h) : initialRect(this.effRatio(), n.w, n.h));
|
|
307
|
+
}
|
|
308
|
+
onImageError() {
|
|
309
|
+
this.natural.set(null);
|
|
310
|
+
this.error.set(this.t().cropperLoadError);
|
|
311
|
+
this.cropError.emit(new Error('No se pudo cargar la imagen'));
|
|
312
|
+
}
|
|
313
|
+
async apply() {
|
|
314
|
+
if (this.busy())
|
|
315
|
+
return;
|
|
316
|
+
this.busy.set(true);
|
|
317
|
+
this.error.set('');
|
|
318
|
+
try {
|
|
319
|
+
const result = await this.crop();
|
|
320
|
+
this.cropped.emit(result);
|
|
321
|
+
this.announce(this.t().cropperDone);
|
|
322
|
+
}
|
|
323
|
+
catch (e) {
|
|
324
|
+
this.error.set(this.t().cropError);
|
|
325
|
+
this.cropError.emit(e);
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
this.busy.set(false);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async rotate(turns) {
|
|
332
|
+
const src = this.displaySrc();
|
|
333
|
+
if (!src || this.busy())
|
|
334
|
+
return;
|
|
335
|
+
this.busy.set(true);
|
|
336
|
+
try {
|
|
337
|
+
const rotated = await rotateImage(src, turns, this.document);
|
|
338
|
+
this.rect.set(null);
|
|
339
|
+
this.load(rotated, true);
|
|
340
|
+
this.announce(turns === 1 ? this.t().cropperRotatedRight : this.t().cropperRotatedLeft);
|
|
341
|
+
}
|
|
342
|
+
catch (e) {
|
|
343
|
+
this.error.set(this.t().cropError);
|
|
344
|
+
this.cropError.emit(e);
|
|
345
|
+
}
|
|
346
|
+
finally {
|
|
347
|
+
this.busy.set(false);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async onFile(event) {
|
|
351
|
+
const input = event.target;
|
|
352
|
+
const file = input.files?.[0];
|
|
353
|
+
input.value = '';
|
|
354
|
+
if (file)
|
|
355
|
+
await this.useFile(file);
|
|
356
|
+
}
|
|
357
|
+
onDragOver(event) {
|
|
358
|
+
if (!this.chooser())
|
|
359
|
+
return;
|
|
360
|
+
event.preventDefault();
|
|
361
|
+
this.dragOver.set(true);
|
|
362
|
+
}
|
|
363
|
+
async onDrop(event) {
|
|
364
|
+
if (!this.chooser())
|
|
365
|
+
return;
|
|
366
|
+
event.preventDefault();
|
|
367
|
+
this.dragOver.set(false);
|
|
368
|
+
const file = event.dataTransfer?.files?.[0];
|
|
369
|
+
if (file)
|
|
370
|
+
await this.useFile(file);
|
|
371
|
+
}
|
|
372
|
+
/** Mueve (`corner` null) o redimensiona el recuadro arrastrando. */
|
|
373
|
+
startDrag(event, corner) {
|
|
374
|
+
const stage = this.stage()?.nativeElement;
|
|
375
|
+
const n = this.natural();
|
|
376
|
+
if (!stage || !n || (event.pointerType === 'mouse' && event.button !== 0))
|
|
377
|
+
return;
|
|
378
|
+
event.preventDefault();
|
|
379
|
+
event.stopPropagation();
|
|
380
|
+
this.box()?.nativeElement.focus({ preventScroll: true });
|
|
381
|
+
const bounds = stage.getBoundingClientRect();
|
|
382
|
+
const from = { x: event.clientX, y: event.clientY, rect: this.current() };
|
|
383
|
+
const move = (e) => {
|
|
384
|
+
const dx = (e.clientX - from.x) / (bounds.width || 1);
|
|
385
|
+
const dy = (e.clientY - from.y) / (bounds.height || 1);
|
|
386
|
+
this.rect.set(corner
|
|
387
|
+
? resizeRect(from.rect, corner, dx, dy, this.effRatio(), n.w, n.h)
|
|
388
|
+
: clampRect({ ...from.rect, x: from.rect.x + dx, y: from.rect.y + dy }));
|
|
389
|
+
};
|
|
390
|
+
const up = () => {
|
|
391
|
+
this.document.removeEventListener('pointermove', move);
|
|
392
|
+
this.document.removeEventListener('pointerup', up);
|
|
393
|
+
this.document.removeEventListener('pointercancel', up);
|
|
394
|
+
};
|
|
395
|
+
this.document.addEventListener('pointermove', move);
|
|
396
|
+
this.document.addEventListener('pointerup', up);
|
|
397
|
+
this.document.addEventListener('pointercancel', up);
|
|
398
|
+
}
|
|
399
|
+
/** Flechas: mover (2 %, 10 % con Ctrl); Mayús + flechas: tamaño desde la esquina inferior derecha; Enter: recortar. */
|
|
400
|
+
onKeydown(event) {
|
|
401
|
+
const n = this.natural();
|
|
402
|
+
if (!n)
|
|
403
|
+
return;
|
|
404
|
+
if (event.key === 'Enter' && this.showApply()) {
|
|
405
|
+
event.preventDefault();
|
|
406
|
+
void this.apply();
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const step = event.ctrlKey || event.metaKey ? 0.1 : 0.02;
|
|
410
|
+
const deltas = { ArrowLeft: [-step, 0], ArrowRight: [step, 0], ArrowUp: [0, -step], ArrowDown: [0, step] };
|
|
411
|
+
const d = deltas[event.key];
|
|
412
|
+
if (!d)
|
|
413
|
+
return;
|
|
414
|
+
event.preventDefault();
|
|
415
|
+
const r = this.current();
|
|
416
|
+
this.rect.set(event.shiftKey ? resizeRect(r, 'se', d[0], d[1], this.effRatio(), n.w, n.h) : clampRect({ ...r, x: r.x + d[0], y: r.y + d[1] }));
|
|
417
|
+
}
|
|
418
|
+
// --- Internos ---
|
|
419
|
+
load(src, keepFocus) {
|
|
420
|
+
this.natural.set(null);
|
|
421
|
+
this.error.set('');
|
|
422
|
+
this.displaySrc.set(src);
|
|
423
|
+
if (keepFocus)
|
|
424
|
+
this.focusWhenReady();
|
|
425
|
+
}
|
|
426
|
+
async useFile(file) {
|
|
427
|
+
if (!file.type.startsWith('image/')) {
|
|
428
|
+
this.error.set(this.t().cropperInvalidFile);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
this.fileSelect.emit(file);
|
|
432
|
+
this.rect.set(null);
|
|
433
|
+
this.load(await readFileAsDataUrl(file), true);
|
|
434
|
+
}
|
|
435
|
+
/** El recuadro aparece cuando carga la imagen: entonces recibe el foco. */
|
|
436
|
+
focusWhenReady() {
|
|
437
|
+
const stop = effect(() => {
|
|
438
|
+
if (this.natural()) {
|
|
439
|
+
untracked(() => afterNextRender(() => this.box()?.nativeElement.focus(), { injector: this.injector }));
|
|
440
|
+
stop.destroy();
|
|
441
|
+
}
|
|
442
|
+
}, { ...(ngDevMode ? { debugName: "stop" } : /* istanbul ignore next */ {}), injector: this.injector });
|
|
443
|
+
}
|
|
444
|
+
/** Un espacio duro alterno hace que el mismo texto cuente como cambio y se vuelva a anunciar. */
|
|
445
|
+
announce(text) {
|
|
446
|
+
this.announceFlip = !this.announceFlip;
|
|
447
|
+
this.announcement.set(this.announceFlip ? text : `${text} `);
|
|
448
|
+
}
|
|
449
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbImageCropper, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
450
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: CbImageCropper, isStandalone: true, selector: "cb-image-cropper", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, rect: { classPropertyName: "rect", publicName: "rect", isSignal: true, isRequired: false, transformFunction: null }, ratio: { classPropertyName: "ratio", publicName: "ratio", isSignal: true, isRequired: false, transformFunction: null }, ratios: { classPropertyName: "ratios", publicName: "ratios", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, output: { classPropertyName: "output", publicName: "output", isSignal: true, isRequired: false, transformFunction: null }, rotatable: { classPropertyName: "rotatable", publicName: "rotatable", isSignal: true, isRequired: false, transformFunction: null }, preview: { classPropertyName: "preview", publicName: "preview", isSignal: true, isRequired: false, transformFunction: null }, chooser: { classPropertyName: "chooser", publicName: "chooser", isSignal: true, isRequired: false, transformFunction: null }, showApply: { classPropertyName: "showApply", publicName: "showApply", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rect: "rectChange", ratio: "ratioChange", cropped: "cropped", fileSelect: "fileSelect", cropError: "cropError" }, host: { classAttribute: "cb-image-cropper" }, viewQueries: [{ propertyName: "stage", first: true, predicate: ["stage"], descendants: true, isSignal: true }, { propertyName: "box", first: true, predicate: ["box"], descendants: true, isSignal: true }, { propertyName: "fileInput", first: true, predicate: ["file"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
451
|
+
@if (displaySrc()) {
|
|
452
|
+
<div class="cb-cropper__main">
|
|
453
|
+
<div class="cb-cropper__stage" #stage>
|
|
454
|
+
<img
|
|
455
|
+
class="cb-cropper__img"
|
|
456
|
+
[src]="displaySrc()"
|
|
457
|
+
alt=""
|
|
458
|
+
draggable="false"
|
|
459
|
+
[style.max-height]="maxHeight()"
|
|
460
|
+
(load)="onLoad($event)"
|
|
461
|
+
(error)="onImageError()"
|
|
462
|
+
/>
|
|
463
|
+
@if (natural()) {
|
|
464
|
+
<div
|
|
465
|
+
#box
|
|
466
|
+
class="cb-cropper__box"
|
|
467
|
+
[class.cb-cropper__box--circle]="shape() === 'circle'"
|
|
468
|
+
role="slider"
|
|
469
|
+
tabindex="0"
|
|
470
|
+
[attr.aria-label]="t().cropArea"
|
|
471
|
+
[attr.aria-valuetext]="valueText()"
|
|
472
|
+
[attr.aria-describedby]="helpId"
|
|
473
|
+
[style.left.%]="current().x * 100"
|
|
474
|
+
[style.top.%]="current().y * 100"
|
|
475
|
+
[style.width.%]="current().w * 100"
|
|
476
|
+
[style.height.%]="current().h * 100"
|
|
477
|
+
(pointerdown)="startDrag($event, null)"
|
|
478
|
+
(keydown)="onKeydown($event)"
|
|
479
|
+
>
|
|
480
|
+
<span class="cb-cropper__grid" aria-hidden="true"></span>
|
|
481
|
+
@for (corner of corners; track corner) {
|
|
482
|
+
<span [class]="'cb-cropper__handle cb-cropper__handle--' + corner" aria-hidden="true" (pointerdown)="startDrag($event, corner)"></span>
|
|
483
|
+
}
|
|
484
|
+
</div>
|
|
485
|
+
}
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
@if (preview() && natural()) {
|
|
489
|
+
<div class="cb-cropper__preview-wrap">
|
|
490
|
+
<span class="cb-cropper__preview-label">{{ t().cropperPreview }}</span>
|
|
491
|
+
<div
|
|
492
|
+
class="cb-cropper__preview"
|
|
493
|
+
[class.cb-cropper__preview--circle]="shape() === 'circle'"
|
|
494
|
+
role="img"
|
|
495
|
+
[attr.aria-label]="t().cropperPreview"
|
|
496
|
+
[style]="previewCss()"
|
|
497
|
+
></div>
|
|
498
|
+
</div>
|
|
499
|
+
}
|
|
500
|
+
</div>
|
|
501
|
+
|
|
502
|
+
<p class="cb-cropper__help" [id]="helpId">{{ t().cropInstructions }}</p>
|
|
503
|
+
|
|
504
|
+
<div class="cb-cropper__bar">
|
|
505
|
+
@if (ratios().length && shape() !== 'circle') {
|
|
506
|
+
<div class="cb-cropper__ratios" role="group" [attr.aria-label]="t().cropperRatio">
|
|
507
|
+
@for (r of ratios(); track r.label) {
|
|
508
|
+
<button type="button" class="cb-cropper__btn" [attr.aria-pressed]="ratio() === r.value" (click)="setRatio(r.value)">
|
|
509
|
+
{{ r.value === null ? t().cropFree : r.label }}
|
|
510
|
+
</button>
|
|
511
|
+
}
|
|
512
|
+
</div>
|
|
513
|
+
}
|
|
514
|
+
@if (rotatable()) {
|
|
515
|
+
<button type="button" class="cb-cropper__icon-btn" [attr.aria-label]="t().cropperRotateLeft" [attr.title]="t().cropperRotateLeft" [disabled]="busy()" (click)="rotate(-1)">
|
|
516
|
+
<cb-icon name="rotate-ccw" [size]="16" aria-hidden="true" />
|
|
517
|
+
</button>
|
|
518
|
+
<button type="button" class="cb-cropper__icon-btn" [attr.aria-label]="t().cropperRotateRight" [attr.title]="t().cropperRotateRight" [disabled]="busy()" (click)="rotate(1)">
|
|
519
|
+
<cb-icon name="rotate-cw" [size]="16" aria-hidden="true" />
|
|
520
|
+
</button>
|
|
521
|
+
}
|
|
522
|
+
<span class="cb-cropper__spacer"></span>
|
|
523
|
+
@if (chooser()) {
|
|
524
|
+
<button type="button" class="cb-cropper__btn" (click)="choose()">{{ t().cropperChange }}</button>
|
|
525
|
+
}
|
|
526
|
+
@if (showApply()) {
|
|
527
|
+
<button type="button" class="cb-cropper__btn cb-cropper__btn--primary" [disabled]="busy() || !natural()" (click)="apply()">{{ t().cropImage }}</button>
|
|
528
|
+
}
|
|
529
|
+
</div>
|
|
530
|
+
} @else {
|
|
531
|
+
<div
|
|
532
|
+
class="cb-cropper__empty"
|
|
533
|
+
[class.cb-cropper__empty--over]="dragOver()"
|
|
534
|
+
(dragover)="onDragOver($event)"
|
|
535
|
+
(dragleave)="dragOver.set(false)"
|
|
536
|
+
(drop)="onDrop($event)"
|
|
537
|
+
>
|
|
538
|
+
<cb-icon name="cb-image" [size]="32" aria-hidden="true" />
|
|
539
|
+
<p>{{ emptyMessage() ?? t().cropperEmpty }}</p>
|
|
540
|
+
@if (chooser()) {
|
|
541
|
+
<button type="button" class="cb-cropper__btn cb-cropper__btn--primary" (click)="choose()">{{ t().cropperChoose }}</button>
|
|
542
|
+
}
|
|
543
|
+
</div>
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
@if (error(); as e) {
|
|
547
|
+
<p class="cb-cropper__error" role="alert">{{ e }}</p>
|
|
548
|
+
}
|
|
549
|
+
<span class="cb-cropper__sr" aria-live="polite">{{ announcement() }}</span>
|
|
550
|
+
<input #file type="file" class="cb-cropper__file" [accept]="accept()" tabindex="-1" aria-hidden="true" (change)="onFile($event)" />
|
|
551
|
+
`, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:.75rem;color:var(--cb-color-text);font-family:var(--cb-font-sans);font-size:var(--cb-text-sm, .875rem)}.cb-cropper__main{display:flex;flex-wrap:wrap;align-items:flex-start;gap:1rem}.cb-cropper__stage{position:relative;max-width:100%;overflow:hidden;border-radius:var(--cb-radius-md, 6px);background:repeating-conic-gradient(#e2e8f0 0% 25%,#f8fafc 0% 50%) 0 0 / 16px 16px;-webkit-user-select:none;user-select:none;touch-action:none}.cb-cropper__img{display:block;max-width:100%}.cb-cropper__box{position:absolute;box-sizing:border-box;border:2px solid #fff;box-shadow:0 0 0 9999px #0000008c;cursor:move;outline:none}.cb-cropper__box--circle{border-radius:50%}.cb-cropper__box:focus-visible{border-color:var(--cb-color-focus-ring);box-shadow:0 0 0 2px #fff,0 0 0 9999px #0000008c}.cb-cropper__grid{position:absolute;inset:0;background:linear-gradient(to right,transparent calc(33.33% - .5px),rgb(255 255 255 / .5) calc(33.33% - .5px),rgb(255 255 255 / .5) calc(33.33% + .5px),transparent calc(33.33% + .5px),transparent calc(66.66% - .5px),rgb(255 255 255 / .5) calc(66.66% - .5px),rgb(255 255 255 / .5) calc(66.66% + .5px),transparent calc(66.66% + .5px)),linear-gradient(to bottom,transparent calc(33.33% - .5px),rgb(255 255 255 / .5) calc(33.33% - .5px),rgb(255 255 255 / .5) calc(33.33% + .5px),transparent calc(33.33% + .5px),transparent calc(66.66% - .5px),rgb(255 255 255 / .5) calc(66.66% - .5px),rgb(255 255 255 / .5) calc(66.66% + .5px),transparent calc(66.66% + .5px));border-radius:inherit;pointer-events:none}.cb-cropper__handle{position:absolute;width:14px;height:14px;box-sizing:border-box;border:2px solid var(--cb-color-primary);border-radius:2px;background:#fff}@media(pointer:coarse){.cb-cropper__handle{width:22px;height:22px}}.cb-cropper__handle--nw{left:-8px;top:-8px;cursor:nwse-resize}.cb-cropper__handle--ne{right:-8px;top:-8px;cursor:nesw-resize}.cb-cropper__handle--sw{left:-8px;bottom:-8px;cursor:nesw-resize}.cb-cropper__handle--se{right:-8px;bottom:-8px;cursor:nwse-resize}.cb-cropper__preview-wrap{display:flex;flex-direction:column;gap:.375rem}.cb-cropper__preview-label{color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem)}.cb-cropper__preview{width:8rem;max-height:12rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md, 6px)}.cb-cropper__preview--circle{border-radius:50%}.cb-cropper__help{margin:0;color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem)}.cb-cropper__bar{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}.cb-cropper__ratios{display:inline-flex;flex-wrap:wrap;gap:.25rem}.cb-cropper__spacer{flex:1}.cb-cropper__btn{min-height:2rem;padding:0 .75rem;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-md, 6px);background:transparent;color:var(--cb-color-text);font:inherit;font-weight:var(--cb-font-weight-medium, 500);cursor:pointer}.cb-cropper__btn:hover:not(:disabled),.cb-cropper__icon-btn:hover:not(:disabled){background:var(--cb-color-surface-hover)}.cb-cropper__btn[aria-pressed=true]{border-color:var(--cb-color-primary);background:color-mix(in srgb,var(--cb-color-primary) 14%,transparent)}.cb-cropper__btn--primary{border-color:var(--cb-color-primary);background:var(--cb-color-primary);color:var(--cb-color-primary-contrast, #fff)}.cb-cropper__btn--primary:hover:not(:disabled){background:var(--cb-color-primary-700, var(--cb-color-primary))}.cb-cropper__btn:disabled,.cb-cropper__icon-btn:disabled{opacity:.5;cursor:not-allowed}.cb-cropper__icon-btn{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-md, 6px);background:transparent;color:var(--cb-color-text);cursor:pointer}.cb-cropper__btn:focus-visible,.cb-cropper__icon-btn:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-cropper__empty{display:flex;flex-direction:column;align-items:center;gap:.75rem;padding:2.5rem 1rem;border:2px dashed var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-lg, 8px);color:var(--cb-color-text-muted);text-align:center}.cb-cropper__empty p{margin:0}.cb-cropper__empty--over{border-color:var(--cb-color-primary);background:color-mix(in srgb,var(--cb-color-primary) 6%,transparent)}.cb-cropper__error{margin:0;color:var(--cb-color-danger)}.cb-cropper__file{display:none}.cb-cropper__sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "component", type: CbIcon, selector: "cb-icon", inputs: ["name", "size", "spin", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
552
|
+
}
|
|
553
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbImageCropper, decorators: [{
|
|
554
|
+
type: Component,
|
|
555
|
+
args: [{ selector: 'cb-image-cropper', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, imports: [CbIcon], host: { class: 'cb-image-cropper' }, template: `
|
|
556
|
+
@if (displaySrc()) {
|
|
557
|
+
<div class="cb-cropper__main">
|
|
558
|
+
<div class="cb-cropper__stage" #stage>
|
|
559
|
+
<img
|
|
560
|
+
class="cb-cropper__img"
|
|
561
|
+
[src]="displaySrc()"
|
|
562
|
+
alt=""
|
|
563
|
+
draggable="false"
|
|
564
|
+
[style.max-height]="maxHeight()"
|
|
565
|
+
(load)="onLoad($event)"
|
|
566
|
+
(error)="onImageError()"
|
|
567
|
+
/>
|
|
568
|
+
@if (natural()) {
|
|
569
|
+
<div
|
|
570
|
+
#box
|
|
571
|
+
class="cb-cropper__box"
|
|
572
|
+
[class.cb-cropper__box--circle]="shape() === 'circle'"
|
|
573
|
+
role="slider"
|
|
574
|
+
tabindex="0"
|
|
575
|
+
[attr.aria-label]="t().cropArea"
|
|
576
|
+
[attr.aria-valuetext]="valueText()"
|
|
577
|
+
[attr.aria-describedby]="helpId"
|
|
578
|
+
[style.left.%]="current().x * 100"
|
|
579
|
+
[style.top.%]="current().y * 100"
|
|
580
|
+
[style.width.%]="current().w * 100"
|
|
581
|
+
[style.height.%]="current().h * 100"
|
|
582
|
+
(pointerdown)="startDrag($event, null)"
|
|
583
|
+
(keydown)="onKeydown($event)"
|
|
584
|
+
>
|
|
585
|
+
<span class="cb-cropper__grid" aria-hidden="true"></span>
|
|
586
|
+
@for (corner of corners; track corner) {
|
|
587
|
+
<span [class]="'cb-cropper__handle cb-cropper__handle--' + corner" aria-hidden="true" (pointerdown)="startDrag($event, corner)"></span>
|
|
588
|
+
}
|
|
589
|
+
</div>
|
|
590
|
+
}
|
|
591
|
+
</div>
|
|
592
|
+
|
|
593
|
+
@if (preview() && natural()) {
|
|
594
|
+
<div class="cb-cropper__preview-wrap">
|
|
595
|
+
<span class="cb-cropper__preview-label">{{ t().cropperPreview }}</span>
|
|
596
|
+
<div
|
|
597
|
+
class="cb-cropper__preview"
|
|
598
|
+
[class.cb-cropper__preview--circle]="shape() === 'circle'"
|
|
599
|
+
role="img"
|
|
600
|
+
[attr.aria-label]="t().cropperPreview"
|
|
601
|
+
[style]="previewCss()"
|
|
602
|
+
></div>
|
|
603
|
+
</div>
|
|
604
|
+
}
|
|
605
|
+
</div>
|
|
606
|
+
|
|
607
|
+
<p class="cb-cropper__help" [id]="helpId">{{ t().cropInstructions }}</p>
|
|
608
|
+
|
|
609
|
+
<div class="cb-cropper__bar">
|
|
610
|
+
@if (ratios().length && shape() !== 'circle') {
|
|
611
|
+
<div class="cb-cropper__ratios" role="group" [attr.aria-label]="t().cropperRatio">
|
|
612
|
+
@for (r of ratios(); track r.label) {
|
|
613
|
+
<button type="button" class="cb-cropper__btn" [attr.aria-pressed]="ratio() === r.value" (click)="setRatio(r.value)">
|
|
614
|
+
{{ r.value === null ? t().cropFree : r.label }}
|
|
615
|
+
</button>
|
|
616
|
+
}
|
|
617
|
+
</div>
|
|
618
|
+
}
|
|
619
|
+
@if (rotatable()) {
|
|
620
|
+
<button type="button" class="cb-cropper__icon-btn" [attr.aria-label]="t().cropperRotateLeft" [attr.title]="t().cropperRotateLeft" [disabled]="busy()" (click)="rotate(-1)">
|
|
621
|
+
<cb-icon name="rotate-ccw" [size]="16" aria-hidden="true" />
|
|
622
|
+
</button>
|
|
623
|
+
<button type="button" class="cb-cropper__icon-btn" [attr.aria-label]="t().cropperRotateRight" [attr.title]="t().cropperRotateRight" [disabled]="busy()" (click)="rotate(1)">
|
|
624
|
+
<cb-icon name="rotate-cw" [size]="16" aria-hidden="true" />
|
|
625
|
+
</button>
|
|
626
|
+
}
|
|
627
|
+
<span class="cb-cropper__spacer"></span>
|
|
628
|
+
@if (chooser()) {
|
|
629
|
+
<button type="button" class="cb-cropper__btn" (click)="choose()">{{ t().cropperChange }}</button>
|
|
630
|
+
}
|
|
631
|
+
@if (showApply()) {
|
|
632
|
+
<button type="button" class="cb-cropper__btn cb-cropper__btn--primary" [disabled]="busy() || !natural()" (click)="apply()">{{ t().cropImage }}</button>
|
|
633
|
+
}
|
|
634
|
+
</div>
|
|
635
|
+
} @else {
|
|
636
|
+
<div
|
|
637
|
+
class="cb-cropper__empty"
|
|
638
|
+
[class.cb-cropper__empty--over]="dragOver()"
|
|
639
|
+
(dragover)="onDragOver($event)"
|
|
640
|
+
(dragleave)="dragOver.set(false)"
|
|
641
|
+
(drop)="onDrop($event)"
|
|
642
|
+
>
|
|
643
|
+
<cb-icon name="cb-image" [size]="32" aria-hidden="true" />
|
|
644
|
+
<p>{{ emptyMessage() ?? t().cropperEmpty }}</p>
|
|
645
|
+
@if (chooser()) {
|
|
646
|
+
<button type="button" class="cb-cropper__btn cb-cropper__btn--primary" (click)="choose()">{{ t().cropperChoose }}</button>
|
|
647
|
+
}
|
|
648
|
+
</div>
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
@if (error(); as e) {
|
|
652
|
+
<p class="cb-cropper__error" role="alert">{{ e }}</p>
|
|
653
|
+
}
|
|
654
|
+
<span class="cb-cropper__sr" aria-live="polite">{{ announcement() }}</span>
|
|
655
|
+
<input #file type="file" class="cb-cropper__file" [accept]="accept()" tabindex="-1" aria-hidden="true" (change)="onFile($event)" />
|
|
656
|
+
`, styles: [":host{display:flex;flex-direction:column;gap:.75rem;color:var(--cb-color-text);font-family:var(--cb-font-sans);font-size:var(--cb-text-sm, .875rem)}.cb-cropper__main{display:flex;flex-wrap:wrap;align-items:flex-start;gap:1rem}.cb-cropper__stage{position:relative;max-width:100%;overflow:hidden;border-radius:var(--cb-radius-md, 6px);background:repeating-conic-gradient(#e2e8f0 0% 25%,#f8fafc 0% 50%) 0 0 / 16px 16px;-webkit-user-select:none;user-select:none;touch-action:none}.cb-cropper__img{display:block;max-width:100%}.cb-cropper__box{position:absolute;box-sizing:border-box;border:2px solid #fff;box-shadow:0 0 0 9999px #0000008c;cursor:move;outline:none}.cb-cropper__box--circle{border-radius:50%}.cb-cropper__box:focus-visible{border-color:var(--cb-color-focus-ring);box-shadow:0 0 0 2px #fff,0 0 0 9999px #0000008c}.cb-cropper__grid{position:absolute;inset:0;background:linear-gradient(to right,transparent calc(33.33% - .5px),rgb(255 255 255 / .5) calc(33.33% - .5px),rgb(255 255 255 / .5) calc(33.33% + .5px),transparent calc(33.33% + .5px),transparent calc(66.66% - .5px),rgb(255 255 255 / .5) calc(66.66% - .5px),rgb(255 255 255 / .5) calc(66.66% + .5px),transparent calc(66.66% + .5px)),linear-gradient(to bottom,transparent calc(33.33% - .5px),rgb(255 255 255 / .5) calc(33.33% - .5px),rgb(255 255 255 / .5) calc(33.33% + .5px),transparent calc(33.33% + .5px),transparent calc(66.66% - .5px),rgb(255 255 255 / .5) calc(66.66% - .5px),rgb(255 255 255 / .5) calc(66.66% + .5px),transparent calc(66.66% + .5px));border-radius:inherit;pointer-events:none}.cb-cropper__handle{position:absolute;width:14px;height:14px;box-sizing:border-box;border:2px solid var(--cb-color-primary);border-radius:2px;background:#fff}@media(pointer:coarse){.cb-cropper__handle{width:22px;height:22px}}.cb-cropper__handle--nw{left:-8px;top:-8px;cursor:nwse-resize}.cb-cropper__handle--ne{right:-8px;top:-8px;cursor:nesw-resize}.cb-cropper__handle--sw{left:-8px;bottom:-8px;cursor:nesw-resize}.cb-cropper__handle--se{right:-8px;bottom:-8px;cursor:nwse-resize}.cb-cropper__preview-wrap{display:flex;flex-direction:column;gap:.375rem}.cb-cropper__preview-label{color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem)}.cb-cropper__preview{width:8rem;max-height:12rem;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-md, 6px)}.cb-cropper__preview--circle{border-radius:50%}.cb-cropper__help{margin:0;color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem)}.cb-cropper__bar{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}.cb-cropper__ratios{display:inline-flex;flex-wrap:wrap;gap:.25rem}.cb-cropper__spacer{flex:1}.cb-cropper__btn{min-height:2rem;padding:0 .75rem;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-md, 6px);background:transparent;color:var(--cb-color-text);font:inherit;font-weight:var(--cb-font-weight-medium, 500);cursor:pointer}.cb-cropper__btn:hover:not(:disabled),.cb-cropper__icon-btn:hover:not(:disabled){background:var(--cb-color-surface-hover)}.cb-cropper__btn[aria-pressed=true]{border-color:var(--cb-color-primary);background:color-mix(in srgb,var(--cb-color-primary) 14%,transparent)}.cb-cropper__btn--primary{border-color:var(--cb-color-primary);background:var(--cb-color-primary);color:var(--cb-color-primary-contrast, #fff)}.cb-cropper__btn--primary:hover:not(:disabled){background:var(--cb-color-primary-700, var(--cb-color-primary))}.cb-cropper__btn:disabled,.cb-cropper__icon-btn:disabled{opacity:.5;cursor:not-allowed}.cb-cropper__icon-btn{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-md, 6px);background:transparent;color:var(--cb-color-text);cursor:pointer}.cb-cropper__btn:focus-visible,.cb-cropper__icon-btn:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-cropper__empty{display:flex;flex-direction:column;align-items:center;gap:.75rem;padding:2.5rem 1rem;border:2px dashed var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-lg, 8px);color:var(--cb-color-text-muted);text-align:center}.cb-cropper__empty p{margin:0}.cb-cropper__empty--over{border-color:var(--cb-color-primary);background:color-mix(in srgb,var(--cb-color-primary) 6%,transparent)}.cb-cropper__error{margin:0;color:var(--cb-color-danger)}.cb-cropper__file{display:none}.cb-cropper__sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}\n"] }]
|
|
657
|
+
}], ctorParameters: () => [], propDecorators: { src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], rect: [{ type: i0.Input, args: [{ isSignal: true, alias: "rect", required: false }] }, { type: i0.Output, args: ["rectChange"] }], ratio: [{ type: i0.Input, args: [{ isSignal: true, alias: "ratio", required: false }] }, { type: i0.Output, args: ["ratioChange"] }], ratios: [{ type: i0.Input, args: [{ isSignal: true, alias: "ratios", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], output: [{ type: i0.Input, args: [{ isSignal: true, alias: "output", required: false }] }], rotatable: [{ type: i0.Input, args: [{ isSignal: true, alias: "rotatable", required: false }] }], preview: [{ type: i0.Input, args: [{ isSignal: true, alias: "preview", required: false }] }], chooser: [{ type: i0.Input, args: [{ isSignal: true, alias: "chooser", required: false }] }], showApply: [{ type: i0.Input, args: [{ isSignal: true, alias: "showApply", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], cropped: [{ type: i0.Output, args: ["cropped"] }], fileSelect: [{ type: i0.Output, args: ["fileSelect"] }], cropError: [{ type: i0.Output, args: ["cropError"] }], stage: [{ type: i0.ViewChild, args: ['stage', { isSignal: true }] }], box: [{ type: i0.ViewChild, args: ['box', { isSignal: true }] }], fileInput: [{ type: i0.ViewChild, args: ['file', { isSignal: true }] }] } });
|
|
658
|
+
/** Iconos propios (Lucide `image`). `rotate-ccw` y `rotate-cw` están en el set núcleo. */
|
|
659
|
+
const ICONS = {
|
|
660
|
+
'cb-image': '<rect width="18" height="18" x="3" y="3" rx="2" ry="2" /><circle cx="9" cy="9" r="2" /><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />',
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
/*
|
|
664
|
+
* Public API Surface de @caribeng/ui/image-cropper
|
|
665
|
+
*/
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Generated bundle index. Do not edit.
|
|
669
|
+
*/
|
|
670
|
+
|
|
671
|
+
export { CbImageCropper, clampRect, cropImage, fitRatio, initialRect, outputSize, previewStyle, readFileAsDataUrl, resizeRect, rotateImage };
|