@caribeng/ui 22.2.0 → 22.4.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 +93 -11
- package/fesm2022/caribeng-ui-accordion.mjs +2 -2
- package/fesm2022/caribeng-ui-avatar.mjs +2 -2
- package/fesm2022/caribeng-ui-badge.mjs +8 -2
- package/fesm2022/caribeng-ui-breadcrumb.mjs +13 -6
- package/fesm2022/caribeng-ui-chart.mjs +8 -5
- package/fesm2022/caribeng-ui-chat.mjs +1303 -0
- 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-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-dataview.mjs +4 -1
- package/fesm2022/caribeng-ui-dock.mjs +52 -12
- package/fesm2022/caribeng-ui-drawer.mjs +9 -4
- package/fesm2022/caribeng-ui-editor.mjs +21 -0
- package/fesm2022/caribeng-ui-fieldset.mjs +2 -2
- package/fesm2022/caribeng-ui-file-card.mjs +134 -17
- package/fesm2022/caribeng-ui-folder-card.mjs +122 -12
- package/fesm2022/caribeng-ui-inlinemessage.mjs +2 -2
- package/fesm2022/caribeng-ui-inputmask.mjs +8 -2
- package/fesm2022/caribeng-ui-kanban.mjs +4612 -0
- package/fesm2022/caribeng-ui-kbd.mjs +122 -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-organizationchart.mjs +6 -10
- package/fesm2022/caribeng-ui-panel.mjs +6 -6
- package/fesm2022/caribeng-ui-panelmenu.mjs +9 -6
- package/fesm2022/caribeng-ui-phone-input.mjs +688 -0
- package/fesm2022/caribeng-ui-pricing.mjs +263 -69
- package/fesm2022/caribeng-ui-qrcode.mjs +564 -0
- package/fesm2022/caribeng-ui-ripple.mjs +2 -0
- package/fesm2022/caribeng-ui-scrolltop.mjs +3 -1
- package/fesm2022/caribeng-ui-sidebar.mjs +573 -0
- package/fesm2022/caribeng-ui-stepper.mjs +10 -3
- package/fesm2022/caribeng-ui-table.mjs +10 -4
- package/fesm2022/caribeng-ui-tabmenu.mjs +8 -4
- package/fesm2022/caribeng-ui-tieredmenu.mjs +109 -19
- package/fesm2022/caribeng-ui.mjs +388 -1
- package/package.json +42 -2
- 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-badge.d.ts +3 -1
- package/types/caribeng-ui-breadcrumb.d.ts +8 -0
- package/types/caribeng-ui-chart.d.ts +3 -1
- package/types/caribeng-ui-chat.d.ts +299 -0
- 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-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-dataview.d.ts +2 -0
- package/types/caribeng-ui-dock.d.ts +13 -1
- package/types/caribeng-ui-drawer.d.ts +3 -1
- package/types/caribeng-ui-file-card.d.ts +27 -2
- package/types/caribeng-ui-folder-card.d.ts +27 -5
- package/types/caribeng-ui-inputmask.d.ts +3 -1
- package/types/caribeng-ui-kanban.d.ts +783 -0
- package/types/caribeng-ui-kbd.d.ts +36 -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-panelmenu.d.ts +7 -0
- package/types/caribeng-ui-phone-input.d.ts +168 -0
- package/types/caribeng-ui-pricing.d.ts +41 -4
- package/types/caribeng-ui-qrcode.d.ts +88 -0
- package/types/caribeng-ui-sidebar.d.ts +167 -0
- package/types/caribeng-ui-stepper.d.ts +2 -0
- package/types/caribeng-ui-table.d.ts +1 -1
- package/types/caribeng-ui-tabmenu.d.ts +7 -0
- package/types/caribeng-ui-tieredmenu.d.ts +17 -1
- package/types/caribeng-ui.d.ts +276 -2
- 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
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { DOCUMENT } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { inject, ViewContainerRef, Injector, viewChild, input, model, booleanAttribute, output, computed, signal, effect, untracked, afterNextRender, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
4
|
+
import { Overlay } from '@angular/cdk/overlay';
|
|
5
|
+
import { TemplatePortal } from '@angular/cdk/portal';
|
|
6
|
+
import { injectCbTranslation, cbFormat } from '@caribeng/ui';
|
|
7
|
+
import { CbIcon } from '@caribeng/ui/icon';
|
|
8
|
+
import { CbKbd } from '@caribeng/ui/kbd';
|
|
9
|
+
|
|
10
|
+
/** Minúsculas y sin tildes, carácter a carácter (conserva las posiciones). */
|
|
11
|
+
function cbCommandNormalize(text) {
|
|
12
|
+
let out = '';
|
|
13
|
+
for (const ch of text)
|
|
14
|
+
out += ch.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().charAt(0) || ch;
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
function isSubsequence(needle, haystack) {
|
|
18
|
+
let i = 0;
|
|
19
|
+
for (const ch of haystack)
|
|
20
|
+
if (ch === needle[i] && ++i === needle.length)
|
|
21
|
+
return true;
|
|
22
|
+
return needle.length === 0;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Relevancia de `item` para `query` (0 = no coincide). Prioriza el label que
|
|
26
|
+
* empieza por la búsqueda, luego el inicio de una palabra, el texto contenido,
|
|
27
|
+
* los sinónimos y la descripción, y por último las letras en orden ("cfg" → "Configuración").
|
|
28
|
+
*/
|
|
29
|
+
function cbCommandScore(item, query) {
|
|
30
|
+
const q = cbCommandNormalize(query.trim());
|
|
31
|
+
if (!q)
|
|
32
|
+
return 1;
|
|
33
|
+
const label = cbCommandNormalize(item.label);
|
|
34
|
+
if (label.startsWith(q))
|
|
35
|
+
return 100;
|
|
36
|
+
if (label.split(/[\s\-_/.]+/).some((word) => word.startsWith(q)))
|
|
37
|
+
return 80;
|
|
38
|
+
if (label.includes(q))
|
|
39
|
+
return 60;
|
|
40
|
+
const keywords = (item.keywords ?? []).map(cbCommandNormalize);
|
|
41
|
+
if (keywords.some((k) => k.startsWith(q)))
|
|
42
|
+
return 50;
|
|
43
|
+
if (keywords.some((k) => k.includes(q)))
|
|
44
|
+
return 40;
|
|
45
|
+
if (cbCommandNormalize(item.description ?? '').includes(q) || cbCommandNormalize(item.group ?? '').includes(q))
|
|
46
|
+
return 30;
|
|
47
|
+
if (q.length > 1 && isSubsequence(q, label.replace(/\s+/g, '')))
|
|
48
|
+
return 10;
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Filtra, ordena y agrupa los items. Sin búsqueda conserva el orden original;
|
|
53
|
+
* con búsqueda ordena por relevancia y los grupos siguen a su mejor resultado.
|
|
54
|
+
*/
|
|
55
|
+
function cbCommandFilter(items, query, filter = true) {
|
|
56
|
+
const ranked = items
|
|
57
|
+
.map((item, index) => ({ item, index, score: filter ? cbCommandScore(item, query) : 1 }))
|
|
58
|
+
.filter((entry) => entry.score > 0);
|
|
59
|
+
if (filter && query.trim())
|
|
60
|
+
ranked.sort((a, b) => b.score - a.score || a.index - b.index);
|
|
61
|
+
const groups = new Map();
|
|
62
|
+
for (const { item } of ranked) {
|
|
63
|
+
const key = item.group ?? '';
|
|
64
|
+
if (!groups.has(key))
|
|
65
|
+
groups.set(key, []);
|
|
66
|
+
groups.get(key).push(item);
|
|
67
|
+
}
|
|
68
|
+
return [...groups].map(([label, groupItems]) => ({ label, items: groupItems }));
|
|
69
|
+
}
|
|
70
|
+
/** Partes del label para resaltar la coincidencia contigua con la búsqueda. */
|
|
71
|
+
function cbCommandHighlight(label, query) {
|
|
72
|
+
const q = cbCommandNormalize(query.trim());
|
|
73
|
+
const start = q ? cbCommandNormalize(label).indexOf(q) : -1;
|
|
74
|
+
if (start < 0)
|
|
75
|
+
return [{ text: label, match: false }];
|
|
76
|
+
const chars = [...label];
|
|
77
|
+
const parts = [
|
|
78
|
+
{ text: chars.slice(0, start).join(''), match: false },
|
|
79
|
+
{ text: chars.slice(start, start + [...q].length).join(''), match: true },
|
|
80
|
+
{ text: chars.slice(start + [...q].length).join(''), match: false },
|
|
81
|
+
];
|
|
82
|
+
return parts.filter((p) => p.text);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let nextUid = 0;
|
|
86
|
+
/** Combinación de teclas en texto (`mod+k`, `ctrl+shift+p`, `/`). `mod` acepta ⌘ y Ctrl. */
|
|
87
|
+
function matchesShortcut(event, shortcut) {
|
|
88
|
+
const parts = shortcut.toLowerCase().split('+').map((p) => p.trim()).filter(Boolean);
|
|
89
|
+
const key = parts.pop();
|
|
90
|
+
if (!key || event.key.toLowerCase() !== key)
|
|
91
|
+
return false;
|
|
92
|
+
const wants = new Set(parts);
|
|
93
|
+
if (wants.has('mod')) {
|
|
94
|
+
if (!event.metaKey && !event.ctrlKey)
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
else if (event.ctrlKey !== wants.has('ctrl') || event.metaKey !== wants.has('meta')) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return event.shiftKey === wants.has('shift') && event.altKey === wants.has('alt');
|
|
101
|
+
}
|
|
102
|
+
/** Si el foco está en un campo de texto (para no robar atajos sin modificador como `/`). */
|
|
103
|
+
function isTyping(target) {
|
|
104
|
+
const el = target;
|
|
105
|
+
return !!el && (el.isContentEditable || /^(input|textarea|select)$/i.test(el.tagName));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Paleta de comandos de CaribeNG (Ctrl/⌘ + K): buscador global de páginas y
|
|
109
|
+
* acciones con teclado. Filtra sin tildes ni mayúsculas, agrupa, resalta la
|
|
110
|
+
* coincidencia y muestra atajos con `cb-kbd`.
|
|
111
|
+
*
|
|
112
|
+
* Accesibilidad: diálogo modal con un combobox (`aria-activedescendant`) que
|
|
113
|
+
* controla una lista de opciones; el foco no sale del campo y vuelve al
|
|
114
|
+
* elemento anterior al cerrar.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```html
|
|
118
|
+
* <cb-command-palette [items]="comandos" (itemSelect)="ir($event.item)" />
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
class CbCommandPalette {
|
|
122
|
+
constructor() {
|
|
123
|
+
/** Textos de interfaz en el idioma activo (`provideCaribeNG({ locale })`). */
|
|
124
|
+
this.t = injectCbTranslation();
|
|
125
|
+
this.overlay = inject(Overlay);
|
|
126
|
+
this.vcr = inject(ViewContainerRef);
|
|
127
|
+
this.document = inject(DOCUMENT);
|
|
128
|
+
this.injector = inject(Injector);
|
|
129
|
+
this.tpl = viewChild.required('tpl');
|
|
130
|
+
this.inputEl = viewChild('input', /* @ts-ignore */
|
|
131
|
+
...(ngDevMode ? [{ debugName: "inputEl" }] : /* istanbul ignore next */ []));
|
|
132
|
+
this.overlayRef = null;
|
|
133
|
+
this.previouslyFocused = null;
|
|
134
|
+
this.listId = `cb-command-${++nextUid}`;
|
|
135
|
+
/** Páginas y acciones. */
|
|
136
|
+
this.items = input([], /* @ts-ignore */
|
|
137
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
138
|
+
/** Abierta o cerrada. */
|
|
139
|
+
this.visible = model(false, /* @ts-ignore */
|
|
140
|
+
...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
141
|
+
/** Texto buscado (bidireccional, para búsquedas en servidor). */
|
|
142
|
+
this.query = model('', /* @ts-ignore */
|
|
143
|
+
...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
|
|
144
|
+
/** Atajo global que abre o cierra la paleta. `''` lo desactiva. */
|
|
145
|
+
this.shortcut = input('mod+k', /* @ts-ignore */
|
|
146
|
+
...(ngDevMode ? [{ debugName: "shortcut" }] : /* istanbul ignore next */ []));
|
|
147
|
+
this.placeholder = input(undefined, /* @ts-ignore */
|
|
148
|
+
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
149
|
+
/** Mensaje sin resultados. Por defecto, "Sin resultados". */
|
|
150
|
+
this.emptyMessage = input(undefined, /* @ts-ignore */
|
|
151
|
+
...(ngDevMode ? [{ debugName: "emptyMessage" }] : /* istanbul ignore next */ []));
|
|
152
|
+
/** Nombre del diálogo. Por defecto, "Paleta de comandos". */
|
|
153
|
+
this.ariaLabel = input(undefined, /* @ts-ignore */
|
|
154
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
155
|
+
/** Filtra `items` con la búsqueda. Desactívalo si los resultados llegan ya filtrados (servidor). */
|
|
156
|
+
this.filter = input(true, { ...(ngDevMode ? { debugName: "filter" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
157
|
+
/** Muestra "Cargando" mientras no hay resultados. */
|
|
158
|
+
this.loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
159
|
+
/** Cierra la paleta al elegir un item. */
|
|
160
|
+
this.closeOnSelect = input(true, { ...(ngDevMode ? { debugName: "closeOnSelect" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
161
|
+
/** Item elegido (con clic o Enter). */
|
|
162
|
+
this.itemSelect = output();
|
|
163
|
+
this.label = computed(() => this.ariaLabel() ?? this.t().commandPalette, /* @ts-ignore */
|
|
164
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
165
|
+
this.groups = computed(() => cbCommandFilter(this.items(), this.query(), this.filter()), /* @ts-ignore */
|
|
166
|
+
...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
|
|
167
|
+
/** Resultados en el orden en que se muestran (para el teclado). */
|
|
168
|
+
this.flat = computed(() => this.groups().flatMap((g) => g.items), /* @ts-ignore */
|
|
169
|
+
...(ngDevMode ? [{ debugName: "flat" }] : /* istanbul ignore next */ []));
|
|
170
|
+
this.enabled = computed(() => this.flat().filter((item) => !item.disabled), /* @ts-ignore */
|
|
171
|
+
...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
|
|
172
|
+
this.activeItem = signal(null, /* @ts-ignore */
|
|
173
|
+
...(ngDevMode ? [{ debugName: "activeItem" }] : /* istanbul ignore next */ []));
|
|
174
|
+
this.activeOptionId = computed(() => {
|
|
175
|
+
const item = this.activeItem();
|
|
176
|
+
return item ? this.optionId(item) : null;
|
|
177
|
+
}, /* @ts-ignore */
|
|
178
|
+
...(ngDevMode ? [{ debugName: "activeOptionId" }] : /* istanbul ignore next */ []));
|
|
179
|
+
this.announcement = computed(() => this.query().trim() && !this.loading() ? cbFormat(this.t().commandResults, String(this.flat().length)) : '', /* @ts-ignore */
|
|
180
|
+
...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
181
|
+
effect(() => {
|
|
182
|
+
const open = this.visible();
|
|
183
|
+
untracked(() => {
|
|
184
|
+
if (open && !this.overlayRef)
|
|
185
|
+
this.attach();
|
|
186
|
+
else if (!open && this.overlayRef)
|
|
187
|
+
this.detach();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
// Al cambiar los resultados, el activo pasa al primero disponible.
|
|
191
|
+
effect(() => {
|
|
192
|
+
const enabled = this.enabled();
|
|
193
|
+
untracked(() => {
|
|
194
|
+
const current = this.activeItem();
|
|
195
|
+
if (!current || !enabled.includes(current))
|
|
196
|
+
this.activeItem.set(enabled[0] ?? null);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
open() {
|
|
201
|
+
this.visible.set(true);
|
|
202
|
+
}
|
|
203
|
+
close() {
|
|
204
|
+
this.visible.set(false);
|
|
205
|
+
}
|
|
206
|
+
toggle() {
|
|
207
|
+
this.visible.update((v) => !v);
|
|
208
|
+
}
|
|
209
|
+
optionId(item) {
|
|
210
|
+
return `${this.listId}-o${this.flat().indexOf(item)}`;
|
|
211
|
+
}
|
|
212
|
+
highlight(item) {
|
|
213
|
+
return this.filter() ? cbCommandHighlight(item.label, this.query()) : [{ text: item.label, match: false }];
|
|
214
|
+
}
|
|
215
|
+
onDocumentKeydown(event) {
|
|
216
|
+
const shortcut = this.shortcut();
|
|
217
|
+
if (!shortcut || !matchesShortcut(event, shortcut))
|
|
218
|
+
return;
|
|
219
|
+
// Un atajo sin modificador (p. ej. "/") no se roba mientras se escribe en otro campo.
|
|
220
|
+
const hasModifier = /(^|\+)(mod|ctrl|meta|alt)\+/i.test(shortcut);
|
|
221
|
+
if (!hasModifier && !this.visible() && isTyping(event.target))
|
|
222
|
+
return;
|
|
223
|
+
event.preventDefault();
|
|
224
|
+
this.toggle();
|
|
225
|
+
}
|
|
226
|
+
onInput(event) {
|
|
227
|
+
this.query.set(event.target.value);
|
|
228
|
+
}
|
|
229
|
+
hover(item) {
|
|
230
|
+
if (!item.disabled && this.activeItem() !== item)
|
|
231
|
+
this.activeItem.set(item);
|
|
232
|
+
}
|
|
233
|
+
onKeydown(event) {
|
|
234
|
+
const enabled = this.enabled();
|
|
235
|
+
const index = enabled.indexOf(this.activeItem());
|
|
236
|
+
switch (event.key) {
|
|
237
|
+
case 'ArrowDown':
|
|
238
|
+
case 'ArrowUp': {
|
|
239
|
+
event.preventDefault();
|
|
240
|
+
if (!enabled.length)
|
|
241
|
+
return;
|
|
242
|
+
const step = event.key === 'ArrowDown' ? 1 : -1;
|
|
243
|
+
this.setActive(enabled[(index + step + enabled.length) % enabled.length]);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case 'Home':
|
|
247
|
+
case 'End':
|
|
248
|
+
if (!event.ctrlKey && !event.metaKey)
|
|
249
|
+
return;
|
|
250
|
+
event.preventDefault();
|
|
251
|
+
this.setActive(event.key === 'Home' ? enabled[0] : enabled[enabled.length - 1]);
|
|
252
|
+
break;
|
|
253
|
+
case 'Enter': {
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
const item = this.activeItem();
|
|
256
|
+
if (item)
|
|
257
|
+
this.select(item, event);
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
case 'Tab':
|
|
261
|
+
// El foco se queda en el campo: la lista se recorre con las flechas.
|
|
262
|
+
event.preventDefault();
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/** Elige un item: ejecuta su `command`, emite `itemSelect` y cierra (según `closeOnSelect`). */
|
|
267
|
+
select(item, originalEvent) {
|
|
268
|
+
if (item.disabled)
|
|
269
|
+
return;
|
|
270
|
+
const event = { item, query: this.query(), originalEvent };
|
|
271
|
+
if (this.closeOnSelect())
|
|
272
|
+
this.close();
|
|
273
|
+
item.command?.(event);
|
|
274
|
+
this.itemSelect.emit(event);
|
|
275
|
+
}
|
|
276
|
+
setActive(item) {
|
|
277
|
+
if (!item)
|
|
278
|
+
return;
|
|
279
|
+
this.activeItem.set(item);
|
|
280
|
+
afterNextRender(() => {
|
|
281
|
+
this.document.getElementById(this.optionId(item))?.scrollIntoView?.({ block: 'nearest' });
|
|
282
|
+
}, { injector: this.injector });
|
|
283
|
+
}
|
|
284
|
+
attach() {
|
|
285
|
+
this.previouslyFocused = this.document.activeElement;
|
|
286
|
+
this.query.set('');
|
|
287
|
+
this.overlayRef = this.overlay.create({
|
|
288
|
+
hasBackdrop: true,
|
|
289
|
+
backdropClass: 'cb-dialog-backdrop',
|
|
290
|
+
panelClass: 'cb-command-pane',
|
|
291
|
+
scrollStrategy: this.overlay.scrollStrategies.block(),
|
|
292
|
+
positionStrategy: this.overlay.position().global().centerHorizontally().top('12vh'),
|
|
293
|
+
});
|
|
294
|
+
this.overlayRef.attach(new TemplatePortal(this.tpl(), this.vcr));
|
|
295
|
+
this.overlayRef.backdropClick().subscribe(() => this.close());
|
|
296
|
+
this.overlayRef.keydownEvents().subscribe((event) => {
|
|
297
|
+
if (event.key === 'Escape') {
|
|
298
|
+
event.preventDefault();
|
|
299
|
+
this.close();
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
this.activeItem.set(this.enabled()[0] ?? null);
|
|
303
|
+
afterNextRender(() => this.inputEl()?.nativeElement.focus(), { injector: this.injector });
|
|
304
|
+
}
|
|
305
|
+
detach() {
|
|
306
|
+
this.overlayRef?.dispose();
|
|
307
|
+
this.overlayRef = null;
|
|
308
|
+
const target = this.previouslyFocused;
|
|
309
|
+
this.previouslyFocused = null;
|
|
310
|
+
if (target?.isConnected)
|
|
311
|
+
target.focus();
|
|
312
|
+
}
|
|
313
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbCommandPalette, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
314
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: CbCommandPalette, isStandalone: true, selector: "cb-command-palette", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, shortcut: { classPropertyName: "shortcut", publicName: "shortcut", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, closeOnSelect: { classPropertyName: "closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange", query: "queryChange", itemSelect: "itemSelect" }, host: { listeners: { "document:keydown": "onDocumentKeydown($event)" }, classAttribute: "cb-command-palette" }, viewQueries: [{ propertyName: "tpl", first: true, predicate: ["tpl"], descendants: true, isSignal: true }, { propertyName: "inputEl", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
315
|
+
<ng-template #tpl>
|
|
316
|
+
<div class="cb-command" role="dialog" aria-modal="true" [attr.aria-label]="label()">
|
|
317
|
+
<div class="cb-command__search">
|
|
318
|
+
<cb-icon name="search" [size]="18" class="cb-command__search-icon" aria-hidden="true" />
|
|
319
|
+
<input
|
|
320
|
+
#input
|
|
321
|
+
class="cb-command__input"
|
|
322
|
+
type="text"
|
|
323
|
+
role="combobox"
|
|
324
|
+
aria-autocomplete="list"
|
|
325
|
+
aria-expanded="true"
|
|
326
|
+
autocomplete="off"
|
|
327
|
+
spellcheck="false"
|
|
328
|
+
[attr.aria-controls]="listId"
|
|
329
|
+
[attr.aria-activedescendant]="activeOptionId()"
|
|
330
|
+
[attr.aria-label]="label()"
|
|
331
|
+
[placeholder]="placeholder() ?? t().commandPlaceholder"
|
|
332
|
+
[value]="query()"
|
|
333
|
+
(input)="onInput($event)"
|
|
334
|
+
(keydown)="onKeydown($event)"
|
|
335
|
+
/>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<div class="cb-command__list" role="listbox" [id]="listId" [attr.aria-label]="label()">
|
|
339
|
+
@for (group of groups(); track group.label; let g = $index) {
|
|
340
|
+
<div role="group" [attr.aria-labelledby]="group.label ? listId + '-g' + g : null">
|
|
341
|
+
@if (group.label) {
|
|
342
|
+
<div class="cb-command__group" [id]="listId + '-g' + g" aria-hidden="true">{{ group.label }}</div>
|
|
343
|
+
}
|
|
344
|
+
@for (item of group.items; track item.id ?? item.label) {
|
|
345
|
+
<div
|
|
346
|
+
class="cb-command__option"
|
|
347
|
+
role="option"
|
|
348
|
+
[id]="optionId(item)"
|
|
349
|
+
[class.cb-command__option--active]="item === activeItem()"
|
|
350
|
+
[attr.aria-selected]="item === activeItem()"
|
|
351
|
+
[attr.aria-disabled]="item.disabled ? 'true' : null"
|
|
352
|
+
(mousemove)="hover(item)"
|
|
353
|
+
(mousedown)="$event.preventDefault()"
|
|
354
|
+
(click)="select(item, $event)"
|
|
355
|
+
>
|
|
356
|
+
@if (item.icon) {
|
|
357
|
+
<cb-icon class="cb-command__icon" [name]="item.icon" [size]="18" aria-hidden="true" />
|
|
358
|
+
}
|
|
359
|
+
<span class="cb-command__text">
|
|
360
|
+
<!-- En una línea: los saltos de línea añadirían espacios dentro de la palabra resaltada. -->
|
|
361
|
+
<span class="cb-command__label">@for (part of highlight(item); track $index) {@if (part.match) {<mark class="cb-command__match">{{ part.text }}</mark>} @else {<span>{{ part.text }}</span>}}</span>
|
|
362
|
+
@if (item.description) {
|
|
363
|
+
<span class="cb-command__description">{{ item.description }}</span>
|
|
364
|
+
}
|
|
365
|
+
</span>
|
|
366
|
+
@if (item.shortcut?.length) {
|
|
367
|
+
<cb-kbd class="cb-command__shortcut" [keys]="item.shortcut!" size="sm" aria-hidden="true" />
|
|
368
|
+
}
|
|
369
|
+
</div>
|
|
370
|
+
}
|
|
371
|
+
</div>
|
|
372
|
+
}
|
|
373
|
+
@if (!flat().length) {
|
|
374
|
+
<div class="cb-command__empty">{{ loading() ? t().loading : (emptyMessage() ?? t().noResults) }}</div>
|
|
375
|
+
}
|
|
376
|
+
</div>
|
|
377
|
+
|
|
378
|
+
<div class="cb-command__footer" aria-hidden="true">
|
|
379
|
+
<span><cb-kbd [keys]="['up', 'down']" size="sm" /> {{ t().commandNavigate }}</span>
|
|
380
|
+
<span><cb-kbd [keys]="['enter']" size="sm" /> {{ t().commandSelect }}</span>
|
|
381
|
+
<span><cb-kbd size="sm">Esc</cb-kbd> {{ t().close }}</span>
|
|
382
|
+
</div>
|
|
383
|
+
<div class="cb-command__status" role="status">{{ announcement() }}</div>
|
|
384
|
+
</div>
|
|
385
|
+
</ng-template>
|
|
386
|
+
`, isInline: true, styles: [":host{display:contents}.cb-command{display:flex;flex-direction:column;width:min(40rem,calc(100vw - 2rem));max-height:min(32rem,76vh);overflow:hidden;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-lg, 12px);background:var(--cb-color-surface-raised);box-shadow:var(--cb-shadow-lg);color:var(--cb-color-text);font-family:var(--cb-font-sans)}.cb-command__search{display:flex;align-items:center;gap:.625rem;padding:0 1rem;border-bottom:1px solid var(--cb-color-border)}.cb-command__search-icon{flex:none;color:var(--cb-color-text-muted)}.cb-command__input{flex:1;min-width:0;height:3.25rem;padding:0;border:0;outline:none;background:transparent;color:inherit;font:inherit;font-size:var(--cb-text-base, 1rem)}.cb-command__input::placeholder{color:var(--cb-color-text-muted)}.cb-command__list{flex:1;overflow-y:auto;overscroll-behavior:contain;padding:.375rem}.cb-command__group{padding:.625rem .625rem .25rem;color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem);font-weight:var(--cb-font-weight-semibold, 600)}.cb-command__option{display:flex;align-items:center;gap:.75rem;min-height:2.5rem;padding:.375rem .625rem;border-radius:var(--cb-radius-md, 8px);cursor:pointer;-webkit-user-select:none;user-select:none}.cb-command__option--active{background:var(--cb-color-surface-hover);color:var(--cb-color-text-strong);outline:2px solid transparent;box-shadow:inset 3px 0 0 var(--cb-color-primary)}.cb-command__option[aria-disabled=true]{cursor:not-allowed;opacity:.5}.cb-command__icon{flex:none;color:var(--cb-color-text-muted)}.cb-command__text{display:flex;flex:1;flex-direction:column;min-width:0}.cb-command__label,.cb-command__description{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cb-command__description{color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem)}.cb-command__match{background:transparent;color:var(--cb-color-primary);font-weight:var(--cb-font-weight-semibold, 600)}.cb-command__shortcut{flex:none}.cb-command__empty{padding:2rem 1rem;color:var(--cb-color-text-muted);text-align:center}.cb-command__footer{display:flex;flex-wrap:wrap;gap:1rem;padding:.5rem 1rem;border-top:1px solid var(--cb-color-border);color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem)}.cb-command__footer span{display:inline-flex;align-items:center;gap:.375rem}.cb-command__status{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}@media(max-width:480px){.cb-command__footer{display:none}}\n"], dependencies: [{ kind: "component", type: CbIcon, selector: "cb-icon", inputs: ["name", "size", "spin", "ariaLabel"] }, { kind: "component", type: CbKbd, selector: "cb-kbd", inputs: ["keys", "platform", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
387
|
+
}
|
|
388
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbCommandPalette, decorators: [{
|
|
389
|
+
type: Component,
|
|
390
|
+
args: [{ selector: 'cb-command-palette', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, imports: [CbIcon, CbKbd], host: {
|
|
391
|
+
class: 'cb-command-palette',
|
|
392
|
+
'(document:keydown)': 'onDocumentKeydown($event)',
|
|
393
|
+
}, template: `
|
|
394
|
+
<ng-template #tpl>
|
|
395
|
+
<div class="cb-command" role="dialog" aria-modal="true" [attr.aria-label]="label()">
|
|
396
|
+
<div class="cb-command__search">
|
|
397
|
+
<cb-icon name="search" [size]="18" class="cb-command__search-icon" aria-hidden="true" />
|
|
398
|
+
<input
|
|
399
|
+
#input
|
|
400
|
+
class="cb-command__input"
|
|
401
|
+
type="text"
|
|
402
|
+
role="combobox"
|
|
403
|
+
aria-autocomplete="list"
|
|
404
|
+
aria-expanded="true"
|
|
405
|
+
autocomplete="off"
|
|
406
|
+
spellcheck="false"
|
|
407
|
+
[attr.aria-controls]="listId"
|
|
408
|
+
[attr.aria-activedescendant]="activeOptionId()"
|
|
409
|
+
[attr.aria-label]="label()"
|
|
410
|
+
[placeholder]="placeholder() ?? t().commandPlaceholder"
|
|
411
|
+
[value]="query()"
|
|
412
|
+
(input)="onInput($event)"
|
|
413
|
+
(keydown)="onKeydown($event)"
|
|
414
|
+
/>
|
|
415
|
+
</div>
|
|
416
|
+
|
|
417
|
+
<div class="cb-command__list" role="listbox" [id]="listId" [attr.aria-label]="label()">
|
|
418
|
+
@for (group of groups(); track group.label; let g = $index) {
|
|
419
|
+
<div role="group" [attr.aria-labelledby]="group.label ? listId + '-g' + g : null">
|
|
420
|
+
@if (group.label) {
|
|
421
|
+
<div class="cb-command__group" [id]="listId + '-g' + g" aria-hidden="true">{{ group.label }}</div>
|
|
422
|
+
}
|
|
423
|
+
@for (item of group.items; track item.id ?? item.label) {
|
|
424
|
+
<div
|
|
425
|
+
class="cb-command__option"
|
|
426
|
+
role="option"
|
|
427
|
+
[id]="optionId(item)"
|
|
428
|
+
[class.cb-command__option--active]="item === activeItem()"
|
|
429
|
+
[attr.aria-selected]="item === activeItem()"
|
|
430
|
+
[attr.aria-disabled]="item.disabled ? 'true' : null"
|
|
431
|
+
(mousemove)="hover(item)"
|
|
432
|
+
(mousedown)="$event.preventDefault()"
|
|
433
|
+
(click)="select(item, $event)"
|
|
434
|
+
>
|
|
435
|
+
@if (item.icon) {
|
|
436
|
+
<cb-icon class="cb-command__icon" [name]="item.icon" [size]="18" aria-hidden="true" />
|
|
437
|
+
}
|
|
438
|
+
<span class="cb-command__text">
|
|
439
|
+
<!-- En una línea: los saltos de línea añadirían espacios dentro de la palabra resaltada. -->
|
|
440
|
+
<span class="cb-command__label">@for (part of highlight(item); track $index) {@if (part.match) {<mark class="cb-command__match">{{ part.text }}</mark>} @else {<span>{{ part.text }}</span>}}</span>
|
|
441
|
+
@if (item.description) {
|
|
442
|
+
<span class="cb-command__description">{{ item.description }}</span>
|
|
443
|
+
}
|
|
444
|
+
</span>
|
|
445
|
+
@if (item.shortcut?.length) {
|
|
446
|
+
<cb-kbd class="cb-command__shortcut" [keys]="item.shortcut!" size="sm" aria-hidden="true" />
|
|
447
|
+
}
|
|
448
|
+
</div>
|
|
449
|
+
}
|
|
450
|
+
</div>
|
|
451
|
+
}
|
|
452
|
+
@if (!flat().length) {
|
|
453
|
+
<div class="cb-command__empty">{{ loading() ? t().loading : (emptyMessage() ?? t().noResults) }}</div>
|
|
454
|
+
}
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
<div class="cb-command__footer" aria-hidden="true">
|
|
458
|
+
<span><cb-kbd [keys]="['up', 'down']" size="sm" /> {{ t().commandNavigate }}</span>
|
|
459
|
+
<span><cb-kbd [keys]="['enter']" size="sm" /> {{ t().commandSelect }}</span>
|
|
460
|
+
<span><cb-kbd size="sm">Esc</cb-kbd> {{ t().close }}</span>
|
|
461
|
+
</div>
|
|
462
|
+
<div class="cb-command__status" role="status">{{ announcement() }}</div>
|
|
463
|
+
</div>
|
|
464
|
+
</ng-template>
|
|
465
|
+
`, styles: [":host{display:contents}.cb-command{display:flex;flex-direction:column;width:min(40rem,calc(100vw - 2rem));max-height:min(32rem,76vh);overflow:hidden;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-lg, 12px);background:var(--cb-color-surface-raised);box-shadow:var(--cb-shadow-lg);color:var(--cb-color-text);font-family:var(--cb-font-sans)}.cb-command__search{display:flex;align-items:center;gap:.625rem;padding:0 1rem;border-bottom:1px solid var(--cb-color-border)}.cb-command__search-icon{flex:none;color:var(--cb-color-text-muted)}.cb-command__input{flex:1;min-width:0;height:3.25rem;padding:0;border:0;outline:none;background:transparent;color:inherit;font:inherit;font-size:var(--cb-text-base, 1rem)}.cb-command__input::placeholder{color:var(--cb-color-text-muted)}.cb-command__list{flex:1;overflow-y:auto;overscroll-behavior:contain;padding:.375rem}.cb-command__group{padding:.625rem .625rem .25rem;color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem);font-weight:var(--cb-font-weight-semibold, 600)}.cb-command__option{display:flex;align-items:center;gap:.75rem;min-height:2.5rem;padding:.375rem .625rem;border-radius:var(--cb-radius-md, 8px);cursor:pointer;-webkit-user-select:none;user-select:none}.cb-command__option--active{background:var(--cb-color-surface-hover);color:var(--cb-color-text-strong);outline:2px solid transparent;box-shadow:inset 3px 0 0 var(--cb-color-primary)}.cb-command__option[aria-disabled=true]{cursor:not-allowed;opacity:.5}.cb-command__icon{flex:none;color:var(--cb-color-text-muted)}.cb-command__text{display:flex;flex:1;flex-direction:column;min-width:0}.cb-command__label,.cb-command__description{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cb-command__description{color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem)}.cb-command__match{background:transparent;color:var(--cb-color-primary);font-weight:var(--cb-font-weight-semibold, 600)}.cb-command__shortcut{flex:none}.cb-command__empty{padding:2rem 1rem;color:var(--cb-color-text-muted);text-align:center}.cb-command__footer{display:flex;flex-wrap:wrap;gap:1rem;padding:.5rem 1rem;border-top:1px solid var(--cb-color-border);color:var(--cb-color-text-muted);font-size:var(--cb-text-xs, .75rem)}.cb-command__footer span{display:inline-flex;align-items:center;gap:.375rem}.cb-command__status{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}@media(max-width:480px){.cb-command__footer{display:none}}\n"] }]
|
|
466
|
+
}], ctorParameters: () => [], propDecorators: { tpl: [{ type: i0.ViewChild, args: ['tpl', { isSignal: true }] }], inputEl: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }, { type: i0.Output, args: ["queryChange"] }], shortcut: [{ type: i0.Input, args: [{ isSignal: true, alias: "shortcut", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], itemSelect: [{ type: i0.Output, args: ["itemSelect"] }] } });
|
|
467
|
+
|
|
468
|
+
/*
|
|
469
|
+
* Public API Surface de @caribeng/ui/command-palette
|
|
470
|
+
*/
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Generated bundle index. Do not edit.
|
|
474
|
+
*/
|
|
475
|
+
|
|
476
|
+
export { CbCommandPalette, cbCommandFilter, cbCommandHighlight, cbCommandNormalize, cbCommandScore };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DOCUMENT } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { inject, model, input, output, effect, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { inject, model, input, output, signal, computed, effect, untracked, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
4
4
|
import * as i1 from '@angular/cdk/a11y';
|
|
5
5
|
import { A11yModule } from '@angular/cdk/a11y';
|
|
6
|
-
import { injectCbTranslation } from '@caribeng/ui';
|
|
6
|
+
import { injectCbTranslation, CbConfirmationService } from '@caribeng/ui';
|
|
7
7
|
import { CbButton } from '@caribeng/ui/button';
|
|
8
8
|
|
|
9
9
|
let nextUid = 0;
|
|
@@ -22,11 +22,22 @@ let nextUid = 0;
|
|
|
22
22
|
* (accepted)="eliminar()"
|
|
23
23
|
* />
|
|
24
24
|
* ```
|
|
25
|
+
*
|
|
26
|
+
* Con `service` muestra las confirmaciones pedidas desde código con
|
|
27
|
+
* `CbConfirmationService` (como `ConfirmationService` de PrimeNG):
|
|
28
|
+
*
|
|
29
|
+
* ```html
|
|
30
|
+
* <cb-confirm-dialog service />
|
|
31
|
+
* ```
|
|
32
|
+
* ```ts
|
|
33
|
+
* inject(CbConfirmationService).confirm({ message: '¿Eliminar?', accept: () => this.eliminar() });
|
|
34
|
+
* ```
|
|
25
35
|
*/
|
|
26
36
|
class CbConfirmDialog {
|
|
27
37
|
constructor() {
|
|
28
38
|
this.t = injectCbTranslation();
|
|
29
39
|
this.document = inject(DOCUMENT);
|
|
40
|
+
this.confirmation = inject(CbConfirmationService);
|
|
30
41
|
/** Visibilidad (two-way). */
|
|
31
42
|
this.visible = model(false, /* @ts-ignore */
|
|
32
43
|
...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
@@ -43,10 +54,35 @@ class CbConfirmDialog {
|
|
|
43
54
|
/** Severidad del botón de aceptar (p. ej. `danger` para acciones destructivas). */
|
|
44
55
|
this.acceptSeverity = input('primary', /* @ts-ignore */
|
|
45
56
|
...(ngDevMode ? [{ debugName: "acceptSeverity" }] : /* istanbul ignore next */ []));
|
|
57
|
+
/**
|
|
58
|
+
* Muestra las confirmaciones pedidas con `CbConfirmationService`. Sin él, el
|
|
59
|
+
* componente solo responde a `[(visible)]` (así un diálogo declarativo no se
|
|
60
|
+
* abre con las confirmaciones de otra parte de la app).
|
|
61
|
+
*/
|
|
62
|
+
this.service = input(false, { ...(ngDevMode ? { debugName: "service" } : /* istanbul ignore next */ {}), transform: toBoolean });
|
|
63
|
+
/** Con `service`: atiende solo las confirmaciones con este mismo `key` (o sin key si no se indica). */
|
|
64
|
+
this.key = input(undefined, /* @ts-ignore */
|
|
65
|
+
...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
|
|
46
66
|
this.accepted = output();
|
|
47
67
|
this.rejected = output();
|
|
48
68
|
this.titleId = `cb-confirm-dialog-${++nextUid}-title`;
|
|
49
69
|
this.messageId = `${this.titleId.replace('-title', '')}-message`;
|
|
70
|
+
/** Confirmación pedida con `CbConfirmationService` que se está mostrando. */
|
|
71
|
+
this.active = signal(null, /* @ts-ignore */
|
|
72
|
+
...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
73
|
+
/** Textos visibles: los de la confirmación del servicio, si la hay; si no, los inputs y el idioma. */
|
|
74
|
+
this.shown = computed(() => {
|
|
75
|
+
const active = this.active();
|
|
76
|
+
const t = this.t();
|
|
77
|
+
return {
|
|
78
|
+
header: active?.header ?? this.header() ?? t.confirm,
|
|
79
|
+
message: active?.message ?? this.message() ?? t.confirmMessage,
|
|
80
|
+
acceptLabel: active?.acceptLabel ?? this.acceptLabel() ?? t.accept,
|
|
81
|
+
rejectLabel: active?.rejectLabel ?? this.rejectLabel() ?? t.cancel,
|
|
82
|
+
acceptSeverity: active?.acceptSeverity ?? this.acceptSeverity(),
|
|
83
|
+
};
|
|
84
|
+
}, /* @ts-ignore */
|
|
85
|
+
...(ngDevMode ? [{ debugName: "shown" }] : /* istanbul ignore next */ []));
|
|
50
86
|
/** Elemento enfocado antes de abrir; recibe el foco de vuelta al cerrar. */
|
|
51
87
|
this.previouslyFocused = null;
|
|
52
88
|
effect(() => {
|
|
@@ -59,6 +95,22 @@ class CbConfirmDialog {
|
|
|
59
95
|
queueMicrotask(() => el.focus?.());
|
|
60
96
|
}
|
|
61
97
|
});
|
|
98
|
+
effect(() => {
|
|
99
|
+
if (!this.service())
|
|
100
|
+
return;
|
|
101
|
+
const request = this.confirmation.request();
|
|
102
|
+
untracked(() => {
|
|
103
|
+
if (request && request.confirmation.key === this.key()) {
|
|
104
|
+
this.active.set(request.confirmation);
|
|
105
|
+
this.visible.set(true);
|
|
106
|
+
}
|
|
107
|
+
else if (!request && this.active()) {
|
|
108
|
+
// Cerrada desde el servicio (close()).
|
|
109
|
+
this.active.set(null);
|
|
110
|
+
this.visible.set(false);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
62
114
|
}
|
|
63
115
|
onEscape() {
|
|
64
116
|
if (this.visible())
|
|
@@ -66,14 +118,24 @@ class CbConfirmDialog {
|
|
|
66
118
|
}
|
|
67
119
|
confirm() {
|
|
68
120
|
this.accepted.emit();
|
|
69
|
-
this.
|
|
121
|
+
this.finish('accept');
|
|
70
122
|
}
|
|
71
123
|
reject() {
|
|
72
124
|
this.rejected.emit();
|
|
125
|
+
this.finish('reject');
|
|
126
|
+
}
|
|
127
|
+
/** Cierra y, si venía del servicio, lo libera y ejecuta su callback (que puede pedir otra confirmación). */
|
|
128
|
+
finish(action) {
|
|
129
|
+
const active = this.active();
|
|
130
|
+
this.active.set(null);
|
|
73
131
|
this.visible.set(false);
|
|
132
|
+
if (active) {
|
|
133
|
+
this.confirmation.close();
|
|
134
|
+
active[action]?.();
|
|
135
|
+
}
|
|
74
136
|
}
|
|
75
137
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbConfirmDialog, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
76
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: CbConfirmDialog, isStandalone: true, selector: "cb-confirm-dialog", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, acceptLabel: { classPropertyName: "acceptLabel", publicName: "acceptLabel", isSignal: true, isRequired: false, transformFunction: null }, rejectLabel: { classPropertyName: "rejectLabel", publicName: "rejectLabel", isSignal: true, isRequired: false, transformFunction: null }, acceptSeverity: { classPropertyName: "acceptSeverity", publicName: "acceptSeverity", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange", accepted: "accepted", rejected: "rejected" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: `
|
|
138
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: CbConfirmDialog, isStandalone: true, selector: "cb-confirm-dialog", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, acceptLabel: { classPropertyName: "acceptLabel", publicName: "acceptLabel", isSignal: true, isRequired: false, transformFunction: null }, rejectLabel: { classPropertyName: "rejectLabel", publicName: "rejectLabel", isSignal: true, isRequired: false, transformFunction: null }, acceptSeverity: { classPropertyName: "acceptSeverity", publicName: "acceptSeverity", isSignal: true, isRequired: false, transformFunction: null }, service: { classPropertyName: "service", publicName: "service", isSignal: true, isRequired: false, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange", accepted: "accepted", rejected: "rejected" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: `
|
|
77
139
|
@if (visible()) {
|
|
78
140
|
<div class="cb-confirm-dialog__mask" (click)="reject()">
|
|
79
141
|
<section
|
|
@@ -86,11 +148,11 @@ class CbConfirmDialog {
|
|
|
86
148
|
cdkTrapFocusAutoCapture
|
|
87
149
|
(click)="$event.stopPropagation()"
|
|
88
150
|
>
|
|
89
|
-
<h2 class="cb-confirm-dialog__title" [id]="titleId">{{
|
|
90
|
-
<p class="cb-confirm-dialog__message" [id]="messageId">{{
|
|
151
|
+
<h2 class="cb-confirm-dialog__title" [id]="titleId">{{ shown().header }}</h2>
|
|
152
|
+
<p class="cb-confirm-dialog__message" [id]="messageId">{{ shown().message }}</p>
|
|
91
153
|
<footer class="cb-confirm-dialog__actions">
|
|
92
|
-
<cb-button variant="text" severity="secondary" (click)="reject()">{{
|
|
93
|
-
<cb-button [severity]="
|
|
154
|
+
<cb-button variant="text" severity="secondary" (click)="reject()">{{ shown().rejectLabel }}</cb-button>
|
|
155
|
+
<cb-button [severity]="shown().acceptSeverity" (click)="confirm()">{{ shown().acceptLabel }}</cb-button>
|
|
94
156
|
</footer>
|
|
95
157
|
</section>
|
|
96
158
|
</div>
|
|
@@ -112,17 +174,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImpor
|
|
|
112
174
|
cdkTrapFocusAutoCapture
|
|
113
175
|
(click)="$event.stopPropagation()"
|
|
114
176
|
>
|
|
115
|
-
<h2 class="cb-confirm-dialog__title" [id]="titleId">{{
|
|
116
|
-
<p class="cb-confirm-dialog__message" [id]="messageId">{{
|
|
177
|
+
<h2 class="cb-confirm-dialog__title" [id]="titleId">{{ shown().header }}</h2>
|
|
178
|
+
<p class="cb-confirm-dialog__message" [id]="messageId">{{ shown().message }}</p>
|
|
117
179
|
<footer class="cb-confirm-dialog__actions">
|
|
118
|
-
<cb-button variant="text" severity="secondary" (click)="reject()">{{
|
|
119
|
-
<cb-button [severity]="
|
|
180
|
+
<cb-button variant="text" severity="secondary" (click)="reject()">{{ shown().rejectLabel }}</cb-button>
|
|
181
|
+
<cb-button [severity]="shown().acceptSeverity" (click)="confirm()">{{ shown().acceptLabel }}</cb-button>
|
|
120
182
|
</footer>
|
|
121
183
|
</section>
|
|
122
184
|
</div>
|
|
123
185
|
}
|
|
124
186
|
`, styles: [":host{display:contents}.cb-confirm-dialog__mask{position:fixed;inset:0;z-index:1000;display:grid;place-items:center;padding:var(--cb-space-4, 1rem);background:color-mix(in srgb,#000 40%,transparent)}.cb-confirm-dialog{box-sizing:border-box;width:min(28rem,100%);padding:var(--cb-space-6, 1.5rem);border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-lg, .75rem);background:var(--cb-color-surface-raised);color:var(--cb-color-text);box-shadow:var(--cb-shadow-lg, 0 1rem 3rem rgb(0 0 0 / 20%))}.cb-confirm-dialog__title{margin:0 0 var(--cb-space-2, .5rem);font-size:var(--cb-text-lg, 1.125rem);font-weight:var(--cb-font-weight-semibold, 600);color:var(--cb-color-text-strong)}.cb-confirm-dialog__message{margin:0;color:var(--cb-color-text-muted);line-height:1.55}.cb-confirm-dialog__actions{display:flex;justify-content:flex-end;gap:var(--cb-space-2, .5rem);margin-top:var(--cb-space-6, 1.5rem)}\n"] }]
|
|
125
|
-
}], ctorParameters: () => [], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], acceptLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptLabel", required: false }] }], rejectLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rejectLabel", required: false }] }], acceptSeverity: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptSeverity", required: false }] }], accepted: [{ type: i0.Output, args: ["accepted"] }], rejected: [{ type: i0.Output, args: ["rejected"] }] } });
|
|
187
|
+
}], ctorParameters: () => [], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], acceptLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptLabel", required: false }] }], rejectLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rejectLabel", required: false }] }], acceptSeverity: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptSeverity", required: false }] }], service: [{ type: i0.Input, args: [{ isSignal: true, alias: "service", required: false }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: false }] }], accepted: [{ type: i0.Output, args: ["accepted"] }], rejected: [{ type: i0.Output, args: ["rejected"] }] } });
|
|
188
|
+
function toBoolean(value) {
|
|
189
|
+
return value !== false && value != null && `${value}` !== 'false';
|
|
190
|
+
}
|
|
126
191
|
|
|
127
192
|
/**
|
|
128
193
|
* Generated bundle index. Do not edit.
|