@mimi-ng/cli 0.1.1 → 0.2.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 +14 -1
- package/dist/init/css.js +17 -0
- package/dist/init/index.js +7 -5
- package/dist/init/items.js +9 -0
- package/dist/registry.json +222 -1
- package/dist/templates/components/avatar/avatar.ts +1 -1
- package/dist/templates/components/avatar/avatar.variants.ts +4 -4
- package/dist/templates/components/badge/badge.ts +41 -6
- package/dist/templates/components/badge/badge.variants.ts +105 -11
- package/dist/templates/components/button/button.ts +74 -7
- package/dist/templates/components/button/button.variants.ts +145 -21
- package/dist/templates/components/card/card.ts +8 -5
- package/dist/templates/components/checkbox/checkbox.ts +3 -3
- package/dist/templates/components/confirm/confirm-texts.ts +46 -0
- package/dist/templates/components/confirm/confirm.ts +190 -0
- package/dist/templates/components/confirm/index.ts +2 -0
- package/dist/templates/components/context-menu/context-menu.ts +378 -0
- package/dist/templates/components/context-menu/index.ts +14 -0
- package/dist/templates/components/dialog/dialog-texts.ts +41 -0
- package/dist/templates/components/dialog/dialog.ts +701 -0
- package/dist/templates/components/dialog/index.ts +18 -0
- package/dist/templates/components/dropdown-menu/dropdown-menu.ts +252 -0
- package/dist/templates/components/dropdown-menu/index.ts +14 -0
- package/dist/templates/components/form-field/form-error.ts +1 -1
- package/dist/templates/components/form-field/form-field.ts +3 -2
- package/dist/templates/components/input/input.variants.ts +6 -4
- package/dist/templates/components/menu/index.ts +15 -0
- package/dist/templates/components/menu/menu.ts +656 -0
- package/dist/templates/components/pagination/index.ts +2 -0
- package/dist/templates/components/pagination/pagination-page-size.ts +74 -0
- package/dist/templates/components/pagination/pagination-texts.ts +69 -0
- package/dist/templates/components/pagination/pagination.ts +443 -0
- package/dist/templates/components/popover/index.ts +12 -0
- package/dist/templates/components/popover/popover.ts +399 -0
- package/dist/templates/components/select/index.ts +12 -0
- package/dist/templates/components/select/select-texts.ts +62 -0
- package/dist/templates/components/select/select.ts +1085 -0
- package/dist/templates/components/separator/separator.variants.ts +10 -7
- package/dist/templates/components/skeleton/skeleton.ts +4 -1
- package/dist/templates/components/switch/switch.ts +6 -6
- package/dist/templates/components/tabs/index.ts +2 -0
- package/dist/templates/components/tabs/tabs.ts +426 -0
- package/dist/templates/components/textarea/textarea.variants.ts +14 -11
- package/dist/templates/components/toast/index.ts +3 -0
- package/dist/templates/components/toast/toast-config.ts +45 -0
- package/dist/templates/components/toast/toast-texts.ts +46 -0
- package/dist/templates/components/toast/toast.ts +1000 -0
- package/dist/templates/components/toolbar/index.ts +1 -0
- package/dist/templates/components/toolbar/toolbar.ts +377 -0
- package/dist/templates/components/tooltip/index.ts +7 -0
- package/dist/templates/components/tooltip/tooltip.ts +407 -0
- package/dist/templates/theme/provider.ts +62 -6
- package/dist/templates/theme/theme-base.css +307 -9
- package/dist/templates/theme/types.ts +278 -4
- package/dist/templates/utils/cn.ts +15 -1
- package/dist/templates/utils/control-styles.ts +2 -2
- package/dist/templates/utils/overlay.ts +140 -0
- package/dist/ui/index.js +80 -8
- package/dist/ui/schema.json +44 -0
- package/dist/utils/write-files.js +156 -59
- package/package.json +1 -1
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CdkConnectedOverlay,
|
|
3
|
+
type FlexibleConnectedPositionStrategyOrigin,
|
|
4
|
+
type FlexibleOverlayPopoverLocation,
|
|
5
|
+
type ScrollStrategy,
|
|
6
|
+
createCloseScrollStrategy,
|
|
7
|
+
createRepositionScrollStrategy,
|
|
8
|
+
} from '@angular/cdk/overlay';
|
|
9
|
+
import {
|
|
10
|
+
ChangeDetectionStrategy,
|
|
11
|
+
Component,
|
|
12
|
+
DOCUMENT,
|
|
13
|
+
DestroyRef,
|
|
14
|
+
Directive,
|
|
15
|
+
ElementRef,
|
|
16
|
+
Injector,
|
|
17
|
+
TemplateRef,
|
|
18
|
+
afterNextRender,
|
|
19
|
+
booleanAttribute,
|
|
20
|
+
computed,
|
|
21
|
+
contentChild,
|
|
22
|
+
effect,
|
|
23
|
+
inject,
|
|
24
|
+
input,
|
|
25
|
+
isDevMode,
|
|
26
|
+
model,
|
|
27
|
+
signal,
|
|
28
|
+
untracked,
|
|
29
|
+
viewChild,
|
|
30
|
+
} from '@angular/core';
|
|
31
|
+
import { MimiMenuItemsImports } from '@/components/ui/menu';
|
|
32
|
+
import { MimiMenuPanel } from '@/components/ui/menu/menu';
|
|
33
|
+
import {
|
|
34
|
+
MIMI_VIEWPORT_MARGIN,
|
|
35
|
+
mimiConnectedPositions,
|
|
36
|
+
mimiPointPositions,
|
|
37
|
+
mimiWaitForExit,
|
|
38
|
+
} from '@/components/ui/utils/overlay';
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
* Context Menu (docs/design/Mimi F2 Overlays.dc.html, «6 · Context Menu»): el menú aparece en el
|
|
42
|
+
* punto del clic derecho, a 8px como mínimo de los bordes. Mismos ítems, panel y tokens que el
|
|
43
|
+
* Dropdown Menu (`@/components/ui/menu`, prefijo `menu`, spec 13).
|
|
44
|
+
*
|
|
45
|
+
* Se abre con el evento `contextmenu` (clic derecho, Shift+F10, la tecla de menú, la pulsación
|
|
46
|
+
* larga de Android). Con el puntero, el origen del CDK es el punto; con el teclado, el elemento
|
|
47
|
+
* enfocado. aria no tiene un patrón de menú contextual: la apertura y la posición son de Mimi y
|
|
48
|
+
* del CDK; el teclado dentro del menú, de aria (MimiMenuPanel).
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/** Distancia al elemento enfocado cuando se abre con el teclado (la del Dropdown Menu). */
|
|
52
|
+
const OFFSET = 8;
|
|
53
|
+
/** Margen para atribuir un `contextmenu` a Shift+F10 o a la tecla de menú. */
|
|
54
|
+
const KEYBOARD_WINDOW = 1000;
|
|
55
|
+
|
|
56
|
+
let nextId = 0;
|
|
57
|
+
/** Eventos ya atendidos: con triggers anidados, abre el más interno. */
|
|
58
|
+
const handled = new WeakSet<Event>();
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Menú contextual: uno o varios triggers (`[mimiContextMenuTrigger]`) y el contenido en un
|
|
62
|
+
* `<ng-template mimiContextMenuContent let-dato>`. No dibuja nada propio (`display: contents`): su
|
|
63
|
+
* `class` y su `aria-label` van al panel.
|
|
64
|
+
*/
|
|
65
|
+
@Component({
|
|
66
|
+
selector: 'mimi-context-menu',
|
|
67
|
+
imports: [CdkConnectedOverlay, MimiMenuPanel],
|
|
68
|
+
template: `
|
|
69
|
+
<ng-content />
|
|
70
|
+
<ng-template
|
|
71
|
+
cdkConnectedOverlay
|
|
72
|
+
[cdkConnectedOverlayOrigin]="origin()!"
|
|
73
|
+
[cdkConnectedOverlayOpen]="rendered()"
|
|
74
|
+
[cdkConnectedOverlayPositions]="positions()"
|
|
75
|
+
[cdkConnectedOverlayViewportMargin]="viewportMargin"
|
|
76
|
+
[cdkConnectedOverlayDisableClose]="true"
|
|
77
|
+
[cdkConnectedOverlayUsePopover]="popoverLocation"
|
|
78
|
+
[cdkConnectedOverlayScrollStrategy]="scrollStrategy"
|
|
79
|
+
cdkConnectedOverlayTransformOriginOn="[data-mimi-overlay-panel]"
|
|
80
|
+
(detach)="onDetach()"
|
|
81
|
+
(overlayOutsideClick)="onOutsideClick($event)"
|
|
82
|
+
>
|
|
83
|
+
<!-- Se recrea en cada apertura: aria no tiene una API pública para reiniciar el menú. -->
|
|
84
|
+
@for (key of [openKey()]; track key) {
|
|
85
|
+
@if (content(); as content) {
|
|
86
|
+
<mimi-menu-panel
|
|
87
|
+
[template]="content.templateRef"
|
|
88
|
+
[context]="context()"
|
|
89
|
+
[menuId]="menuId"
|
|
90
|
+
[label]="ariaLabel() ?? ''"
|
|
91
|
+
[state]="state()"
|
|
92
|
+
[class]="userClass()"
|
|
93
|
+
(closeRequest)="close($event)"
|
|
94
|
+
/>
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
</ng-template>
|
|
98
|
+
`,
|
|
99
|
+
host: {
|
|
100
|
+
style: 'display: contents',
|
|
101
|
+
// El class y el aria-label son del panel: en el host no tendrían efecto o se heredarían.
|
|
102
|
+
'[attr.class]': 'null',
|
|
103
|
+
'[attr.aria-label]': 'null',
|
|
104
|
+
'[attr.data-state]': 'open() ? "open" : "closed"',
|
|
105
|
+
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
106
|
+
},
|
|
107
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
108
|
+
})
|
|
109
|
+
export class MimiContextMenu {
|
|
110
|
+
private readonly document = inject(DOCUMENT);
|
|
111
|
+
private readonly injector = inject(Injector);
|
|
112
|
+
|
|
113
|
+
/** Abierto o cerrado. Admite `[(open)]`; abierto desde el código, va junto al primer trigger. */
|
|
114
|
+
readonly open = model(false);
|
|
115
|
+
/** Nombre accesible del menú (`aria-label`). Hace falta: no hay un botón con texto que lo nombre. */
|
|
116
|
+
readonly ariaLabel = input<string>(undefined, { alias: 'aria-label' });
|
|
117
|
+
/** Sin menú propio: el clic derecho muestra el del navegador. */
|
|
118
|
+
readonly disabled = input(false, { transform: booleanAttribute });
|
|
119
|
+
/** Clases del panel, mezcladas con `cn()` (gana la del usuario). */
|
|
120
|
+
readonly userClass = input('', { alias: 'class' });
|
|
121
|
+
|
|
122
|
+
protected readonly content = contentChild(MimiContextMenuContent);
|
|
123
|
+
private readonly panel = viewChild(MimiMenuPanel);
|
|
124
|
+
|
|
125
|
+
readonly menuId = `mimi-context-menu-${nextId++}`;
|
|
126
|
+
|
|
127
|
+
/** El panel está en el DOM (también mientras dura la animación de salida). */
|
|
128
|
+
protected readonly rendered = signal(false);
|
|
129
|
+
protected readonly state = signal<'open' | 'closed'>('closed');
|
|
130
|
+
/** Cambia en cada apertura para recrear el menú (ítem activo y submenús desde cero). */
|
|
131
|
+
protected readonly openKey = signal(0);
|
|
132
|
+
protected readonly origin = signal<FlexibleConnectedPositionStrategyOrigin | null>(null);
|
|
133
|
+
/** Abierto con el puntero (en un punto) o con el teclado (junto al elemento). */
|
|
134
|
+
private readonly byPointer = signal(false);
|
|
135
|
+
/** Dato del trigger que lo abrió (`let-x` en el contenido). */
|
|
136
|
+
protected readonly context = signal<unknown>(undefined);
|
|
137
|
+
/** El trigger que lo abrió (lleva `data-state="open"`). */
|
|
138
|
+
readonly activeTrigger = signal<MimiContextMenuTrigger | null>(null);
|
|
139
|
+
|
|
140
|
+
protected readonly viewportMargin = MIMI_VIEWPORT_MARGIN;
|
|
141
|
+
/** El panel va dentro de este componente, después de los triggers (orden de Tab). */
|
|
142
|
+
protected readonly popoverLocation: FlexibleOverlayPopoverLocation = {
|
|
143
|
+
type: 'parent',
|
|
144
|
+
element: inject<ElementRef<HTMLElement>>(ElementRef).nativeElement,
|
|
145
|
+
};
|
|
146
|
+
protected readonly positions = computed(() =>
|
|
147
|
+
this.byPointer() ? mimiPointPositions() : mimiConnectedPositions('bottom', 'start', OFFSET),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Al hacer scroll: abierto con el puntero se cierra (como el menú del sistema: el punto no
|
|
152
|
+
* sigue al contenido); abierto con el teclado, sigue al elemento.
|
|
153
|
+
*/
|
|
154
|
+
protected readonly scrollStrategy: ScrollStrategy = (() => {
|
|
155
|
+
const close = createCloseScrollStrategy(this.injector);
|
|
156
|
+
const reposition = createRepositionScrollStrategy(this.injector);
|
|
157
|
+
return {
|
|
158
|
+
attach: (overlayRef) => {
|
|
159
|
+
close.attach(overlayRef);
|
|
160
|
+
reposition.attach(overlayRef);
|
|
161
|
+
},
|
|
162
|
+
enable: () => (this.byPointer() ? close : reposition).enable(),
|
|
163
|
+
disable: () => {
|
|
164
|
+
close.disable();
|
|
165
|
+
reposition.disable();
|
|
166
|
+
},
|
|
167
|
+
detach: () => {
|
|
168
|
+
close.detach?.();
|
|
169
|
+
reposition.detach?.();
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
})();
|
|
173
|
+
|
|
174
|
+
private readonly triggers = new Set<MimiContextMenuTrigger>();
|
|
175
|
+
/** Elemento con el foco antes de abrir: lo recupera al cerrar. */
|
|
176
|
+
private previousFocus: HTMLElement | null = null;
|
|
177
|
+
/** timeStamp del último Shift+F10 o tecla de menú. */
|
|
178
|
+
private keyAt: number | null = null;
|
|
179
|
+
private closing = 0;
|
|
180
|
+
private refocus = true;
|
|
181
|
+
private warnedLabel = false;
|
|
182
|
+
|
|
183
|
+
private readonly syncOpen = effect(() => {
|
|
184
|
+
const open = this.open();
|
|
185
|
+
untracked(() => (open ? this.show() : this.hide()));
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
private readonly syncDisabled = effect(() => {
|
|
189
|
+
if (this.disabled() && untracked(this.open)) this.close();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
/** Cierra. Con `refocus` (ítem elegido, Escape) el foco vuelve a donde estaba antes de abrir. */
|
|
193
|
+
close(refocus = true): void {
|
|
194
|
+
this.refocus = refocus;
|
|
195
|
+
this.open.set(false);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Lo usan los triggers. */
|
|
199
|
+
register(trigger: MimiContextMenuTrigger): void {
|
|
200
|
+
this.triggers.add(trigger);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
unregister(trigger: MimiContextMenuTrigger): void {
|
|
204
|
+
this.triggers.delete(trigger);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Shift+F10 o la tecla de menú: el `contextmenu` que sigue viene del teclado. */
|
|
208
|
+
markKeyboard(event: KeyboardEvent): void {
|
|
209
|
+
this.keyAt = event.timeStamp;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Abre desde un `contextmenu` en un trigger. */
|
|
213
|
+
openFrom(trigger: MimiContextMenuTrigger, event: MouseEvent): void {
|
|
214
|
+
if (this.disabled() || handled.has(event)) return;
|
|
215
|
+
handled.add(event);
|
|
216
|
+
event.preventDefault();
|
|
217
|
+
// Chrome marca con button -1 el contextmenu del teclado; los demás, por la tecla de antes.
|
|
218
|
+
const keyboard =
|
|
219
|
+
event.button === -1 ||
|
|
220
|
+
(this.keyAt !== null && event.timeStamp - this.keyAt < KEYBOARD_WINDOW);
|
|
221
|
+
this.keyAt = null;
|
|
222
|
+
if (!this.rendered()) {
|
|
223
|
+
const active = this.document.activeElement;
|
|
224
|
+
this.previousFocus = active instanceof HTMLElement ? active : null;
|
|
225
|
+
}
|
|
226
|
+
this.activeTrigger.set(trigger);
|
|
227
|
+
this.context.set(trigger.data());
|
|
228
|
+
this.byPointer.set(!keyboard);
|
|
229
|
+
const target = event.target instanceof Element ? event.target : trigger.element;
|
|
230
|
+
this.origin.set(keyboard ? target : { x: event.clientX, y: event.clientY });
|
|
231
|
+
if (this.rendered()) this.reopen();
|
|
232
|
+
this.open.set(true);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private reopen(): void {
|
|
236
|
+
this.openKey.update((key) => key + 1);
|
|
237
|
+
// El modo de scroll puede haber cambiado (puntero o teclado).
|
|
238
|
+
this.scrollStrategy.disable();
|
|
239
|
+
this.scrollStrategy.enable();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private show(): void {
|
|
243
|
+
this.closing++;
|
|
244
|
+
if (!this.origin()) {
|
|
245
|
+
// Abierto desde el código: junto al primer trigger.
|
|
246
|
+
const first = this.triggers.values().next().value;
|
|
247
|
+
if (!first) {
|
|
248
|
+
this.open.set(false);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
this.activeTrigger.set(first);
|
|
252
|
+
this.context.set(first.data());
|
|
253
|
+
this.byPointer.set(false);
|
|
254
|
+
this.origin.set(first.element);
|
|
255
|
+
}
|
|
256
|
+
this.warnMissingLabel();
|
|
257
|
+
this.state.set('open');
|
|
258
|
+
if (this.rendered()) this.openKey.update((key) => key + 1);
|
|
259
|
+
else this.rendered.set(true);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private hide(): void {
|
|
263
|
+
if (!this.rendered()) return;
|
|
264
|
+
this.state.set('closed');
|
|
265
|
+
const panel = this.panel()?.element;
|
|
266
|
+
const active = this.document.activeElement;
|
|
267
|
+
const focusInside = !active || active === this.document.body || panel?.contains(active);
|
|
268
|
+
if (this.refocus && focusInside) this.restoreFocus();
|
|
269
|
+
this.refocus = true;
|
|
270
|
+
const closing = ++this.closing;
|
|
271
|
+
afterNextRender(
|
|
272
|
+
{
|
|
273
|
+
read: () => {
|
|
274
|
+
void mimiWaitForExit(panel).then(() => {
|
|
275
|
+
if (closing !== this.closing) return;
|
|
276
|
+
this.rendered.set(false);
|
|
277
|
+
this.origin.set(null);
|
|
278
|
+
this.activeTrigger.set(null);
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
{ injector: this.injector },
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** El elemento que tenía el foco antes de abrir o, si ya no está, el trigger (si es enfocable). */
|
|
287
|
+
private restoreFocus(): void {
|
|
288
|
+
const previous = this.previousFocus;
|
|
289
|
+
const target =
|
|
290
|
+
previous?.isConnected && previous !== this.document.body
|
|
291
|
+
? previous
|
|
292
|
+
: this.activeTrigger()?.element;
|
|
293
|
+
target?.focus();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
protected onDetach(): void {
|
|
297
|
+
// El CDK lo cerró solo (scroll): el foco estaba en el menú, que ya no existe.
|
|
298
|
+
const wasOpen = this.open();
|
|
299
|
+
this.rendered.set(false);
|
|
300
|
+
this.origin.set(null);
|
|
301
|
+
this.activeTrigger.set(null);
|
|
302
|
+
if (!wasOpen) return;
|
|
303
|
+
this.open.set(false);
|
|
304
|
+
const active = this.document.activeElement;
|
|
305
|
+
if (!active || active === this.document.body) this.restoreFocus();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Clic fuera cierra; un clic derecho sobre otro trigger lo reabre ahí (lo hace el trigger). */
|
|
309
|
+
protected onOutsideClick(event: MouseEvent): void {
|
|
310
|
+
const target = event.target as Node | null;
|
|
311
|
+
if (
|
|
312
|
+
event.type === 'contextmenu' &&
|
|
313
|
+
[...this.triggers].some((t) => t.element.contains(target))
|
|
314
|
+
) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
this.close(false);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private warnMissingLabel(): void {
|
|
321
|
+
if (!isDevMode() || this.warnedLabel || this.ariaLabel()) return;
|
|
322
|
+
this.warnedLabel = true;
|
|
323
|
+
console.warn(
|
|
324
|
+
'[Mimi] Context Menu: el menú no tiene nombre accesible. Agrega aria-label="…" al <mimi-context-menu>.',
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Zona que abre el menú con clic derecho, Shift+F10 o la tecla de menú: cualquier elemento, uno o
|
|
331
|
+
* varios (por ejemplo, cada fila de una tabla). `mimiContextMenuTriggerData` llega al contenido.
|
|
332
|
+
*/
|
|
333
|
+
@Directive({
|
|
334
|
+
selector: '[mimiContextMenuTrigger]',
|
|
335
|
+
host: {
|
|
336
|
+
'[attr.data-state]': 'active() ? "open" : "closed"',
|
|
337
|
+
'(contextmenu)': 'menu.openFrom(this, $event)',
|
|
338
|
+
'(keydown)': 'onKeydown($event)',
|
|
339
|
+
},
|
|
340
|
+
})
|
|
341
|
+
export class MimiContextMenuTrigger {
|
|
342
|
+
protected readonly menu = inject(MimiContextMenu);
|
|
343
|
+
readonly element = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;
|
|
344
|
+
/** Dato para el contenido (`<ng-template mimiContextMenuContent let-fila>`). */
|
|
345
|
+
readonly data = input<unknown>(undefined, { alias: 'mimiContextMenuTriggerData' });
|
|
346
|
+
|
|
347
|
+
protected readonly active = computed(
|
|
348
|
+
() => this.menu.open() && this.menu.activeTrigger() === this,
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
constructor() {
|
|
352
|
+
this.menu.register(this);
|
|
353
|
+
inject(DestroyRef).onDestroy(() => this.menu.unregister(this));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
protected onKeydown(event: KeyboardEvent): void {
|
|
357
|
+
if ((event.key === 'F10' && event.shiftKey) || event.key === 'ContextMenu') {
|
|
358
|
+
this.menu.markKeyboard(event);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Contenido del menú: `<ng-template mimiContextMenuContent let-dato>`. Se crea en cada apertura y
|
|
365
|
+
* se destruye al cerrar. `dato` es el `mimiContextMenuTriggerData` del trigger (sin tipo: anótalo
|
|
366
|
+
* en tu componente si lo necesitas).
|
|
367
|
+
*/
|
|
368
|
+
@Directive({ selector: 'ng-template[mimiContextMenuContent]' })
|
|
369
|
+
export class MimiContextMenuContent {
|
|
370
|
+
readonly templateRef = inject(TemplateRef);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export const MimiContextMenuImports = [
|
|
374
|
+
MimiContextMenu,
|
|
375
|
+
MimiContextMenuTrigger,
|
|
376
|
+
MimiContextMenuContent,
|
|
377
|
+
...MimiMenuItemsImports,
|
|
378
|
+
] as const;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './context-menu';
|
|
2
|
+
// Los ítems y sus tipos, para importar todo desde @/components/ui/context-menu.
|
|
3
|
+
export {
|
|
4
|
+
MimiMenuCheckboxItem,
|
|
5
|
+
MimiMenuItem,
|
|
6
|
+
type MimiMenuItemTone,
|
|
7
|
+
MimiMenuLabel,
|
|
8
|
+
MimiMenuRadioGroup,
|
|
9
|
+
MimiMenuRadioItem,
|
|
10
|
+
MimiMenuSeparator,
|
|
11
|
+
MimiMenuSub,
|
|
12
|
+
MimiMenuSubContent,
|
|
13
|
+
MimiMenuSubTrigger,
|
|
14
|
+
} from '@/components/ui/menu';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { InjectionToken, type Provider } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Textos de Dialog, en archivo propio y sin @angular/cdk: así se pueden proveer en
|
|
5
|
+
* `app.config.ts` sin llevar el Dialog al chunk inicial.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Textos que Dialog anuncia. */
|
|
9
|
+
export interface MimiDialogText {
|
|
10
|
+
/** Nombre accesible del botón de cerrar (la X). */
|
|
11
|
+
close: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Textos predeterminados (inglés). */
|
|
15
|
+
export const MIMI_DIALOG_TEXT_EN: MimiDialogText = {
|
|
16
|
+
close: 'Close',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Textos en español: `provideMimiDialogText(MIMI_DIALOG_TEXT_ES)`. */
|
|
20
|
+
export const MIMI_DIALOG_TEXT_ES: MimiDialogText = {
|
|
21
|
+
close: 'Cerrar',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Textos activos. Sin `provideMimiDialogText`, los predeterminados en inglés. */
|
|
25
|
+
export const MIMI_DIALOG_TEXT = new InjectionToken<MimiDialogText>('MIMI_DIALOG_TEXT', {
|
|
26
|
+
providedIn: 'root',
|
|
27
|
+
factory: () => MIMI_DIALOG_TEXT_EN,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Cambia los textos de Dialog. Lo que no definas sigue en inglés:
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* provideMimiDialogText(MIMI_DIALOG_TEXT_ES)
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* Va en `app.config.ts` o en los `providers` de un componente o ruta.
|
|
38
|
+
*/
|
|
39
|
+
export function provideMimiDialogText(text: Partial<MimiDialogText>): Provider {
|
|
40
|
+
return { provide: MIMI_DIALOG_TEXT, useValue: { ...MIMI_DIALOG_TEXT_EN, ...text } };
|
|
41
|
+
}
|