@apolopay-sdk/ui 1.0.0 → 1.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.
@@ -1,84 +0,0 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- };
7
- import { LitElement, html, css } from 'lit';
8
- import { customElement, property, state } from 'lit/decorators.js';
9
- import { I18n } from '@apolopay-sdk/core';
10
- let PaymentTimer = class PaymentTimer extends LitElement {
11
- constructor() {
12
- super(...arguments);
13
- this.expiresAt = 0;
14
- this.timerString = '-- : --';
15
- this._interval = null;
16
- }
17
- connectedCallback() {
18
- super.connectedCallback();
19
- this.startTimer();
20
- }
21
- disconnectedCallback() {
22
- this.stopTimer();
23
- super.disconnectedCallback();
24
- }
25
- updated(changedProperties) {
26
- if (changedProperties.has('expiresAt')) {
27
- this.startTimer();
28
- }
29
- }
30
- startTimer() {
31
- this.stopTimer();
32
- if (!this.expiresAt || isNaN(this.expiresAt))
33
- return;
34
- const tick = () => {
35
- const now = Date.now();
36
- const distance = this.expiresAt - now;
37
- if (distance <= 0) {
38
- this.stopTimer();
39
- const minLabel = I18n.t.modal.labels.minutes;
40
- const secLabel = I18n.t.modal.labels.seconds;
41
- this.timerString = `00 ${minLabel} : 00 ${secLabel}`;
42
- this.dispatchEvent(new CustomEvent('expired'));
43
- return;
44
- }
45
- const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
46
- const seconds = Math.floor((distance % (1000 * 60)) / 1000);
47
- const m = minutes.toString().padStart(2, '0');
48
- const s = seconds.toString().padStart(2, '0');
49
- const minLabel = I18n.t.modal.labels.minutes;
50
- const secLabel = I18n.t.modal.labels.seconds;
51
- this.timerString = `${m} ${minLabel} : ${s} ${secLabel}`;
52
- };
53
- tick();
54
- this._interval = window.setInterval(tick, 1000);
55
- }
56
- stopTimer() {
57
- if (this._interval) {
58
- clearInterval(this._interval);
59
- this._interval = null;
60
- }
61
- }
62
- render() {
63
- return html `${this.timerString}`;
64
- }
65
- };
66
- PaymentTimer.styles = css `
67
- :host {
68
- display: block;
69
- color: var(--apolo-accent, #ea580c);
70
- font-weight: 600;
71
- font-size: 0.9rem;
72
- margin-bottom: 1rem;
73
- }
74
- `;
75
- __decorate([
76
- property({ type: Number })
77
- ], PaymentTimer.prototype, "expiresAt", void 0);
78
- __decorate([
79
- state()
80
- ], PaymentTimer.prototype, "timerString", void 0);
81
- PaymentTimer = __decorate([
82
- customElement('payment-timer')
83
- ], PaymentTimer);
84
- export { PaymentTimer };
@@ -1,151 +0,0 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- };
7
- import { LitElement, html, css } from 'lit';
8
- import { customElement, property } from 'lit/decorators.js';
9
- import { sharedStyles } from '../styles/shared-styles.js';
10
- import { logoApolo } from '../assets/logo_apolo.js';
11
- import { I18n } from '@apolopay-sdk/core';
12
- let TriggerButton = class TriggerButton extends LitElement {
13
- constructor() {
14
- super(...arguments);
15
- this.lang = 'es';
16
- this.loading = false;
17
- this.disabled = false;
18
- this.label = undefined;
19
- this.hasError = false;
20
- }
21
- render() {
22
- const t = I18n.t;
23
- const defaultLabel = this.hasError
24
- ? (t.errors.config || 'Config Error')
25
- : (this.loading ? t.trigger.loading : (this.label || 'Apolo Pay'));
26
- return html `
27
- <div class="button-wrapper">
28
- ${this.hasError ? '' : html `
29
- <img class="logo-apolo" src="${logoApolo}" alt="Icon" />
30
- `}
31
- <button
32
- ?disabled=${this.disabled || this.loading || this.hasError}
33
- class="${this.hasError ? 'error' : ''}"
34
- type="button"
35
- >
36
- ${defaultLabel}
37
- </button>
38
- </div>
39
- `;
40
- }
41
- };
42
- TriggerButton.styles = [
43
- sharedStyles,
44
- css `
45
- :host { display: inline-block; }
46
-
47
- .button-wrapper {
48
- --img-size: 24px;
49
- --gap: 8px;
50
- }
51
-
52
- button {
53
- position: relative;
54
- display: inline-flex;
55
- align-items: center;
56
- justify-content: center;
57
- width: 100%;
58
- height: 100%;
59
- padding: 12px 32px;
60
-
61
- font-size: 0.9rem;
62
- letter-spacing: 0.5px;
63
- color: var(--apolo-on-primary);
64
-
65
- background: #74727225;
66
- border: none;
67
- border-radius: 9999px;
68
- cursor: pointer;
69
- transition: transform 0.2s, background-color 0.2s;
70
-
71
- text-indent: var(--img-size);
72
- mix-blend-mode: difference;
73
- z-index: 1;
74
- }
75
-
76
- button::before {
77
- content: "";
78
- position: absolute;
79
- inset: 0;
80
- border-radius: 9999px;
81
- padding: 2px;
82
- background: linear-gradient(90deg, var(--apolo-primary), #ffffff);
83
- -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
84
- mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
85
- -webkit-mask-composite: xor;
86
- mask-composite: exclude;
87
- z-index: -1;
88
- }
89
-
90
- button:hover {
91
- transform: translateY(-1px);
92
- background-color: rgba(255, 255, 255, 0.1);
93
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
94
- }
95
-
96
- .button-wrapper:hover img {
97
- transform: translateY(calc(-50% - 1px));
98
- }
99
-
100
- button:active {
101
- transform: translateY(0);
102
- }
103
-
104
- button:disabled {
105
- opacity: 0.6;
106
- cursor: not-allowed;
107
- filter: grayscale(1);
108
- transform: none;
109
- }
110
-
111
- button.error {
112
- background-color: rgba(239, 68, 68, 0.1);
113
- color: #ef4444;
114
- text-indent: 0;
115
- }
116
-
117
- button.error::before {
118
- background: linear-gradient(90deg, #ef4444, #fca5a5);
119
- }
120
-
121
- .logo-apolo {
122
- position: absolute;
123
- top: 50%;
124
- left: calc((var(--img-size) / 2) + var(--gap));
125
- transform: translateY(-50%);
126
- width: var(--img-size);
127
- height: var(--img-size);
128
- margin-right: var(--gap);
129
- transition: transform 0.2s;
130
- }
131
- `
132
- ];
133
- __decorate([
134
- property({ type: String })
135
- ], TriggerButton.prototype, "lang", void 0);
136
- __decorate([
137
- property({ type: Boolean })
138
- ], TriggerButton.prototype, "loading", void 0);
139
- __decorate([
140
- property({ type: Boolean })
141
- ], TriggerButton.prototype, "disabled", void 0);
142
- __decorate([
143
- property({ type: String })
144
- ], TriggerButton.prototype, "label", void 0);
145
- __decorate([
146
- property({ type: Boolean })
147
- ], TriggerButton.prototype, "hasError", void 0);
148
- TriggerButton = __decorate([
149
- customElement('trigger-button')
150
- ], TriggerButton);
151
- export { TriggerButton };
package/dist/index.js DELETED
@@ -1,2 +0,0 @@
1
- export * from './payment-button.js';
2
- export * from '@apolopay-sdk/core';
@@ -1,371 +0,0 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- };
7
- import { LitElement, html, css } from 'lit';
8
- import { customElement, property, state } from 'lit/decorators.js';
9
- import { I18n, ModalStep, PaymentService, } from '@apolopay-sdk/core';
10
- // Import child components
11
- import './components/trigger-button.js';
12
- import './components/payment-modal.js';
13
- let ApoloPayButton = class ApoloPayButton extends LitElement {
14
- constructor() {
15
- super(...arguments);
16
- // --- Component Properties ---
17
- this.client = undefined;
18
- this.processId = undefined;
19
- this.productTitle = undefined;
20
- this.lang = 'es';
21
- this.label = undefined;
22
- this.loading = false;
23
- this.disabled = false;
24
- this.barrierDismissible = false;
25
- // --- Internal State ---
26
- this.isOpen = false; // Controls modal visibility
27
- this.status = 'idle'; // General status, used for QR generation and final result
28
- this.currentStep = ModalStep.SELECT_ASSET; // Current step in the modal flow
29
- this.selectedAsset = null; // ID of the chosen asset
30
- this.selectedNetwork = null; // ID of the chosen blockchain
31
- this.qrCodeUrl = null; // URL for the QR code image
32
- this.qrCodeExpiresAt = null; // Expiration time for the QR code
33
- this.paymentAddress = null; // Wallet address for payment
34
- this.paymentUrl = null; // Direct link for single-device payment
35
- this.assets = []; // List fetched from API
36
- this.error = null; // Stores error details if something fails
37
- this.isLoadingData = true; // Tracks initial loading of assets/networks
38
- this.amount = 0; // Fetched from processId
39
- this.hasConfigError = false; // Invalid publicKey or missing client
40
- this.email = null; // TODO set email from socket response
41
- this._service = null; // Internal business logic manager
42
- }
43
- // Detectar cambios en propiedades
44
- willUpdate(changedProperties) {
45
- if (changedProperties.has('lang'))
46
- I18n.setLocale(this.lang);
47
- if (changedProperties.has('client') ||
48
- changedProperties.has('processId')) {
49
- this.validateConfig();
50
- if (this.client && this._service === null) {
51
- this.initService();
52
- }
53
- if (this.client && this.processId) {
54
- this.loadInitialData();
55
- }
56
- }
57
- super.willUpdate(changedProperties);
58
- }
59
- // --- API Client Instance ---
60
- // If the 'client' property is not provided, the component might fail.
61
- // We no longer manage the client instance internally.
62
- // --- Lifecycle Methods ---
63
- // Called when the component is added to the DOM
64
- connectedCallback() {
65
- super.connectedCallback();
66
- this.validateConfig();
67
- if (this.client) {
68
- this.initService();
69
- if (this.processId) {
70
- this.loadInitialData(); // Fetch assets and blockchains immediately
71
- }
72
- }
73
- }
74
- initService() {
75
- if (!this.client || this.hasConfigError)
76
- return;
77
- this._service = new PaymentService(this.client);
78
- }
79
- get isValidProcessId() {
80
- if (!this.processId)
81
- return false;
82
- const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
83
- return uuidRegex.test(this.processId);
84
- }
85
- validateConfig() {
86
- const key = this.client?.getPublicKey();
87
- const isKeyValid = !!(key && key.startsWith('pk_') && key.length === 35);
88
- if (this.client && !isKeyValid) {
89
- console.error(`PaymentButton Error: Invalid publicKey "${key}". Must start with "pk_" and be 35 characters long.`);
90
- }
91
- this.hasConfigError = !this.client || !isKeyValid;
92
- }
93
- // Called when the component is removed from the DOM
94
- disconnectedCallback() {
95
- super.disconnectedCallback();
96
- // Ensure WebSocket is disconnected if the component is removed
97
- this._service?.disconnectWebSocket();
98
- }
99
- // Replaced by external client initialization
100
- // --- Data Loading ---
101
- async loadInitialData() {
102
- if (!this._service)
103
- return;
104
- this.isLoadingData = true;
105
- this.error = null;
106
- if (!this.processId)
107
- return;
108
- try {
109
- this.assets = await this._service.getAssets();
110
- }
111
- catch (e) {
112
- console.error('Error loading initial payment options:', e);
113
- this.error = { code: 'DATA_LOAD_ERROR', message: 'Could not load payment options.' };
114
- }
115
- finally {
116
- this.isLoadingData = false;
117
- }
118
- }
119
- resetState() {
120
- this.currentStep = ModalStep.SELECT_ASSET;
121
- this.status = 'idle';
122
- this.error = null;
123
- this.selectedAsset = null;
124
- this.selectedNetwork = null;
125
- this.qrCodeUrl = null;
126
- this.paymentAddress = null;
127
- this.paymentUrl = null;
128
- this.qrCodeExpiresAt = null;
129
- }
130
- // --- Event Handlers (Triggered by Child Components) ---
131
- // Triggered by <trigger-button> when clicked
132
- handleOpen() {
133
- this.resetState();
134
- if (this.hasConfigError || !this.client || !this.processId) {
135
- console.error('PaymentButton Error: client and process-id are required and must be valid');
136
- return;
137
- }
138
- if (this.loading)
139
- return;
140
- this.isOpen = true;
141
- }
142
- // Triggered by <payment-modal> requesting to close (X, backdrop, Escape)
143
- handleCloseRequest() {
144
- this.isOpen = false;
145
- // Disconnect WebSocket if the user cancels before payment completion
146
- if (this.currentStep === ModalStep.SHOW_QR && this.status !== 'success' && this.status !== 'error') {
147
- this._service?.disconnectWebSocket();
148
- }
149
- setTimeout(() => this.resetState(), 300);
150
- }
151
- // Triggered by <payment-modal> when an asset is selected
152
- handleAssetSelect(event) {
153
- this.selectedAsset = event.detail.assetId;
154
- this.currentStep = ModalStep.SELECT_NETWORK;
155
- this.error = null;
156
- }
157
- handleExpired(event) {
158
- this.dispatchEvent(new CustomEvent('error', { detail: event.detail.error }));
159
- }
160
- // Triggered by <payment-modal> when a network is selected
161
- async handleInitiatePayment(event) {
162
- if (!this.client || !this.processId)
163
- return;
164
- this.selectedNetwork = event.detail.networkId;
165
- if (!this.selectedAsset || !this.selectedNetwork)
166
- return;
167
- const detail = {
168
- assetId: this.selectedAsset,
169
- networkId: this.selectedNetwork
170
- };
171
- this.dispatchEvent(new CustomEvent('generateQr', { detail }));
172
- this.status = 'loading';
173
- this.currentStep = ModalStep.SHOW_QR;
174
- this.qrCodeUrl = null;
175
- this.paymentAddress = null;
176
- this.error = null;
177
- try {
178
- const qrData = await this._service.fetchQrCodeDetails(detail, {
179
- processId: this.processId,
180
- onSuccess: (response) => {
181
- if (!this.isOpen)
182
- return;
183
- this.status = 'success';
184
- this.currentStep = ModalStep.RESULT;
185
- this.dispatchEvent(new CustomEvent('success', { detail: response }));
186
- },
187
- onError: (error) => {
188
- if (!this.isOpen)
189
- return;
190
- this.status = 'error';
191
- this.error = error;
192
- this.currentStep = ModalStep.RESULT;
193
- this.dispatchEvent(new CustomEvent('error', { detail: error }));
194
- }
195
- });
196
- this.qrCodeUrl = qrData.qrCodeUrl;
197
- this.paymentAddress = qrData.address;
198
- this.paymentUrl = qrData.paymentUrl || null;
199
- this.qrCodeExpiresAt = qrData.expiresAtMs;
200
- this.amount = typeof qrData.amount === 'string' ? parseFloat(qrData.amount) : qrData.amount;
201
- this.status = 'idle';
202
- }
203
- catch (e) {
204
- console.error("Error fetching QR code details:", e);
205
- this.error = { code: 'QR_FETCH_ERROR', message: (e instanceof Error ? e.message : 'Failed to get payment details.') };
206
- this.status = 'error';
207
- this.currentStep = ModalStep.SELECT_NETWORK;
208
- }
209
- }
210
- // Triggered by <payment-modal> "Back" buttons
211
- handleChangeStep(event) {
212
- // Disconnect WebSocket if going back from QR step before completion
213
- if (this.currentStep === ModalStep.SHOW_QR && event.detail !== ModalStep.RESULT) {
214
- this._service?.disconnectWebSocket();
215
- }
216
- this.currentStep = event.detail;
217
- this.status = 'idle'; // Reset status when moving back
218
- this.error = null;
219
- // Clear QR data if moving back from the QR step
220
- if (this.currentStep !== ModalStep.SHOW_QR) {
221
- this.qrCodeUrl = null;
222
- this.paymentAddress = null;
223
- this.paymentUrl = null;
224
- }
225
- }
226
- // --- Render Method ---
227
- render() {
228
- // Renders the trigger button and the modal, passing down all necessary state
229
- return html `
230
- <div id="trigger-wrapper" @click=${this.handleOpen}>
231
- <slot>
232
- <trigger-button
233
- .lang=${this.lang}
234
- .label=${this.label}
235
- .loading=${this.loading ||
236
- (this.isLoadingData && !this.hasConfigError) ||
237
- !this.isValidProcessId}
238
- .hasError=${this.hasConfigError}
239
- ?disabled=${this.disabled ||
240
- this.hasConfigError ||
241
- !this.isValidProcessId}
242
- ></trigger-button>
243
- </slot>
244
- </div>
245
-
246
- <payment-modal
247
- ?isOpen=${this.isOpen}
248
- .barrierDismissible=${this.barrierDismissible}
249
- .lang=${this.lang}
250
- .currentStep=${this.currentStep}
251
- .status=${this.status}
252
- .productTitle=${this.productTitle}
253
- .error=${this.error}
254
- .isLoadingData=${this.isLoadingData}
255
- .assets=${this.assets}
256
- .selectedAsset=${this.selectedAsset}
257
- .selectedNetwork=${this.selectedNetwork}
258
- .qrCodeUrl=${this.qrCodeUrl}
259
- .paymentAddress=${this.paymentAddress}
260
- .amount=${this.amount}
261
- .email=${this.email}
262
- .qrCodeExpiresAt=${this.qrCodeExpiresAt}
263
- .paymentUrl=${this.paymentUrl}
264
- @closeRequest=${this.handleCloseRequest}
265
- @assetSelect=${this.handleAssetSelect}
266
- @networkSelect=${this.handleInitiatePayment}
267
- @changeStep=${this.handleChangeStep}
268
- @expired=${this.handleExpired}
269
- ></payment-modal>
270
- `;
271
- }
272
- };
273
- // --- Styles ---
274
- ApoloPayButton.styles = css `
275
- :host {
276
- display: inline-block;
277
- }
278
-
279
- #trigger-wrapper {
280
- position: relative;
281
- display: inline-block;
282
- cursor: pointer;
283
- }
284
- `;
285
- __decorate([
286
- property({ type: Object })
287
- ], ApoloPayButton.prototype, "client", void 0);
288
- __decorate([
289
- property({ type: String, attribute: 'process-id' })
290
- ], ApoloPayButton.prototype, "processId", void 0);
291
- __decorate([
292
- property({ type: String, attribute: 'product-title' })
293
- ], ApoloPayButton.prototype, "productTitle", void 0);
294
- __decorate([
295
- property({ type: String })
296
- ], ApoloPayButton.prototype, "lang", void 0);
297
- __decorate([
298
- property({ type: String })
299
- ], ApoloPayButton.prototype, "label", void 0);
300
- __decorate([
301
- property({ type: Boolean })
302
- ], ApoloPayButton.prototype, "loading", void 0);
303
- __decorate([
304
- property({ type: Boolean })
305
- ], ApoloPayButton.prototype, "disabled", void 0);
306
- __decorate([
307
- property({
308
- type: Boolean,
309
- attribute: 'barrier-dismissible',
310
- converter: {
311
- fromAttribute: (value) => {
312
- if (value === null)
313
- return false;
314
- return value !== 'false';
315
- },
316
- toAttribute: (value) => value ? '' : null
317
- }
318
- })
319
- ], ApoloPayButton.prototype, "barrierDismissible", void 0);
320
- __decorate([
321
- state()
322
- ], ApoloPayButton.prototype, "isOpen", void 0);
323
- __decorate([
324
- state()
325
- ], ApoloPayButton.prototype, "status", void 0);
326
- __decorate([
327
- state()
328
- ], ApoloPayButton.prototype, "currentStep", void 0);
329
- __decorate([
330
- state()
331
- ], ApoloPayButton.prototype, "selectedAsset", void 0);
332
- __decorate([
333
- state()
334
- ], ApoloPayButton.prototype, "selectedNetwork", void 0);
335
- __decorate([
336
- state()
337
- ], ApoloPayButton.prototype, "qrCodeUrl", void 0);
338
- __decorate([
339
- state()
340
- ], ApoloPayButton.prototype, "qrCodeExpiresAt", void 0);
341
- __decorate([
342
- state()
343
- ], ApoloPayButton.prototype, "paymentAddress", void 0);
344
- __decorate([
345
- state()
346
- ], ApoloPayButton.prototype, "paymentUrl", void 0);
347
- __decorate([
348
- state()
349
- ], ApoloPayButton.prototype, "assets", void 0);
350
- __decorate([
351
- state()
352
- ], ApoloPayButton.prototype, "error", void 0);
353
- __decorate([
354
- state()
355
- ], ApoloPayButton.prototype, "isLoadingData", void 0);
356
- __decorate([
357
- state()
358
- ], ApoloPayButton.prototype, "amount", void 0);
359
- __decorate([
360
- state()
361
- ], ApoloPayButton.prototype, "hasConfigError", void 0);
362
- __decorate([
363
- state()
364
- ], ApoloPayButton.prototype, "email", void 0);
365
- __decorate([
366
- state()
367
- ], ApoloPayButton.prototype, "_service", void 0);
368
- ApoloPayButton = __decorate([
369
- customElement('apolopay-button')
370
- ], ApoloPayButton);
371
- export { ApoloPayButton };
@@ -1,75 +0,0 @@
1
- import { css } from 'lit';
2
- export const modalBaseStyles = css `
3
- /* 1. Definimos las Animaciones */
4
- @keyframes modal-enter {
5
- from { opacity: 0; transform: scale(0.95) translateY(10px); }
6
- to { opacity: 1; transform: scale(1) translateY(0); }
7
- }
8
-
9
- @keyframes modal-exit {
10
- from { opacity: 1; transform: scale(1) translateY(0); }
11
- to { opacity: 0; transform: scale(0.95) translateY(10px); }
12
- }
13
-
14
- @keyframes backdrop-enter {
15
- from { background-color: rgba(0, 0, 0, 0); backdrop-filter: blur(0px); }
16
- to { background-color: rgba(0, 0, 0, 0.6); backdrop-filter: blur(4px); }
17
- }
18
-
19
- @keyframes backdrop-exit {
20
- from { background-color: rgba(0, 0, 0, 0.6); backdrop-filter: blur(4px); }
21
- to { background-color: rgba(0, 0, 0, 0); backdrop-filter: blur(0px); }
22
- }
23
-
24
- /* 2. Estilos Base */
25
- dialog {
26
- border: none;
27
- padding: 0;
28
- margin: auto;
29
-
30
- /* Geometría */
31
- min-width: 320px;
32
- max-width: 420px;
33
- width: 90vw;
34
- border-radius: var(--apolo-radius);
35
-
36
- background-color: var(--apolo-bg);
37
- color: var(--apolo-text);
38
- box-shadow: var(--apolo-shadow);
39
- font-family: var(--apolo-font);
40
-
41
- opacity: 0;
42
- pointer-events: none;
43
- }
44
-
45
- /* 3. Estado: ABIERTO (Animación de Entrada) */
46
- dialog[open] {
47
- opacity: 1;
48
- animation: modal-enter 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
49
- pointer-events: auto;
50
- }
51
-
52
- /* 4. Estado: CERRANDO (Animación de Salida) */
53
- dialog.closing {
54
- /* Sobrescribimos la animación de entrada */
55
- animation: modal-exit 0.15s ease-in forwards;
56
- }
57
-
58
- /* 5. Backdrop (Fondo Oscuro) */
59
- dialog::backdrop {
60
- background-color: rgba(0,0,0,0); /* Invisible por defecto */
61
- }
62
-
63
- dialog[open]::backdrop {
64
- animation: backdrop-enter 0.2s ease-out forwards;
65
- }
66
-
67
- dialog.closing::backdrop {
68
- animation: backdrop-exit 0.15s ease-in forwards;
69
- }
70
-
71
- dialog:not([open])::backdrop {
72
- display: none;
73
- pointer-events: none;
74
- }
75
- `;