@masterkeymaterial/ui 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/masterkeymaterial-ui.mjs +6457 -0
- package/fesm2022/masterkeymaterial-ui.mjs.map +1 -0
- package/package.json +14 -5
- package/types/masterkeymaterial-ui.d.ts +928 -0
- package/ng-package.json +0 -10
- package/src/elements/ui-button/ui-button.css +0 -229
- package/src/elements/ui-button/ui-button.html +0 -12
- package/src/elements/ui-button/ui-button.ts +0 -133
- package/src/elements/ui-check-box/ui-check-box.css +0 -66
- package/src/elements/ui-check-box/ui-check-box.html +0 -5
- package/src/elements/ui-check-box/ui-check-box.ts +0 -65
- package/src/elements/ui-chip/ui-chip.css +0 -24
- package/src/elements/ui-chip/ui-chip.html +0 -3
- package/src/elements/ui-chip/ui-chip.ts +0 -25
- package/src/elements/ui-drop-zone/ui-drop-zone.css +0 -91
- package/src/elements/ui-drop-zone/ui-drop-zone.html +0 -8
- package/src/elements/ui-drop-zone/ui-drop-zone.ts +0 -153
- package/src/elements/ui-file-list/ui-file-list.css +0 -43
- package/src/elements/ui-file-list/ui-file-list.html +0 -17
- package/src/elements/ui-file-list/ui-file-list.ts +0 -22
- package/src/elements/ui-list-errors/ui-list-errors.css +0 -30
- package/src/elements/ui-list-errors/ui-list-errors.html +0 -10
- package/src/elements/ui-list-errors/ui-list-errors.ts +0 -14
- package/src/elements/ui-loading/ui-loading.css +0 -13
- package/src/elements/ui-loading/ui-loading.html +0 -1
- package/src/elements/ui-loading/ui-loading.ts +0 -10
- package/src/elements/ui-menu/ui-menu.css +0 -69
- package/src/elements/ui-menu/ui-menu.html +0 -20
- package/src/elements/ui-menu/ui-menu.ts +0 -267
- package/src/elements/ui-procurar/ui-procurar.css +0 -48
- package/src/elements/ui-procurar/ui-procurar.html +0 -14
- package/src/elements/ui-procurar/ui-procurar.ts +0 -82
- package/src/elements/ui-progress/ui-progress.css +0 -0
- package/src/elements/ui-progress/ui-progress.html +0 -1
- package/src/elements/ui-progress/ui-progress.ts +0 -15
- package/src/elements/ui-select/ui-select.css +0 -211
- package/src/elements/ui-select/ui-select.html +0 -46
- package/src/elements/ui-select/ui-select.ts +0 -482
- package/src/elements/ui-slide/ui-slide.css +0 -75
- package/src/elements/ui-slide/ui-slide.html +0 -7
- package/src/elements/ui-slide/ui-slide.ts +0 -61
- package/src/fields/Base/BaseFieldsForm/BaseFieldsForm.component.ts +0 -113
- package/src/fields/Base/BaseFieldsValues/BaseFieldsValues.ts +0 -112
- package/src/fields/Formulario/Formulario.ts +0 -1056
- package/src/fields/Formulario/form-action/form-action.css +0 -98
- package/src/fields/Formulario/form-action/form-action.html +0 -75
- package/src/fields/Formulario/form-action/form-action.ts +0 -187
- package/src/fields/Formulario/form-controls/form-controls.css +0 -108
- package/src/fields/Formulario/form-controls/form-controls.html +0 -105
- package/src/fields/Formulario/form-controls/form-controls.ts +0 -122
- package/src/fields/Formulario/form-fase/form-fase.css +0 -84
- package/src/fields/Formulario/form-fase/form-fase.html +0 -24
- package/src/fields/Formulario/form-fase/form-fase.ts +0 -157
- package/src/fields/Formulario/form-filter/form-filter.css +0 -50
- package/src/fields/Formulario/form-filter/form-filter.html +0 -25
- package/src/fields/Formulario/form-filter/form-filter.ts +0 -53
- package/src/fields/Formulario/form-no-action/form-no-action.css +0 -32
- package/src/fields/Formulario/form-no-action/form-no-action.html +0 -12
- package/src/fields/Formulario/form-no-action/form-no-action.ts +0 -21
- package/src/fields/Formulario/formated-values/formated-values.css +0 -104
- package/src/fields/Formulario/formated-values/formated-values.html +0 -15
- package/src/fields/Formulario/formated-values/formated-values.ts +0 -186
- package/src/fields/Formulario/single-values/single-values.css +0 -88
- package/src/fields/Formulario/single-values/single-values.html +0 -11
- package/src/fields/Formulario/single-values/single-values.ts +0 -65
- package/src/fields/autocomplete/autocomplete.css +0 -94
- package/src/fields/autocomplete/autocomplete.html +0 -38
- package/src/fields/autocomplete/autocomplete.ts +0 -294
- package/src/fields/button/button.css +0 -7
- package/src/fields/button/button.html +0 -19
- package/src/fields/button/button.ts +0 -38
- package/src/fields/checkbox/checkbox.css +0 -27
- package/src/fields/checkbox/checkbox.html +0 -20
- package/src/fields/checkbox/checkbox.ts +0 -44
- package/src/fields/color/CirculoColorido/circulocolorido.css +0 -50
- package/src/fields/color/CirculoColorido/circulocolorido.html +0 -8
- package/src/fields/color/CirculoColorido/circulocolorido.ts +0 -24
- package/src/fields/color/color.css +0 -15
- package/src/fields/color/color.html +0 -24
- package/src/fields/color/color.ts +0 -47
- package/src/fields/date/date.html +0 -19
- package/src/fields/date/date.ts +0 -29
- package/src/fields/datetime/datetime.html +0 -19
- package/src/fields/datetime/datetime.ts +0 -29
- package/src/fields/display/display.css +0 -7
- package/src/fields/display/display.html +0 -20
- package/src/fields/display/display.ts +0 -43
- package/src/fields/editor/editor.css +0 -51
- package/src/fields/editor/editor.html +0 -37
- package/src/fields/editor/editor.ts +0 -218
- package/src/fields/email/email.html +0 -19
- package/src/fields/email/email.ts +0 -29
- package/src/fields/fields.css +0 -180
- package/src/fields/generic/generic.html +0 -3
- package/src/fields/generic/generic.ts +0 -91
- package/src/fields/hidden/hidden.html +0 -3
- package/src/fields/hidden/hidden.ts +0 -20
- package/src/fields/icon/icon.css +0 -19
- package/src/fields/icon/icon.html +0 -27
- package/src/fields/icon/icon.ts +0 -31
- package/src/fields/multifactor/multifactor.css +0 -21
- package/src/fields/multifactor/multifactor.html +0 -39
- package/src/fields/multifactor/multifactor.ts +0 -106
- package/src/fields/multikv/multikv.css +0 -43
- package/src/fields/multikv/multikv.html +0 -47
- package/src/fields/multikv/multikv.ts +0 -88
- package/src/fields/multitext/multitext.css +0 -36
- package/src/fields/multitext/multitext.html +0 -38
- package/src/fields/multitext/multitext.ts +0 -75
- package/src/fields/number/number.html +0 -20
- package/src/fields/number/number.ts +0 -35
- package/src/fields/password/password.html +0 -23
- package/src/fields/password/password.ts +0 -37
- package/src/fields/search/search.css +0 -0
- package/src/fields/search/search.html +0 -19
- package/src/fields/search/search.ts +0 -54
- package/src/fields/select/select.css +0 -15
- package/src/fields/select/select.html +0 -18
- package/src/fields/select/select.ts +0 -52
- package/src/fields/slide/slide.css +0 -27
- package/src/fields/slide/slide.html +0 -20
- package/src/fields/slide/slide.ts +0 -45
- package/src/fields/text/text.html +0 -19
- package/src/fields/text/text.ts +0 -30
- package/src/fields/textarea/textarea.css +0 -4
- package/src/fields/textarea/textarea.html +0 -20
- package/src/fields/textarea/textarea.ts +0 -31
- package/src/fields/time/time.html +0 -19
- package/src/fields/time/time.ts +0 -29
- package/src/fields/upload/upload.css +0 -24
- package/src/fields/upload/upload.html +0 -41
- package/src/fields/upload/upload.ts +0 -137
- package/src/interfaces/interfaces.ts +0 -61
- package/src/public-api.ts +0 -38
- package/src/util/ClassOf.pipe.ts +0 -11
- package/src/util/JsonColorido.pipe.ts +0 -11
- package/src/util/util.ts +0 -2151
- package/tsconfig.lib.json +0 -16
- package/tsconfig.lib.prod.json +0 -9
- package/tsconfig.spec.json +0 -13
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { Component, ElementRef, input, model, output, viewChild } from '@angular/core';
|
|
2
|
-
import { IDropOutput, IDropZoneEvent, IFileMetadata } from '../../interfaces/interfaces';
|
|
3
|
-
import { LibUtil, LOG } from '../../util/util';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'ui-drop-zone',
|
|
7
|
-
templateUrl: './ui-drop-zone.html',
|
|
8
|
-
styleUrl: './ui-drop-zone.css',
|
|
9
|
-
})
|
|
10
|
-
export class UiDropZone {
|
|
11
|
-
|
|
12
|
-
value = model<IDropOutput>();
|
|
13
|
-
disabled = input<boolean>();
|
|
14
|
-
sobrepor = input<boolean>();
|
|
15
|
-
clicked = output<Event>();
|
|
16
|
-
changed = output<IDropOutput>();
|
|
17
|
-
|
|
18
|
-
rippleEffect = viewChild('rippleEffect', { read: ElementRef });
|
|
19
|
-
ripple = viewChild('ripple', { read: ElementRef });
|
|
20
|
-
// dropzonearea = viewChild('dropzonearea', { read: ElementRef })
|
|
21
|
-
isDragging: boolean = false;
|
|
22
|
-
|
|
23
|
-
async onDropZone(tipo: IDropZoneEvent, ev: Event) {
|
|
24
|
-
ev.preventDefault();
|
|
25
|
-
ev.stopPropagation();
|
|
26
|
-
if (this.disabled()) return;
|
|
27
|
-
|
|
28
|
-
let target = ((ev as Event).target as HTMLDivElement);
|
|
29
|
-
if (tipo == "dragenter" && !this.disabled()) this.isDragging = true;
|
|
30
|
-
if (tipo == "dragleave" && !this.disabled()) this.isDragging = false;
|
|
31
|
-
if (tipo == "click" && target && target.classList?.contains("dropzone")) {
|
|
32
|
-
this.clicked.emit(ev);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async onDrop(ev: DragEvent) {
|
|
37
|
-
ev.preventDefault();
|
|
38
|
-
ev.stopPropagation();
|
|
39
|
-
this.isDragging = false;
|
|
40
|
-
if (this.disabled()) return;
|
|
41
|
-
|
|
42
|
-
const dataTransfer = ev.dataTransfer;
|
|
43
|
-
if (!dataTransfer) {
|
|
44
|
-
this.createRipple(ev as DragEvent, "red");
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
this.createRipple(ev as DragEvent);
|
|
48
|
-
|
|
49
|
-
const output: IDropOutput = {
|
|
50
|
-
timestamp: new Date().toISOString(),
|
|
51
|
-
types: (dataTransfer.types as any),
|
|
52
|
-
tipo: "unknown",
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// 1. Verificar se há arquivos
|
|
56
|
-
if (dataTransfer.files && dataTransfer.files.length > 0) {
|
|
57
|
-
|
|
58
|
-
output.data = ([] as IFileMetadata[]);
|
|
59
|
-
output.tipo = "file";
|
|
60
|
-
|
|
61
|
-
for await (let file of dataTransfer.files) {
|
|
62
|
-
let lastIndexOf = file.name.lastIndexOf(".");
|
|
63
|
-
if (lastIndexOf < 0) {
|
|
64
|
-
LOG(1, `❌ O arquivo ${file.name} está sem uma extensão.`);
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
let base64: string | null = await LibUtil.fileReader(file);
|
|
68
|
-
if (!base64) {
|
|
69
|
-
LOG(1, `❌ Ocorreu um erro com o arquivo ${file.name}.`);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
(output.data as IFileMetadata[]).push({
|
|
74
|
-
base64: base64,
|
|
75
|
-
name: file.name,
|
|
76
|
-
size: file.size,
|
|
77
|
-
extension: file.name.slice(lastIndexOf), // inclui o "."
|
|
78
|
-
type: file.type,
|
|
79
|
-
lastModifiedIso: new Date(file.lastModified).toISOString(),
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 2. Verificar se há texto
|
|
86
|
-
const text = dataTransfer.getData('text/plain');
|
|
87
|
-
if (text) {
|
|
88
|
-
output.data = text;
|
|
89
|
-
output.tipo = "text";
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// 3. Verificar se há HTML
|
|
93
|
-
const html = dataTransfer.getData('text/html');
|
|
94
|
-
if (html) {
|
|
95
|
-
output.data = html;
|
|
96
|
-
output.tipo = "html";
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 4. Verificar se há URL
|
|
100
|
-
const url = dataTransfer.getData('text/uri-list');
|
|
101
|
-
if (url) {
|
|
102
|
-
output.data = url;
|
|
103
|
-
output.tipo = "url";
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 5. Verificar se há dados JSON customizados
|
|
107
|
-
try {
|
|
108
|
-
const json = dataTransfer.getData('application/json');
|
|
109
|
-
if (json) {
|
|
110
|
-
output.data = JSON.parse(json);
|
|
111
|
-
output.tipo = "json";
|
|
112
|
-
}
|
|
113
|
-
} catch (e) {
|
|
114
|
-
// JSON inválido, ignorar
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// 6. Capturar todos os outros tipos de dados disponíveis
|
|
118
|
-
dataTransfer.types.forEach(type => {
|
|
119
|
-
if (!['text/plain', 'text/html', 'text/uri-list', 'application/json', 'Files'].includes(type)) {
|
|
120
|
-
const data = dataTransfer.getData(type);
|
|
121
|
-
if (data) {
|
|
122
|
-
output.data = data;
|
|
123
|
-
output.tipo = "unknown";
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
this.value.set(output);
|
|
129
|
-
this.changed.emit(output);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
createRipple(ev: DragEvent, classColor: string = "") {
|
|
133
|
-
const rippleEffect = ev.currentTarget ?? this.rippleEffect()?.nativeElement;
|
|
134
|
-
const ripple = this.ripple()?.nativeElement.cloneNode(true) as HTMLElement;
|
|
135
|
-
const rect = rippleEffect.getBoundingClientRect();
|
|
136
|
-
// Calcula a posição do clique relativa ao botão
|
|
137
|
-
const x = ev.clientX - rect.left;
|
|
138
|
-
const y = ev.clientY - rect.top;
|
|
139
|
-
// Define o tamanho do ripple (maior dimensão do botão * 2)
|
|
140
|
-
const size = Math.max(rect.width, rect.height) * 2;
|
|
141
|
-
ripple.style.width = ripple.style.height = size + 'px';
|
|
142
|
-
ripple.style.left = (x - size / 2) + 'px';
|
|
143
|
-
ripple.style.top = (y - size / 2) + 'px';
|
|
144
|
-
ripple.classList.add('ripple');
|
|
145
|
-
if (classColor) {
|
|
146
|
-
ripple.classList.add(classColor);
|
|
147
|
-
}
|
|
148
|
-
rippleEffect.appendChild(ripple);
|
|
149
|
-
ripple.addEventListener('animationend', () => {
|
|
150
|
-
ripple.remove();
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
:host {
|
|
2
|
-
width: 100%;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
.e-filelist-area {
|
|
6
|
-
display: flex;
|
|
7
|
-
flex-direction: column;
|
|
8
|
-
gap: 12px;
|
|
9
|
-
margin: 6px 0;
|
|
10
|
-
width: 100%;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.e-filelist-item {
|
|
14
|
-
display: grid;
|
|
15
|
-
grid-template-columns: 1fr auto 1fr;
|
|
16
|
-
gap: 5px;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.e-filelist-item-icon {
|
|
20
|
-
display: flex;
|
|
21
|
-
flex-direction: column;
|
|
22
|
-
justify-content: center;
|
|
23
|
-
align-items: center;
|
|
24
|
-
height: 100%;
|
|
25
|
-
aspect-ratio: 1 / 1;
|
|
26
|
-
border-radius: 6px;
|
|
27
|
-
font-size: 1.2rem;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.e-filelist-item-icon-ext {
|
|
31
|
-
display: flex;
|
|
32
|
-
justify-content: center;
|
|
33
|
-
align-items: center;
|
|
34
|
-
font-size: 0.7rem;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.e-filelist-item-text {
|
|
38
|
-
align-items: center;
|
|
39
|
-
align-content: center;
|
|
40
|
-
overflow: hidden;
|
|
41
|
-
text-overflow: ellipsis;
|
|
42
|
-
white-space: nowrap;
|
|
43
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<div class="e-filelist-area">
|
|
2
|
-
@for (file of filesList(); track $index) {
|
|
3
|
-
<div class="e-filelist-item">
|
|
4
|
-
<div class="e-filelist-item-icon">
|
|
5
|
-
<i class="bi bi-file-earmark-fill"></i>
|
|
6
|
-
<div class="e-filelist-item-icon-ext">{{ file.extension?.slice(1) }}</div>
|
|
7
|
-
</div>
|
|
8
|
-
<div class="e-filelist-item-text">{{ file.name }}</div>
|
|
9
|
-
<div class="e-filelist-action">
|
|
10
|
-
@if(!disabled()){
|
|
11
|
-
<ui-button tipo="icon" tema="error" [disabled]="disabled()" (click)="onDelete(file)" icone="bi bi-x-lg">
|
|
12
|
-
</ui-button>
|
|
13
|
-
}
|
|
14
|
-
</div>
|
|
15
|
-
</div>
|
|
16
|
-
}
|
|
17
|
-
</div>
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Component, input, output } from '@angular/core';
|
|
2
|
-
import { UiButton } from "../ui-button/ui-button";
|
|
3
|
-
import { IFileMetadata } from '../../interfaces/interfaces';
|
|
4
|
-
|
|
5
|
-
@Component({
|
|
6
|
-
selector: 'ui-file-list',
|
|
7
|
-
templateUrl: './ui-file-list.html',
|
|
8
|
-
styleUrl: './ui-file-list.css',
|
|
9
|
-
imports: [UiButton]
|
|
10
|
-
})
|
|
11
|
-
export class UiFileList {
|
|
12
|
-
|
|
13
|
-
disabled = input<boolean>();
|
|
14
|
-
filesList = input<IFileMetadata[]>();
|
|
15
|
-
remove = output<IFileMetadata>();
|
|
16
|
-
|
|
17
|
-
onDelete(file: IFileMetadata) {
|
|
18
|
-
if (this.disabled()) return;
|
|
19
|
-
this.remove.emit(file);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
:host {
|
|
2
|
-
display: contents;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
.erroArea {
|
|
6
|
-
background-color: transparent;
|
|
7
|
-
border-radius: var(--MasterRadius);
|
|
8
|
-
font-size: 12px;
|
|
9
|
-
padding: 0px 8px 2px 8px;
|
|
10
|
-
display: flex;
|
|
11
|
-
flex-wrap: wrap;
|
|
12
|
-
justify-content: flex-end;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.erroArea .erroText {
|
|
16
|
-
color: var(--sys-error);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.erroArea .dicaText {
|
|
20
|
-
color: var(--sys-info);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.erroArea.hide {
|
|
24
|
-
display: none;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
span {
|
|
28
|
-
display: block;
|
|
29
|
-
margin-left: 3px;
|
|
30
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
@if(show()){
|
|
2
|
-
<div class="erroArea" [class.hide]="(!errors() || errors()?.length === 0) && (!dica())">
|
|
3
|
-
@for (erro of errors(); track $index) {
|
|
4
|
-
<span class="erroText">{{ erro ?? 'Erro desconhecido' }}</span>
|
|
5
|
-
}
|
|
6
|
-
@if(dica()){
|
|
7
|
-
<span class="dicaText">{{ dica() ?? '' }}</span>
|
|
8
|
-
}
|
|
9
|
-
</div>
|
|
10
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { Component, input } from '@angular/core';
|
|
2
|
-
|
|
3
|
-
@Component({
|
|
4
|
-
selector: 'ui-list-errors',
|
|
5
|
-
templateUrl: './ui-list-errors.html',
|
|
6
|
-
styleUrl: './ui-list-errors.css',
|
|
7
|
-
})
|
|
8
|
-
export class UiListErrors {
|
|
9
|
-
|
|
10
|
-
show = input.required<boolean | undefined>();
|
|
11
|
-
|
|
12
|
-
errors = input.required<(string | undefined)[] | undefined>();
|
|
13
|
-
dica = input.required<string | undefined>();
|
|
14
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<i class="fa fa-spinner"></i>
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
.e-menu-area {
|
|
2
|
-
background-color: var(--sys-card);
|
|
3
|
-
border-radius: 4px;
|
|
4
|
-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
5
|
-
padding: 8px 0;
|
|
6
|
-
display: flex;
|
|
7
|
-
flex-direction: column;
|
|
8
|
-
gap: var(--ui-gap, 4px);
|
|
9
|
-
user-select: none;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.e-menu-trigger {
|
|
13
|
-
flex: 1;
|
|
14
|
-
display: block;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.e-menu-overlay {
|
|
18
|
-
position: fixed;
|
|
19
|
-
top: 0;
|
|
20
|
-
left: 0;
|
|
21
|
-
width: 100%;
|
|
22
|
-
height: 100vh;
|
|
23
|
-
z-index: 1001;
|
|
24
|
-
background-color: transparent;
|
|
25
|
-
pointer-events: none;
|
|
26
|
-
transition: background-color 0.2s ease-in-out;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.e-menu-overlay.open {
|
|
30
|
-
background-color: rgba(0, 0, 0, 0.25);
|
|
31
|
-
pointer-events: auto;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.e-menu-container {
|
|
35
|
-
width: max-content;
|
|
36
|
-
position: fixed;
|
|
37
|
-
z-index: 2002;
|
|
38
|
-
display: flex;
|
|
39
|
-
flex-direction: column;
|
|
40
|
-
overflow: hidden;
|
|
41
|
-
border-radius: 10px;
|
|
42
|
-
transform: translateZ(0);
|
|
43
|
-
will-change: transform;
|
|
44
|
-
|
|
45
|
-
transform-origin: top left;
|
|
46
|
-
transform: scale(0.8) translate(0, -10%) rotateX(90deg) skew(15deg);
|
|
47
|
-
|
|
48
|
-
opacity: 0;
|
|
49
|
-
visibility: hidden;
|
|
50
|
-
/* transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out, transform 0.2s ease-in-out; */
|
|
51
|
-
transition: all 0.15s ease-in-out;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.e-menu-overlay.open .e-menu-container {
|
|
55
|
-
color: var(--sys-on-card);
|
|
56
|
-
background: var(--sys-card);
|
|
57
|
-
outline: 1px solid var(--sys-outline);
|
|
58
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
59
|
-
visibility: visible;
|
|
60
|
-
opacity: 1;
|
|
61
|
-
transform: scale(1);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.e-menu-overlay.open .e-menu-container.smallScreen {
|
|
65
|
-
width: 90vw;
|
|
66
|
-
left: 5vw !important;
|
|
67
|
-
/* 170 == 300 List + 40 Search / 2 */
|
|
68
|
-
top: calc(50vh - 170px) !important;
|
|
69
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<div #menuTrigger class="e-menu-trigger" (click)="onClickSlotBotao($event)" (keyup.Enter)="onOpenOrCloseOptions()"
|
|
2
|
-
(keydown.ArrowDown)="onArrowDown($event)" (keyup.ArrowUp)="onArrowUp($event)" (keyup.Escape)="closeOverlay()"
|
|
3
|
-
[id]="'tr'+id()" [class.open]="isOpened()">
|
|
4
|
-
<ng-content select="[slot='botao']">
|
|
5
|
-
<ui-button tipo="icon" tema="primary" [icone]="icone()"></ui-button>
|
|
6
|
-
</ng-content>
|
|
7
|
-
</div>
|
|
8
|
-
|
|
9
|
-
<div #menuOverlay class="e-menu-overlay" [class.open]="isOpened()" (click)="onClickOverlay()"
|
|
10
|
-
(keydown.ArrowDown)="onArrowDown($event)" (keyup.ArrowUp)="onArrowUp($event)" [style.zIndex]="zIndex()"
|
|
11
|
-
[id]="'ov'+id()">
|
|
12
|
-
|
|
13
|
-
<div #menuContainer class="e-menu-container" [class.smallScreen]="isSmallScreen()" [style.left]="menuPosition().left"
|
|
14
|
-
[style.top]="menuPosition().top" (click)="onClickContainer($event)">
|
|
15
|
-
<div [style.minWidth.px]="optionMinWidth()" class="e-menu-area">
|
|
16
|
-
<ng-content></ng-content>
|
|
17
|
-
</div>
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
|
-
</div>
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import { AfterViewInit, Component, effect, ElementRef, HostListener, inject, input, model, OnDestroy, OnInit, signal, viewChild } from '@angular/core';
|
|
2
|
-
import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';
|
|
3
|
-
import { UiButton } from "../ui-button/ui-button";
|
|
4
|
-
import { LibUtil, LOG } from '../../util/util';
|
|
5
|
-
import { IPosicoesFloating } from '../../interfaces/interfaces';
|
|
6
|
-
|
|
7
|
-
@Component({
|
|
8
|
-
selector: 'ui-menu',
|
|
9
|
-
templateUrl: './ui-menu.html',
|
|
10
|
-
styleUrl: './ui-menu.css',
|
|
11
|
-
imports: [UiButton],
|
|
12
|
-
host: { '[class.opened]': 'isOpened()' }
|
|
13
|
-
})
|
|
14
|
-
export class UiMenu implements OnDestroy, OnInit, AfterViewInit {
|
|
15
|
-
// UTILIZE:
|
|
16
|
-
// 'slot:botao' para colocar o botão que irá abrir o menu, ou utilize o botão padrão (três pontos verticais)
|
|
17
|
-
// O demais elementos dentro do <ui-menu> serão considerados opções do menu, e serão exibidos dentro do overlay quando o menu for aberto
|
|
18
|
-
|
|
19
|
-
elementRef: ElementRef = inject(ElementRef);
|
|
20
|
-
|
|
21
|
-
readonly disabled = input<boolean>();
|
|
22
|
-
readonly icone = input<string | undefined | null>("bi bi-three-dots-vertical");
|
|
23
|
-
autoCloseInCascade = model<boolean>(true);
|
|
24
|
-
|
|
25
|
-
posicao = model<IPosicoesFloating & string>('right-start');
|
|
26
|
-
isOpened = model(false);
|
|
27
|
-
id = signal(LibUtil.uuid().split('-')[0]);
|
|
28
|
-
internalId = signal<{ id: string | undefined; changed: boolean } | undefined>({ id: undefined, changed: false });
|
|
29
|
-
isSmallScreen = signal(window.innerWidth < 500);
|
|
30
|
-
optionMinWidth = signal<number>(0);
|
|
31
|
-
menuPosition = signal<{ top: string; left: string }>({ top: '0px', left: '0px' });
|
|
32
|
-
zIndex = signal<string>('999');
|
|
33
|
-
closeOverlayObserver = new MutationObserver((mutations) => {
|
|
34
|
-
let hasOpenClass = false;
|
|
35
|
-
for (const mutation of mutations) {
|
|
36
|
-
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
|
37
|
-
const target = mutation.target as HTMLElement;
|
|
38
|
-
if (target.classList.contains('open')) {
|
|
39
|
-
hasOpenClass = true;
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (!hasOpenClass) {
|
|
45
|
-
// LOG(1, "%cObserver detectou overlay fechado:", 'color: lightcoral;', mutations?.[0]);
|
|
46
|
-
this.closeOverlay(true); // Callback para fechar o overlay quando a mutação indicar que perdeu a classe 'open'
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
readonly menuTrigger = viewChild<ElementRef>('menuTrigger');
|
|
52
|
-
readonly menuOverlay = viewChild<ElementRef>('menuOverlay');
|
|
53
|
-
readonly menuContainer = viewChild<ElementRef>('menuContainer');
|
|
54
|
-
|
|
55
|
-
constructor() {
|
|
56
|
-
effect(() => {
|
|
57
|
-
|
|
58
|
-
const eTrigger = this.menuTrigger()?.nativeElement as HTMLElement;
|
|
59
|
-
if (!eTrigger) return;
|
|
60
|
-
|
|
61
|
-
if (!this.isOpened()) return;
|
|
62
|
-
|
|
63
|
-
const cleanupAutoUpdate = autoUpdate(
|
|
64
|
-
eTrigger,
|
|
65
|
-
this.menuContainer()?.nativeElement,
|
|
66
|
-
() => this.updatePosition()
|
|
67
|
-
);
|
|
68
|
-
return () => {
|
|
69
|
-
cleanupAutoUpdate();
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Atualiza posição quando o menu abre
|
|
75
|
-
effect(() => {
|
|
76
|
-
if (this.isOpened()) {
|
|
77
|
-
setTimeout(() => {
|
|
78
|
-
this.updatePosition();
|
|
79
|
-
}, 1);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
effect(() => {
|
|
83
|
-
let internalId = this.internalId();
|
|
84
|
-
if (internalId?.id === undefined && internalId?.changed === true) {
|
|
85
|
-
// LOG(1, "%cAlterado Internal:", 'color: lightcoral;', internalId, " - Menu ID:", this.id());
|
|
86
|
-
this.closeOverlay();
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
ngOnInit() {
|
|
91
|
-
// Inicializa o zIndex com base no valor armazenado no sessionStorage
|
|
92
|
-
this.zIndex.set(sessionStorage.getItem(`ui-menu-index`) ? (Number(sessionStorage.getItem(`ui-menu-index`)) + 1).toString() : '1001');
|
|
93
|
-
sessionStorage.setItem(`ui-menu-index`, this.zIndex());
|
|
94
|
-
}
|
|
95
|
-
ngOnDestroy() {
|
|
96
|
-
if (this.isOpened() == true) this.closeOverlay();
|
|
97
|
-
this.closeOverlayObserver.disconnect();
|
|
98
|
-
// this.cleanupScrollListeners();
|
|
99
|
-
}
|
|
100
|
-
ngAfterViewInit() {
|
|
101
|
-
this.checkScreenSize();
|
|
102
|
-
this.setOverlaySizeToSizeViewport();
|
|
103
|
-
|
|
104
|
-
this.updatePosition();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Abre ou fecha o overlay de opções
|
|
108
|
-
onOpenOrCloseOptions() {
|
|
109
|
-
if (!this.disabled()) {
|
|
110
|
-
if (this.isOpened()) {
|
|
111
|
-
this.closeOverlay();
|
|
112
|
-
} else {
|
|
113
|
-
this.openOverlay();
|
|
114
|
-
}
|
|
115
|
-
this.menuTrigger()?.nativeElement.focus();
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
// Abre ou fecha o overlay de opções
|
|
119
|
-
onClickSlotBotao(event: Event) {
|
|
120
|
-
if (!this.disabled()) {
|
|
121
|
-
this.openOverlay();
|
|
122
|
-
this.menuTrigger()?.nativeElement.focus();
|
|
123
|
-
}
|
|
124
|
-
// LOG(1, "%cAbriu ID:", 'color: lightgreen;', this.id());
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
onClickOverlay() {
|
|
128
|
-
// LOG(1, "%cFECHOU OVERLAY ID:", 'color: lightblue;', this.id());
|
|
129
|
-
this.closeOverlay();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
onClickContainer(event: Event) {
|
|
134
|
-
event.stopPropagation();
|
|
135
|
-
let closestOverlay = (event.target as HTMLElement).closest('.e-menu-overlay');
|
|
136
|
-
let overlayId = closestOverlay?.id?.replaceAll('ov', '');
|
|
137
|
-
let closestTrigger = (event.target as HTMLElement).closest('.e-menu-trigger');
|
|
138
|
-
let triggerId = closestTrigger?.id?.replaceAll('tr', '');
|
|
139
|
-
this.internalId.set({ id: triggerId, changed: true });
|
|
140
|
-
|
|
141
|
-
// LOG(1, 'Triger ID:', triggerId, ' - Overlay ID:', overlayId);
|
|
142
|
-
|
|
143
|
-
if (overlayId == this.id() && triggerId != null && triggerId != this.id()) {
|
|
144
|
-
// Um menu interno foi clicado, não fecha o overlay
|
|
145
|
-
// O atual nível de menu irá observar o elemento closestTrigger e só irá fechar o overlay quando o elemento trigger fechar.
|
|
146
|
-
this.closeOverlayObserver.observe(closestTrigger as HTMLElement, { attributes: true, attributeFilter: ['class'] });
|
|
147
|
-
} else {
|
|
148
|
-
// Clicou no container, fecha o overlay
|
|
149
|
-
this.closeOverlay();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Fecha o overlay de opções
|
|
155
|
-
protected closeOverlay(fromCascade: boolean = false) {
|
|
156
|
-
if (fromCascade && !this.autoCloseInCascade()) return;
|
|
157
|
-
|
|
158
|
-
this.closeOverlayObserver.disconnect();
|
|
159
|
-
setTimeout(() => {
|
|
160
|
-
this.isOpened.set(false);
|
|
161
|
-
}, 2)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Abre o overlay de opções
|
|
165
|
-
protected openOverlay() {
|
|
166
|
-
this.updatePosition();
|
|
167
|
-
setTimeout(() => {
|
|
168
|
-
this.isOpened.set(true);
|
|
169
|
-
}, 2)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Atualiza a posição do menu de opções
|
|
173
|
-
private async updatePosition() {
|
|
174
|
-
if (this.isSmallScreen()) return;
|
|
175
|
-
|
|
176
|
-
const trigger = this.menuTrigger()?.nativeElement;
|
|
177
|
-
const menuContainer = this.menuContainer()?.nativeElement;
|
|
178
|
-
if (!trigger || !menuContainer) return;
|
|
179
|
-
|
|
180
|
-
const { x, y } = await computePosition(trigger, menuContainer, {
|
|
181
|
-
placement: this.posicao(),
|
|
182
|
-
middleware: [
|
|
183
|
-
offset(2),
|
|
184
|
-
flip({
|
|
185
|
-
fallbackPlacements: ['left-start', 'bottom-start', 'top-start', 'bottom-end', 'top-end'],
|
|
186
|
-
}),
|
|
187
|
-
shift({ padding: 10 }),
|
|
188
|
-
],
|
|
189
|
-
});
|
|
190
|
-
this.menuPosition.set({
|
|
191
|
-
left: `${x}px`,
|
|
192
|
-
top: `${y}px`
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
setOverlaySizeToSizeViewport() {
|
|
197
|
-
// if (!this.isOpened()) return;
|
|
198
|
-
|
|
199
|
-
// Obtem o tamanho da largura da viewport
|
|
200
|
-
const viewportWidth = window.innerWidth;
|
|
201
|
-
const parcialViewport = viewportWidth * 0.92;
|
|
202
|
-
|
|
203
|
-
const overlay = this.menuOverlay()?.nativeElement as HTMLElement;
|
|
204
|
-
if (overlay) {
|
|
205
|
-
let currentElement = overlay.parentElement as HTMLElement | null;
|
|
206
|
-
while (currentElement) {
|
|
207
|
-
let larguraOverlay = overlay.clientWidth;
|
|
208
|
-
|
|
209
|
-
// Se chegar no body, para (para evitar mover o overlay para fora do body)
|
|
210
|
-
if (currentElement.tagName.toLowerCase() === "body") break;
|
|
211
|
-
|
|
212
|
-
// Move o overlay para o próximo nível acima
|
|
213
|
-
currentElement.appendChild(overlay);
|
|
214
|
-
|
|
215
|
-
if (larguraOverlay >= parcialViewport) {
|
|
216
|
-
currentElement = null;
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
currentElement = currentElement.parentElement as HTMLElement;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
onChangeMediaQuery(e: MediaQueryListEvent | MediaQueryList) {
|
|
227
|
-
this.isSmallScreen.set(e.matches);
|
|
228
|
-
const eTrigger = this.menuTrigger()?.nativeElement as HTMLElement;
|
|
229
|
-
if (eTrigger) {
|
|
230
|
-
if (this.isSmallScreen()) {
|
|
231
|
-
this.optionMinWidth.set(0);
|
|
232
|
-
} else {
|
|
233
|
-
this.optionMinWidth.set(eTrigger.offsetWidth - 4);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
checkScreenSize() {
|
|
239
|
-
const mediaQuery = window.matchMedia('(max-width: 500px)');
|
|
240
|
-
this.onChangeMediaQuery(mediaQuery);
|
|
241
|
-
mediaQuery.addEventListener('change', this.onChangeMediaQuery.bind(this));
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
onArrowUp(event: Event) {
|
|
245
|
-
event.preventDefault();
|
|
246
|
-
// this.moveFocoPara('cima', event);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
onArrowDown(event: Event) {
|
|
250
|
-
event.preventDefault();
|
|
251
|
-
// this.moveFocoPara('baixo', event);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// moveFocoPara(direcao: 'cima' | 'baixo', event: Event) {
|
|
255
|
-
// this.openOverlay();
|
|
256
|
-
// LOG(1, `Moverá para ${direcao} - Event:`, event);
|
|
257
|
-
// const container = this.menuContainer()?.nativeElement as HTMLElement;
|
|
258
|
-
// if (!container) return;
|
|
259
|
-
|
|
260
|
-
// const focusableElements = Array.from(container.querySelectorAll('.e-menu-area>*')).filter(el => el instanceof HTMLElement && !el.hasAttribute('disabled')) as HTMLElement[];
|
|
261
|
-
// if (focusableElements.length === 0) return;
|
|
262
|
-
|
|
263
|
-
// //
|
|
264
|
-
|
|
265
|
-
// }
|
|
266
|
-
|
|
267
|
-
}
|