@block-auth.io/angular 0.1.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/.turbo/turbo-build.log +37 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +82 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +10 -0
- package/ng-package.json +6 -0
- package/package.json +50 -0
- package/scripts/css-to-ts.mjs +14 -0
- package/src/lib/__tests__/integration.test.ts +45 -0
- package/src/lib/block-auth.service.test.ts +69 -0
- package/src/lib/block-auth.service.ts +81 -0
- package/src/lib/components/btns/btn-base-provider.component.test.ts +22 -0
- package/src/lib/components/btns/btn-base-provider.component.ts +55 -0
- package/src/lib/components/btns/btn-block-auth.component.test.ts +79 -0
- package/src/lib/components/btns/btn-block-auth.component.ts +104 -0
- package/src/lib/components/btns/btn-gmail.component.test.ts +16 -0
- package/src/lib/components/btns/btn-gmail.component.ts +41 -0
- package/src/lib/components/btns/btn-google-base.component.test.ts +16 -0
- package/src/lib/components/btns/btn-google-base.component.ts +73 -0
- package/src/lib/components/btns/btn-metamask.component.test.ts +20 -0
- package/src/lib/components/btns/btn-metamask.component.ts +98 -0
- package/src/lib/components/btns/btn-microsoft.component.test.ts +16 -0
- package/src/lib/components/btns/btn-microsoft.component.ts +77 -0
- package/src/lib/components/btns/btn-phantom.component.test.ts +20 -0
- package/src/lib/components/btns/btn-phantom.component.ts +93 -0
- package/src/lib/components/btns/btn-signin.component.test.ts +55 -0
- package/src/lib/components/btns/btn-signin.component.ts +108 -0
- package/src/lib/components/btns/btn-wallet-connect.component.test.ts +20 -0
- package/src/lib/components/btns/btn-wallet-connect.component.ts +109 -0
- package/src/lib/components/btns/index.ts +11 -0
- package/src/lib/components/contents/content-signin.component.test.ts +33 -0
- package/src/lib/components/contents/content-signin.component.ts +68 -0
- package/src/lib/components/contents/content-signup.component.test.ts +78 -0
- package/src/lib/components/contents/content-signup.component.ts +225 -0
- package/src/lib/components/dds/change-account.component.test.ts +66 -0
- package/src/lib/components/dds/change-account.component.ts +112 -0
- package/src/lib/components/dds/dropdown-profile-btn.component.test.ts +66 -0
- package/src/lib/components/dds/dropdown-profile-btn.component.ts +185 -0
- package/src/lib/components/dds/dropdown-profile-item.component.test.ts +75 -0
- package/src/lib/components/dds/dropdown-profile-item.component.ts +74 -0
- package/src/lib/components/dds/dropdown-profile.component.test.ts +98 -0
- package/src/lib/components/dds/dropdown-profile.component.ts +258 -0
- package/src/lib/components/flow-block-auth.component.test.ts +93 -0
- package/src/lib/components/flow-block-auth.component.ts +226 -0
- package/src/lib/components/modals/modal-overlay.component.test.ts +81 -0
- package/src/lib/components/modals/modal-overlay.component.ts +43 -0
- package/src/lib/components/modals/modal-restoration-expired.component.test.ts +71 -0
- package/src/lib/components/modals/modal-restoration-expired.component.ts +83 -0
- package/src/lib/components/modals/modal-restore.component.test.ts +74 -0
- package/src/lib/components/modals/modal-restore.component.ts +104 -0
- package/src/lib/components/modals/modal-signin.component.test.ts +66 -0
- package/src/lib/components/modals/modal-signin.component.ts +44 -0
- package/src/lib/components/modals/modal-signup.component.test.ts +56 -0
- package/src/lib/components/modals/modal-signup.component.ts +40 -0
- package/src/lib/components/sections/footer-section.component.test.ts +19 -0
- package/src/lib/components/sections/footer-section.component.ts +49 -0
- package/src/lib/components/sections/internals/providers-buttons.component.test.ts +27 -0
- package/src/lib/components/sections/internals/providers-buttons.component.ts +290 -0
- package/src/lib/components/sections/internals/providers-filters.component.test.ts +25 -0
- package/src/lib/components/sections/internals/providers-filters.component.ts +139 -0
- package/src/lib/components/sections/internals/selected-button.component.test.ts +36 -0
- package/src/lib/components/sections/internals/selected-button.component.ts +92 -0
- package/src/lib/components/sections/login-section.component.test.ts +49 -0
- package/src/lib/components/sections/login-section.component.ts +120 -0
- package/src/lib/components/sections/providers-section.component.test.ts +22 -0
- package/src/lib/components/sections/providers-section.component.ts +187 -0
- package/src/lib/components/txns/internals/permissions-required-modal.component.test.ts +14 -0
- package/src/lib/components/txns/internals/permissions-required-modal.component.ts +35 -0
- package/src/lib/components/txns/internals/solicited-data-modal.component.test.ts +72 -0
- package/src/lib/components/txns/internals/solicited-data-modal.component.ts +187 -0
- package/src/lib/components/txns/internals/txn-error.component.test.ts +100 -0
- package/src/lib/components/txns/internals/txn-error.component.ts +121 -0
- package/src/lib/components/txns/internals/txn-type.component.test.ts +52 -0
- package/src/lib/components/txns/internals/txn-type.component.ts +51 -0
- package/src/lib/components/txns/internals/txn.component.test.ts +188 -0
- package/src/lib/components/txns/internals/txn.component.ts +298 -0
- package/src/lib/components/txns/txns-overview.component.test.ts +107 -0
- package/src/lib/components/txns/txns-overview.component.ts +229 -0
- package/src/lib/icons/ai-outline-disconnect.component.ts +21 -0
- package/src/lib/icons/bi-logo-gmail.component.ts +21 -0
- package/src/lib/icons/cg-profile.component.ts +31 -0
- package/src/lib/icons/check-icon.component.ts +27 -0
- package/src/lib/icons/ci-clock2.component.ts +31 -0
- package/src/lib/icons/fa-arrow-right.component.ts +21 -0
- package/src/lib/icons/fa-times.component.ts +21 -0
- package/src/lib/icons/fa-undo.component.ts +21 -0
- package/src/lib/icons/icons.test.ts +25 -0
- package/src/lib/icons/index.ts +22 -0
- package/src/lib/icons/io-ios-arrow-back.component.ts +21 -0
- package/src/lib/icons/io-ios-arrow-forward.component.ts +21 -0
- package/src/lib/icons/io-md-arrow-back.component.ts +19 -0
- package/src/lib/icons/io-md-arrow-forward.component.ts +19 -0
- package/src/lib/icons/pi-warning-fill.component.ts +21 -0
- package/src/lib/icons/tb-arrows-exchange2.component.ts +30 -0
- package/src/lib/icons/tb-copy-check-filled.component.ts +21 -0
- package/src/lib/icons/tb-copy.component.ts +34 -0
- package/src/lib/icons/ti-vendor-microsoft.component.ts +21 -0
- package/src/lib/inject-store.test.ts +26 -0
- package/src/lib/inject-store.ts +16 -0
- package/src/lib/primitives/arrow-icon.component.test.ts +12 -0
- package/src/lib/primitives/arrow-icon.component.ts +38 -0
- package/src/lib/primitives/btn-arrow-dir.component.test.ts +22 -0
- package/src/lib/primitives/btn-arrow-dir.component.ts +65 -0
- package/src/lib/primitives/btn-base.component.test.ts +38 -0
- package/src/lib/primitives/btn-base.component.ts +140 -0
- package/src/lib/primitives/divider-base.component.test.ts +12 -0
- package/src/lib/primitives/divider-base.component.ts +74 -0
- package/src/lib/primitives/dropdown-arrow.component.test.ts +12 -0
- package/src/lib/primitives/dropdown-arrow.component.ts +46 -0
- package/src/lib/primitives/dropdown-base.component.test.ts +49 -0
- package/src/lib/primitives/dropdown-base.component.ts +329 -0
- package/src/lib/primitives/helpers.ts +151 -0
- package/src/lib/primitives/horizontal-loader.component.test.ts +19 -0
- package/src/lib/primitives/horizontal-loader.component.ts +70 -0
- package/src/lib/primitives/icon-base.component.test.ts +19 -0
- package/src/lib/primitives/icon-base.component.ts +58 -0
- package/src/lib/primitives/img-loader.component.test.ts +12 -0
- package/src/lib/primitives/img-loader.component.ts +118 -0
- package/src/lib/primitives/index.ts +29 -0
- package/src/lib/primitives/input-base.component.test.ts +20 -0
- package/src/lib/primitives/input-base.component.ts +210 -0
- package/src/lib/primitives/lifetime-slider-base.component.test.ts +88 -0
- package/src/lib/primitives/lifetime-slider-base.component.ts +75 -0
- package/src/lib/primitives/oval-spinner.component.test.ts +9 -0
- package/src/lib/primitives/oval-spinner.component.ts +158 -0
- package/src/lib/primitives/pickers/dropdown-picker-animated-base.component.ts +220 -0
- package/src/lib/primitives/pickers/picker-button.component.ts +74 -0
- package/src/lib/primitives/pickers/time-picker-dropdown-selector.component.ts +188 -0
- package/src/lib/primitives/pickers/time-picker.component.ts +124 -0
- package/src/lib/primitives/pickers/time-unit-selector.component.ts +82 -0
- package/src/lib/primitives/progressbar.component.test.ts +18 -0
- package/src/lib/primitives/progressbar.component.ts +93 -0
- package/src/lib/primitives/providersStyles.ts +268 -0
- package/src/lib/primitives/slider-base.component.ts +56 -0
- package/src/lib/provide-block-auth.test.ts +43 -0
- package/src/lib/provide-block-auth.ts +43 -0
- package/src/lib/runtime/block-auth-runtime.directive.test.ts +89 -0
- package/src/lib/runtime/block-auth-runtime.directive.ts +68 -0
- package/src/lib/teleport-body.directive.test.ts +29 -0
- package/src/lib/teleport-body.directive.ts +21 -0
- package/src/lib/testing.ts +15 -0
- package/src/lib/transitions.css +23 -0
- package/src/public-api.ts +85 -0
- package/src/vitest.setup.ts +8 -0
- package/tailwind.config.cjs +6 -0
- package/tsconfig.json +15 -0
- package/tsconfig.lib.json +9 -0
- package/vite.config.mts +44 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
// Sin react-loader-spinner (`<Oval>`)
|
|
4
|
+
// ni `animate.css`: el anillo animado se dibuja con un <svg> propio (pista +
|
|
5
|
+
// arco giratorio con la clase `bas-oval-spin` de transitions.css) y el
|
|
6
|
+
// fade-in de entrada se resuelve con una animación CSS local equivalente a
|
|
7
|
+
// `animate__fadeIn`. `icon` (ReactNode / slot `icon`) pasa a ser
|
|
8
|
+
// `<ng-content select="[slot=icon]" />`.
|
|
9
|
+
const BLOCKAUTH_LOGO = 'https://dashboard.block-auth.io/img/blockauth_logo.png';
|
|
10
|
+
|
|
11
|
+
type OvalSize = 'xs' | 'small' | 'medium' | 'large';
|
|
12
|
+
type OvalPosition =
|
|
13
|
+
| 'center'
|
|
14
|
+
| 'top'
|
|
15
|
+
| 'bottom'
|
|
16
|
+
| 'left'
|
|
17
|
+
| 'right'
|
|
18
|
+
| 'top-left'
|
|
19
|
+
| 'top-right'
|
|
20
|
+
| 'bottom-left'
|
|
21
|
+
| 'bottom-right';
|
|
22
|
+
|
|
23
|
+
@Component({
|
|
24
|
+
selector: 'ba-oval-spinner',
|
|
25
|
+
standalone: true,
|
|
26
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
27
|
+
template: `
|
|
28
|
+
<div
|
|
29
|
+
[class]="
|
|
30
|
+
'bal-ba-backdrop-blur-base bal-ba-backdrop-blur-xl bas-absolute bas-flex bas-h-full bas-w-full ' +
|
|
31
|
+
(animateBg() ? 'bas-oval-fadein' : '') +
|
|
32
|
+
' ' +
|
|
33
|
+
positionClasses() +
|
|
34
|
+
' ' +
|
|
35
|
+
zIndex() +
|
|
36
|
+
' ' +
|
|
37
|
+
classNames()
|
|
38
|
+
"
|
|
39
|
+
>
|
|
40
|
+
<div
|
|
41
|
+
[class]="'bas-relative bas-flex bas-items-center bas-justify-center ' + positionClassNames()"
|
|
42
|
+
>
|
|
43
|
+
<svg
|
|
44
|
+
class="bas-oval-fadein"
|
|
45
|
+
[attr.width]="ovalSize()"
|
|
46
|
+
[attr.height]="ovalSize()"
|
|
47
|
+
role="img"
|
|
48
|
+
aria-label="oval-loading"
|
|
49
|
+
[attr.viewBox]="'0 0 ' + ovalSize() + ' ' + ovalSize()"
|
|
50
|
+
>
|
|
51
|
+
<circle
|
|
52
|
+
[attr.cx]="ovalSize() / 2"
|
|
53
|
+
[attr.cy]="ovalSize() / 2"
|
|
54
|
+
[attr.r]="radius()"
|
|
55
|
+
fill="none"
|
|
56
|
+
stroke="#93C5FD"
|
|
57
|
+
[attr.stroke-width]="strokeWidth"
|
|
58
|
+
/>
|
|
59
|
+
<circle
|
|
60
|
+
class="bas-oval-spin"
|
|
61
|
+
[attr.cx]="ovalSize() / 2"
|
|
62
|
+
[attr.cy]="ovalSize() / 2"
|
|
63
|
+
[attr.r]="radius()"
|
|
64
|
+
fill="none"
|
|
65
|
+
stroke="#00509A"
|
|
66
|
+
[attr.stroke-width]="strokeWidth"
|
|
67
|
+
[attr.stroke-dasharray]="circumference() * 0.25 + ' ' + circumference()"
|
|
68
|
+
stroke-linecap="round"
|
|
69
|
+
style="transform-origin: 50% 50%"
|
|
70
|
+
/>
|
|
71
|
+
</svg>
|
|
72
|
+
<div [class]="showLogo() ? 'bas-absolute ' + (customLogoSize() || imageSize()) : 'bas-hidden'">
|
|
73
|
+
@if (!isIcon()) {
|
|
74
|
+
<img
|
|
75
|
+
id="loader-image"
|
|
76
|
+
[src]="imageLogoSrc()"
|
|
77
|
+
class="bas-aspect-square bas-h-full bas-w-full bas-rounded-full"
|
|
78
|
+
[alt]="imageAlt()"
|
|
79
|
+
/>
|
|
80
|
+
} @else {
|
|
81
|
+
<ng-content select="[slot=icon]" />
|
|
82
|
+
}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
`,
|
|
87
|
+
styles: [
|
|
88
|
+
`
|
|
89
|
+
@keyframes bas-oval-fadein-kf {
|
|
90
|
+
from {
|
|
91
|
+
opacity: 0;
|
|
92
|
+
}
|
|
93
|
+
to {
|
|
94
|
+
opacity: 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
.bas-oval-fadein {
|
|
98
|
+
animation: bas-oval-fadein-kf 0.3s ease-in;
|
|
99
|
+
}
|
|
100
|
+
`,
|
|
101
|
+
],
|
|
102
|
+
})
|
|
103
|
+
export class OvalSpinnerComponent {
|
|
104
|
+
size = input<OvalSize>('medium');
|
|
105
|
+
customSize = input<number | undefined>(undefined);
|
|
106
|
+
classNames = input('');
|
|
107
|
+
position = input<OvalPosition>('center');
|
|
108
|
+
positionClassNames = input('');
|
|
109
|
+
animateBg = input(true);
|
|
110
|
+
showLogo = input(false);
|
|
111
|
+
logoSize = input<OvalSize>('medium');
|
|
112
|
+
customLogoSize = input<string | undefined>(undefined);
|
|
113
|
+
isIcon = input(false);
|
|
114
|
+
imageLogoSrc = input(BLOCKAUTH_LOGO);
|
|
115
|
+
zIndex = input('bas-z-50');
|
|
116
|
+
|
|
117
|
+
readonly strokeWidth = 2;
|
|
118
|
+
|
|
119
|
+
ovalSize = computed(() => {
|
|
120
|
+
if (this.customSize()) return this.customSize()!;
|
|
121
|
+
if (this.size() === 'xs') return 48;
|
|
122
|
+
if (this.size() === 'small') return 64;
|
|
123
|
+
if (this.size() === 'medium') return 80;
|
|
124
|
+
return 112; // large
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
positionClasses = computed(
|
|
128
|
+
() =>
|
|
129
|
+
({
|
|
130
|
+
center: 'bas-items-center bas-justify-center',
|
|
131
|
+
top: 'bas-items-start bas-justify-center',
|
|
132
|
+
bottom: 'bas-items-end bas-justify-center',
|
|
133
|
+
left: 'bas-items-center bas-justify-start',
|
|
134
|
+
right: 'bas-items-center bas-justify-end',
|
|
135
|
+
'top-left': 'bas-items-start bas-justify-start',
|
|
136
|
+
'top-right': 'bas-items-start bas-justify-end',
|
|
137
|
+
'bottom-left': 'bas-items-end bas-justify-start',
|
|
138
|
+
'bottom-right': 'bas-items-end bas-justify-end',
|
|
139
|
+
})[this.position()],
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
imageSize = computed(
|
|
143
|
+
() =>
|
|
144
|
+
({
|
|
145
|
+
xs: 'bas-h-10 bas-w-10',
|
|
146
|
+
small: 'bas-h-12 bas-w-12',
|
|
147
|
+
medium: 'bas-h-16 bas-w-16',
|
|
148
|
+
large: 'bas-h-20 bas-w-20',
|
|
149
|
+
})[this.logoSize()],
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
imageAlt = computed(() =>
|
|
153
|
+
this.imageLogoSrc() === BLOCKAUTH_LOGO ? 'Block-Auth Logo' : 'Image inside loader',
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
radius = computed(() => this.ovalSize() / 2 - this.strokeWidth);
|
|
157
|
+
circumference = computed(() => 2 * Math.PI * this.radius());
|
|
158
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
ElementRef,
|
|
5
|
+
PLATFORM_ID,
|
|
6
|
+
computed,
|
|
7
|
+
effect,
|
|
8
|
+
inject,
|
|
9
|
+
input,
|
|
10
|
+
output,
|
|
11
|
+
viewChild,
|
|
12
|
+
} from '@angular/core';
|
|
13
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
14
|
+
import { PickerButtonComponent } from './picker-button.component';
|
|
15
|
+
|
|
16
|
+
// Shell animado de los pickers: el `<ba-picker-button>` más el revelado del
|
|
17
|
+
// cuerpo del dropdown (posicionado en absoluto, por lo que su orden en el DOM
|
|
18
|
+
// respecto del botón es irrelevante — se renderiza el botón UNA sola vez y el
|
|
19
|
+
// dropdown se coloca arriba/abajo según `dropdownDirection`).
|
|
20
|
+
//
|
|
21
|
+
// - La animación (framer-motion en react / `<Transition clip-path>` en vue)
|
|
22
|
+
// pasa a una animación CSS `.ba-clip-down`/`.ba-clip-up` (revelado circular).
|
|
23
|
+
// - `icon` (ReactNode / slot #icon) se reenvía por `[slot=icon]` a
|
|
24
|
+
// `<ba-picker-button>` (vía `ngProjectAs`); los hijos van al slot por defecto.
|
|
25
|
+
// - `setIsOpen`/`toggleDropdown`/`clickedOutside` -> outputs `isOpenChange`/
|
|
26
|
+
// `toggleDropdown`/`clickOutside`.
|
|
27
|
+
// - Click-outside anclado a `document` (o al elemento del modal cuando
|
|
28
|
+
// `isInsideModal`); NO se retira el listener del modal (al desmontar ya no
|
|
29
|
+
// existe — mismo leak deliberado que el original). Se replica con `effect()`
|
|
30
|
+
// sobre `isOpen()`, sólo en navegador (SSR-safe).
|
|
31
|
+
@Component({
|
|
32
|
+
selector: 'ba-dropdown-picker-animated-base',
|
|
33
|
+
standalone: true,
|
|
34
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
35
|
+
host: { style: 'display: contents' },
|
|
36
|
+
imports: [PickerButtonComponent],
|
|
37
|
+
template: `
|
|
38
|
+
<div
|
|
39
|
+
[class]="
|
|
40
|
+
displayClassNames() + ' bas-w-full bas-items-center bas-gap-1.5 ' +
|
|
41
|
+
getHeightAndGridClassNames(size(), enableError(), enableWhiteSpaces()) + ' ' + classNames()
|
|
42
|
+
"
|
|
43
|
+
[style.width]="widthStyle()"
|
|
44
|
+
>
|
|
45
|
+
@if (enableError() && enableWhiteSpaces()) {
|
|
46
|
+
<p
|
|
47
|
+
[class]="
|
|
48
|
+
'bas-m-0 bas-h-[18px] ' + (error() ? 'bal-ba-error-text' : 'bal-ba-error-text-invisible')
|
|
49
|
+
"
|
|
50
|
+
>{{ errorMessage() }}</p>
|
|
51
|
+
}
|
|
52
|
+
<div #internalContainer class="bas-relative bas-h-full bas-w-full">
|
|
53
|
+
<section
|
|
54
|
+
[class]="
|
|
55
|
+
'bal-ba-transition-2 bas-relative bas-flex bas-w-full bas-flex-col bas-items-center ' +
|
|
56
|
+
'bas-justify-start ' + getHeightAndGridClassNames(size(), false, false)
|
|
57
|
+
"
|
|
58
|
+
>
|
|
59
|
+
<ba-picker-button
|
|
60
|
+
[size]="size()"
|
|
61
|
+
[isOpen]="isOpen()"
|
|
62
|
+
[blueStyles]="blueStyles()"
|
|
63
|
+
[btnId]="btnId()"
|
|
64
|
+
[inputId]="inputId()"
|
|
65
|
+
[inputValue]="inputValue()"
|
|
66
|
+
[btnZIndexClassNames]="btnZIndexClassNames()"
|
|
67
|
+
(toggleDropdown)="toggleDropdown.emit()"
|
|
68
|
+
>
|
|
69
|
+
<ng-container ngProjectAs="[slot=icon]"><ng-content select="[slot=icon]" /></ng-container>
|
|
70
|
+
</ba-picker-button>
|
|
71
|
+
|
|
72
|
+
@if (activeDropdown() && isOpen()) {
|
|
73
|
+
<div [class]="dropdownClasses()">
|
|
74
|
+
<ng-content />
|
|
75
|
+
</div>
|
|
76
|
+
}
|
|
77
|
+
</section>
|
|
78
|
+
</div>
|
|
79
|
+
@if (enableError()) {
|
|
80
|
+
<p class="bal-ba-error-text-invisible bas-m-0 bas-h-[18px]">white space</p>
|
|
81
|
+
}
|
|
82
|
+
</div>
|
|
83
|
+
`,
|
|
84
|
+
styles: [
|
|
85
|
+
`
|
|
86
|
+
.ba-clip-down {
|
|
87
|
+
animation: ba-clip-down-in 0.2s ease;
|
|
88
|
+
transform-origin: top center;
|
|
89
|
+
}
|
|
90
|
+
.ba-clip-up {
|
|
91
|
+
animation: ba-clip-up-in 0.2s ease;
|
|
92
|
+
transform-origin: bottom center;
|
|
93
|
+
}
|
|
94
|
+
@keyframes ba-clip-down-in {
|
|
95
|
+
from { clip-path: circle(0% at 50% 0%); opacity: 0.4; }
|
|
96
|
+
to { clip-path: circle(150% at 50% 0%); opacity: 1; }
|
|
97
|
+
}
|
|
98
|
+
@keyframes ba-clip-up-in {
|
|
99
|
+
from { clip-path: circle(0% at 50% 100%); opacity: 0.4; }
|
|
100
|
+
to { clip-path: circle(150% at 50% 100%); opacity: 1; }
|
|
101
|
+
}
|
|
102
|
+
`,
|
|
103
|
+
],
|
|
104
|
+
})
|
|
105
|
+
export class DropdownPickerAnimatedBaseComponent {
|
|
106
|
+
size = input<'xxs' | 'xs' | 'small' | 'base' | 'large'>('base');
|
|
107
|
+
isOpen = input(false);
|
|
108
|
+
closeWhenClickedOutside = input(true);
|
|
109
|
+
enableError = input(true);
|
|
110
|
+
enableWhiteSpaces = input(true);
|
|
111
|
+
error = input(false);
|
|
112
|
+
errorMessage = input('');
|
|
113
|
+
btnId = input('');
|
|
114
|
+
inputId = input('');
|
|
115
|
+
inputValue = input('');
|
|
116
|
+
container = input<HTMLElement | null>(null);
|
|
117
|
+
exceptions = input<(HTMLElement | null)[]>([]);
|
|
118
|
+
blueStyles = input(true);
|
|
119
|
+
activeDropdown = input(true);
|
|
120
|
+
width = input<number | string>('100%');
|
|
121
|
+
classNames = input('');
|
|
122
|
+
displayClassNames = input('bas-grid');
|
|
123
|
+
btnZIndexClassNames = input('bas-z-0');
|
|
124
|
+
dropdownZIndexClassNames = input('bas-z-50');
|
|
125
|
+
isInsideModal = input(false);
|
|
126
|
+
modalId = input('txns-overview');
|
|
127
|
+
dropdownDirection = input<'up' | 'down'>('down');
|
|
128
|
+
|
|
129
|
+
isOpenChange = output<boolean>();
|
|
130
|
+
toggleDropdown = output<void>();
|
|
131
|
+
clickOutside = output<EventTarget | null>();
|
|
132
|
+
|
|
133
|
+
private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
134
|
+
private readonly internalContainer =
|
|
135
|
+
viewChild<ElementRef<HTMLDivElement>>('internalContainer');
|
|
136
|
+
|
|
137
|
+
protected readonly widthStyle = computed(() => {
|
|
138
|
+
const w = this.width();
|
|
139
|
+
return typeof w === 'number' ? `${w}px` : w;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
protected readonly dropdownClasses = computed(() => {
|
|
143
|
+
const down = this.dropdownDirection() === 'down';
|
|
144
|
+
return (
|
|
145
|
+
'bal-ba-transition-2 bal-ba-shadow-base bas-w-full bas-min-w-[152px] ' +
|
|
146
|
+
'bas-rounded-md bas-border bas-border-solid bas-border-[#a9d6e5] ' +
|
|
147
|
+
'bas-bg-white hover:bas-border-[#98c0ce] ' +
|
|
148
|
+
this.dropdownZIndexClassNames() +
|
|
149
|
+
' ' +
|
|
150
|
+
(down
|
|
151
|
+
? 'ba-clip-down bal-ba-hover-shadow-md bas-absolute bas-top-full bas-mt-2'
|
|
152
|
+
: 'ba-clip-up bal-ba-hover-shadow-top-md bas-absolute bas-mb-2 bas-bottom-full')
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
private readonly handleClickOutsideTimer = (e: Event): void => {
|
|
157
|
+
if (!this.isOpen()) return;
|
|
158
|
+
const target = e.target as HTMLElement;
|
|
159
|
+
const activeException = this.exceptions().some((el) => el && el.id === target.id);
|
|
160
|
+
if (activeException) return;
|
|
161
|
+
const el = this.internalContainer()?.nativeElement;
|
|
162
|
+
if (el && !el.contains(target)) {
|
|
163
|
+
if (this.closeWhenClickedOutside()) this.isOpenChange.emit(false);
|
|
164
|
+
else this.clickOutside.emit(target);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
constructor() {
|
|
169
|
+
// Port 1:1 del useEffect([isOpen]) de DropdownPickerAnimatedBase.tsx.
|
|
170
|
+
effect((onCleanup) => {
|
|
171
|
+
if (!this.isBrowser || !this.activeDropdown()) return;
|
|
172
|
+
const open = this.isOpen();
|
|
173
|
+
const insideModal = this.isInsideModal();
|
|
174
|
+
const removeListener = (): void => {
|
|
175
|
+
if (!insideModal) document.removeEventListener('click', this.handleClickOutsideTimer);
|
|
176
|
+
};
|
|
177
|
+
if (open) {
|
|
178
|
+
if (!insideModal) {
|
|
179
|
+
document.addEventListener('click', this.handleClickOutsideTimer);
|
|
180
|
+
} else {
|
|
181
|
+
const modal = document.getElementById(this.modalId());
|
|
182
|
+
if (modal) modal.addEventListener('click', this.handleClickOutsideTimer);
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
removeListener();
|
|
186
|
+
}
|
|
187
|
+
onCleanup(removeListener);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getHeightAndGridClassNames(size: string, enableError: boolean, enableWhiteSpaces: boolean): string {
|
|
192
|
+
return {
|
|
193
|
+
xxs: enableError
|
|
194
|
+
? enableWhiteSpaces
|
|
195
|
+
? 'bas-h-[82px] bas-grid-rows-[18px_34px_18px]'
|
|
196
|
+
: 'bas-h-[58px] bas-grid-rows-[34px_18px]'
|
|
197
|
+
: 'bas-h-[34px]',
|
|
198
|
+
xs: enableError
|
|
199
|
+
? enableWhiteSpaces
|
|
200
|
+
? 'bas-h-[86px] bas-grid-rows-[18px_38px_18px]'
|
|
201
|
+
: 'bas-h-[62px] bas-grid-rows-[38px_18px]'
|
|
202
|
+
: 'bas-h-[38px]',
|
|
203
|
+
small: enableError
|
|
204
|
+
? enableWhiteSpaces
|
|
205
|
+
? 'bas-h-[90px] bas-grid-rows-[18px_42px_18px]'
|
|
206
|
+
: 'bas-h-[66px] bas-grid-rows-[42px_18px]'
|
|
207
|
+
: 'bas-h-[42px]',
|
|
208
|
+
base: enableError
|
|
209
|
+
? enableWhiteSpaces
|
|
210
|
+
? 'bas-h-[94px] bas-grid-rows-[18px_46px_18px]'
|
|
211
|
+
: 'bas-h-[70px] bas-grid-rows-[46px_18px]'
|
|
212
|
+
: 'bas-h-[46px]',
|
|
213
|
+
large: enableError
|
|
214
|
+
? enableWhiteSpaces
|
|
215
|
+
? 'bas-h-[102px] bas-grid-rows-[18px_54px_18px]'
|
|
216
|
+
: 'bas-h-[78px] bas-grid-rows-[54px_18px]'
|
|
217
|
+
: 'bas-h-[54px]',
|
|
218
|
+
}[size]!;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
// Input de solo-lectura + icono al final que alterna la apertura del dropdown
|
|
4
|
+
// del picker. El icono llega por el slot `[slot=icon]` (en react era la prop
|
|
5
|
+
// `icon?: ReactNode`). `toggleDropdown` (callback prop) -> output `toggleDropdown`.
|
|
6
|
+
// `:host { display: contents }`: el `<button>` es el hijo real del flex/grid del
|
|
7
|
+
// contenedor del picker (como en react/vue).
|
|
8
|
+
@Component({
|
|
9
|
+
selector: 'ba-picker-button',
|
|
10
|
+
standalone: true,
|
|
11
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
12
|
+
host: { style: 'display: contents' },
|
|
13
|
+
template: `
|
|
14
|
+
<button
|
|
15
|
+
type="button"
|
|
16
|
+
[id]="btnId()"
|
|
17
|
+
[class]="
|
|
18
|
+
'bal-ba-outline-none bal-ba-transition-150 bas-grid bas-h-full ' +
|
|
19
|
+
'bas-w-full bas-cursor-pointer bas-grid-cols-[1fr_28px] bas-items-center ' +
|
|
20
|
+
'bas-rounded-lg bas-border-2 bas-border-solid ' + bgColor() + ' ' +
|
|
21
|
+
getHeightClassNames(size()) + ' ' + btnZIndexClassNames() + ' ' +
|
|
22
|
+
(blueStyles() || isOpen()
|
|
23
|
+
? 'bas-border-[var(--bal-primary-100)] hover:bas-border-[var(--bal-primary-300)]'
|
|
24
|
+
: 'bas-border-gray-300 hover:bas-border-slate-400')
|
|
25
|
+
"
|
|
26
|
+
(click)="onBtnClicked($event)"
|
|
27
|
+
>
|
|
28
|
+
<input
|
|
29
|
+
[id]="inputId()"
|
|
30
|
+
[class]="
|
|
31
|
+
'bal-ba-outline-none bal-ba-transition-150 bal-ba-shadow-base bal-ba-focus-shadow-none ' +
|
|
32
|
+
'bas-box-border bas-h-full bas-w-full bas-cursor-pointer bas-border-none bas-px-0 ' +
|
|
33
|
+
'bas-py-1 bas-text-start bas-font-normal xsm1:bas-pl-2 ' +
|
|
34
|
+
(size() !== 'xxs' ? 'bas-text-lg' : 'bas-text-sm') + ' ' +
|
|
35
|
+
(blueStyles() || isOpen()
|
|
36
|
+
? 'bas-text-[#004e98] hover:bas-text-[#00296b]'
|
|
37
|
+
: 'bas-text-gray-500 hover:bas-text-slate-600') + ' ' + bgColor()
|
|
38
|
+
"
|
|
39
|
+
[value]="inputValue()"
|
|
40
|
+
readonly
|
|
41
|
+
/>
|
|
42
|
+
<span class="bas-flex bas-h-full bas-w-full bas-items-center bas-justify-center bas-rounded-full">
|
|
43
|
+
<ng-content select="[slot=icon]" />
|
|
44
|
+
</span>
|
|
45
|
+
</button>
|
|
46
|
+
`,
|
|
47
|
+
})
|
|
48
|
+
export class PickerButtonComponent {
|
|
49
|
+
size = input<'xxs' | 'xs' | 'small' | 'base' | 'large'>('base');
|
|
50
|
+
isOpen = input(false);
|
|
51
|
+
blueStyles = input(false);
|
|
52
|
+
btnId = input('picker-btn-id');
|
|
53
|
+
inputId = input('picker-btn-id');
|
|
54
|
+
inputValue = input('Picker Value');
|
|
55
|
+
bgColor = input('bas-bg-transparent');
|
|
56
|
+
btnZIndexClassNames = input('bas-z-0');
|
|
57
|
+
|
|
58
|
+
toggleDropdown = output<void>();
|
|
59
|
+
|
|
60
|
+
onBtnClicked(e: MouseEvent): void {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
this.toggleDropdown.emit();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getHeightClassNames(size: string): string {
|
|
66
|
+
return {
|
|
67
|
+
xxs: 'bas-h-[34px] bas-px-1.5 bas-pb-0.5 bas-pt-0.5',
|
|
68
|
+
xs: 'bas-h-[38px] bas-px-1.5 bas-pb-0.5 bas-pt-1',
|
|
69
|
+
small: 'bas-h-[42px] bas-px-1.5 bas-pb-1 bas-pt-1.5',
|
|
70
|
+
base: 'bas-h-[46px] bas-px-1.5 bas-pb-1.5 bas-pt-2',
|
|
71
|
+
large: 'bas-h-[54px] bas-px-1.5 bas-pb-2.5 bas-pt-3',
|
|
72
|
+
}[size]!;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
effect,
|
|
5
|
+
input,
|
|
6
|
+
output,
|
|
7
|
+
signal,
|
|
8
|
+
untracked,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import { getSecFromTimeValue } from '../helpers';
|
|
11
|
+
import { TimeUnitSelectorComponent } from './time-unit-selector.component';
|
|
12
|
+
|
|
13
|
+
export interface TimeArrays {
|
|
14
|
+
minRange: string[];
|
|
15
|
+
defaultRange: string[];
|
|
16
|
+
maxRange: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Selector de tiempo de tres columnas (horas/minutos/segundos) dentro del
|
|
20
|
+
// dropdown del picker, más el botón Guardar. `onChangeTimeUnit` reconcilia los
|
|
21
|
+
// rangos min/max a medida que el usuario cambia cada unidad (máximo total
|
|
22
|
+
// 29100s → 08:05:00); Guardar confía el tiempo elegido al padre como segundos
|
|
23
|
+
// totales vía el output `timeChanged`. Los `useState` de react -> signals; el
|
|
24
|
+
// `useEffect([defaultValues])` (reset) -> effect() que sólo trackea
|
|
25
|
+
// `defaultValues`. `:host { display: contents }`: el grid interno es el hijo
|
|
26
|
+
// real del contenedor del dropdown.
|
|
27
|
+
const range = (length: number, offset = 0): string[] =>
|
|
28
|
+
Array.from({ length }, (_, i) => String(i + offset).padStart(2, '0'));
|
|
29
|
+
|
|
30
|
+
const initialStates = {
|
|
31
|
+
hoursArray: range(8, 1),
|
|
32
|
+
minutesArrays: { minRange: range(50, 10), defaultRange: range(60), maxRange: range(41) },
|
|
33
|
+
secondsArrays: { minRange: range(50, 10), defaultRange: range(60), maxRange: range(1) },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
@Component({
|
|
37
|
+
selector: 'ba-time-picker-dropdown-selector',
|
|
38
|
+
standalone: true,
|
|
39
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
40
|
+
host: { style: 'display: contents' },
|
|
41
|
+
imports: [TimeUnitSelectorComponent],
|
|
42
|
+
template: `
|
|
43
|
+
<div class="bas-grid bas-grid-rows-[232px_32px] bas-justify-center bas-gap-1 bas-py-1.5 bas-pl-2.5 bas-pr-1">
|
|
44
|
+
<div class="bas-grid bas-h-full bas-w-[136px] bas-grid-cols-3 bas-gap-0.5">
|
|
45
|
+
<ba-time-unit-selector
|
|
46
|
+
[id]="id()"
|
|
47
|
+
[timeUnitArray]="hoursArray()"
|
|
48
|
+
timeUnit="hours"
|
|
49
|
+
[selectedValue]="selectedValues()[0]"
|
|
50
|
+
[firstValue]="parseIntSafe(hoursArray()[0])"
|
|
51
|
+
(changeTimeUnit)="onChangeTimeUnit($event.timeUnit, $event.value)"
|
|
52
|
+
/>
|
|
53
|
+
<ba-time-unit-selector
|
|
54
|
+
[id]="id()"
|
|
55
|
+
[timeUnitArray]="minutesArray()"
|
|
56
|
+
timeUnit="minutes"
|
|
57
|
+
[selectedValue]="selectedValues()[1]"
|
|
58
|
+
[firstValue]="parseIntSafe(minutesArray()[0])"
|
|
59
|
+
(changeTimeUnit)="onChangeTimeUnit($event.timeUnit, $event.value)"
|
|
60
|
+
/>
|
|
61
|
+
<ba-time-unit-selector
|
|
62
|
+
[id]="id()"
|
|
63
|
+
[timeUnitArray]="secondsArray()"
|
|
64
|
+
timeUnit="seconds"
|
|
65
|
+
[selectedValue]="selectedValues()[2]"
|
|
66
|
+
[firstValue]="parseIntSafe(secondsArray()[0])"
|
|
67
|
+
(changeTimeUnit)="onChangeTimeUnit($event.timeUnit, $event.value)"
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="bas-flex bas-items-center bas-justify-end bas-px-1.5">
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
[id]="id() + '-btn-save'"
|
|
74
|
+
class="bal-ba-transition-2 bas-cursor-pointer bas-rounded-md
|
|
75
|
+
bas-border-none bas-bg-[var(--bal-primary-300)] bas-px-1.5 bas-py-1
|
|
76
|
+
bas-text-sm bas-text-white hover:bas-bg-[#4d849b]
|
|
77
|
+
disabled:bas-cursor-not-allowed disabled:bas-opacity-50"
|
|
78
|
+
[disabled]="isBtnSaveDisabled()"
|
|
79
|
+
(click)="onSaveTimeBtnClicked()"
|
|
80
|
+
>
|
|
81
|
+
{{ btnSaveText() }}
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
`,
|
|
86
|
+
})
|
|
87
|
+
export class TimePickerDropdownSelectorComponent {
|
|
88
|
+
id = input('time-picker');
|
|
89
|
+
hoursArray = input<string[]>(initialStates.hoursArray);
|
|
90
|
+
minutesArrays = input<TimeArrays>(initialStates.minutesArrays);
|
|
91
|
+
secondsArrays = input<TimeArrays>(initialStates.secondsArrays);
|
|
92
|
+
defaultValues = input('08:05:00');
|
|
93
|
+
btnSaveText = input('Save');
|
|
94
|
+
|
|
95
|
+
timeChanged = output<number>();
|
|
96
|
+
toggleTimerDropdown = output<void>();
|
|
97
|
+
|
|
98
|
+
protected readonly minutesArray = signal<string[]>(initialStates.minutesArrays.defaultRange);
|
|
99
|
+
protected readonly secondsArray = signal<string[]>(initialStates.secondsArrays.defaultRange);
|
|
100
|
+
protected readonly selectedValues = signal<string[]>('08:05:00'.split(':'));
|
|
101
|
+
protected readonly isBtnSaveDisabled = signal(true);
|
|
102
|
+
private initialValues: string[] = '08:05:00'.split(':');
|
|
103
|
+
|
|
104
|
+
constructor() {
|
|
105
|
+
// Reset: sólo trackea defaultValues;
|
|
106
|
+
// el resto se lee sin tracking para no re-disparar en cascada.
|
|
107
|
+
effect(() => {
|
|
108
|
+
const defaultValues = this.defaultValues();
|
|
109
|
+
untracked(() => {
|
|
110
|
+
const splitted = defaultValues.split(':');
|
|
111
|
+
this.initialValues = [...splitted];
|
|
112
|
+
this.selectedValues.set([...splitted]);
|
|
113
|
+
this.isBtnSaveDisabled.set(true);
|
|
114
|
+
this.onChangeTimeUnit('hours', splitted[0], [...splitted], true);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
parseIntSafe(v: string | undefined): number {
|
|
120
|
+
return parseInt(v ?? '0', 10) || 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
onChangeTimeUnit(
|
|
124
|
+
timeUnit: string,
|
|
125
|
+
newValue: string,
|
|
126
|
+
timeArrayValues: string[] = this.selectedValues(),
|
|
127
|
+
isDefaultTime = false,
|
|
128
|
+
): void {
|
|
129
|
+
const timeArray = [...timeArrayValues];
|
|
130
|
+
const hoursArray = this.hoursArray();
|
|
131
|
+
if (timeUnit === 'hours' || timeUnit === 'minutes') {
|
|
132
|
+
const minMinutesArray = this.minutesArrays().minRange;
|
|
133
|
+
const defaultMinutesArray = this.minutesArrays().defaultRange;
|
|
134
|
+
const maxMinutesArray = this.minutesArrays().maxRange;
|
|
135
|
+
const minSecondsArray = this.secondsArrays().minRange;
|
|
136
|
+
const defaultSecondsArray = this.secondsArrays().defaultRange;
|
|
137
|
+
const maxSecondsArray = this.secondsArrays().maxRange;
|
|
138
|
+
let newMinutesArray = defaultMinutesArray;
|
|
139
|
+
let newSecondsArray = defaultSecondsArray;
|
|
140
|
+
if (timeUnit === 'hours') timeArray[0] = newValue;
|
|
141
|
+
else timeArray[1] = newValue;
|
|
142
|
+
if (timeArray[0] === hoursArray[hoursArray.length - 1]) {
|
|
143
|
+
newMinutesArray = maxMinutesArray;
|
|
144
|
+
if (
|
|
145
|
+
!maxMinutesArray.includes(timeArray[1]) ||
|
|
146
|
+
timeArray[1] === maxMinutesArray[maxMinutesArray.length - 1]
|
|
147
|
+
) {
|
|
148
|
+
if (!maxMinutesArray.includes(timeArray[1])) {
|
|
149
|
+
timeArray[1] = maxMinutesArray[maxMinutesArray.length - 1];
|
|
150
|
+
}
|
|
151
|
+
newSecondsArray = maxSecondsArray;
|
|
152
|
+
if (!maxSecondsArray.includes(timeArray[2])) {
|
|
153
|
+
timeArray[2] = maxSecondsArray[maxSecondsArray.length - 1];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} else if (timeArray[0] === hoursArray[0]) {
|
|
157
|
+
newMinutesArray = minMinutesArray;
|
|
158
|
+
if (!minMinutesArray.includes(timeArray[1]) || timeArray[1] === minMinutesArray[0]) {
|
|
159
|
+
if (!minMinutesArray.includes(timeArray[1])) {
|
|
160
|
+
timeArray[1] = minMinutesArray[0];
|
|
161
|
+
}
|
|
162
|
+
newSecondsArray = minSecondsArray;
|
|
163
|
+
if (!minSecondsArray.includes(timeArray[2])) {
|
|
164
|
+
timeArray[2] = minSecondsArray[0];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
this.minutesArray.set([...newMinutesArray]);
|
|
169
|
+
this.secondsArray.set([...newSecondsArray]);
|
|
170
|
+
} else timeArray[2] = newValue;
|
|
171
|
+
|
|
172
|
+
this.selectedValues.set([...timeArray]);
|
|
173
|
+
|
|
174
|
+
if (isDefaultTime) this.isBtnSaveDisabled.set(true);
|
|
175
|
+
else {
|
|
176
|
+
this.isBtnSaveDisabled.set(
|
|
177
|
+
timeArray.every((timeValue, index) => timeValue === this.initialValues[index]),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
onSaveTimeBtnClicked(): void {
|
|
183
|
+
this.initialValues = this.selectedValues();
|
|
184
|
+
const newTimeValue = this.selectedValues().join(':');
|
|
185
|
+
this.timeChanged.emit(getSecFromTimeValue(newTimeValue));
|
|
186
|
+
this.toggleTimerDropdown.emit();
|
|
187
|
+
}
|
|
188
|
+
}
|