@moni-labs/moni-ui 0.4.14 → 0.4.17
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/custom-elements.json +10 -0
- package/dist/browser/moni-ui.iife.min.js +6 -1
- package/dist/browser/moni-ui.iife.min.js.br +0 -0
- package/dist/browser/moni-ui.iife.min.js.gz +0 -0
- package/dist/browser/moni-ui.iife.min.js.map +1 -1
- package/dist/browser/moni-ui.min.js +75 -23
- package/dist/browser/moni-ui.min.js.br +0 -0
- package/dist/browser/moni-ui.min.js.gz +0 -0
- package/dist/browser/moni-ui.min.js.map +1 -1
- package/dist/cdn/components/moni-bottom-sheet.js +6 -1
- package/dist/cdn/importmap.json +98 -98
- package/dist/components/moni-bottom-sheet.d.ts +19 -5
- package/dist/components/moni-bottom-sheet.d.ts.map +1 -1
- package/dist/components/moni-bottom-sheet.js +169 -30
- package/docs/api.json +13 -2
- package/docs/assets/moni-color-field/shape--square.png +0 -0
- package/docs/assets/moni-file-field/shape--square.png +0 -0
- package/docs/components/bottom-sheet.md +2 -1
- package/package.json +1 -1
- package/src/components/moni-bottom-sheet.test.ts +393 -0
- package/src/components/moni-bottom-sheet.ts +175 -29
|
@@ -153,11 +153,32 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
153
153
|
this._originalSibling = null;
|
|
154
154
|
this._isDragging = false;
|
|
155
155
|
this._startY = 0;
|
|
156
|
-
this.
|
|
156
|
+
this._lastDeltaY = 0;
|
|
157
157
|
this._sheetHeight = 0;
|
|
158
158
|
this._defaultHeight = 0;
|
|
159
159
|
this._justDragged = false;
|
|
160
160
|
this._skipRequestClose = false;
|
|
161
|
+
this._isClosing = false;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Abre el bottom sheet.
|
|
165
|
+
*/
|
|
166
|
+
show() {
|
|
167
|
+
this._isClosing = false;
|
|
168
|
+
this._isDragging = false;
|
|
169
|
+
this._lastDeltaY = 0;
|
|
170
|
+
if (this._dialog) {
|
|
171
|
+
this._dialog.classList.remove('dragging', 'expanded');
|
|
172
|
+
this._dialog.style.transform = '';
|
|
173
|
+
this._dialog.style.height = '';
|
|
174
|
+
}
|
|
175
|
+
this.open = true;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Cierra el bottom sheet ejecutando la animación fluida hacia abajo.
|
|
179
|
+
*/
|
|
180
|
+
close() {
|
|
181
|
+
return this._animateAndClose();
|
|
161
182
|
}
|
|
162
183
|
willUpdate(changed) {
|
|
163
184
|
if (changed.has('open') && this.open !== changed.get('open')) {
|
|
@@ -177,12 +198,18 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
177
198
|
}
|
|
178
199
|
}
|
|
179
200
|
async _animateAndClose() {
|
|
201
|
+
if (this._isClosing)
|
|
202
|
+
return false;
|
|
203
|
+
if (!this.open)
|
|
204
|
+
return false;
|
|
205
|
+
this._isClosing = true;
|
|
180
206
|
this._skipRequestClose = true;
|
|
181
207
|
const allowed = emitMoniEvent(this, 'moni-request-close', { cancelable: true });
|
|
182
208
|
this._skipRequestClose = false;
|
|
183
|
-
if (!allowed)
|
|
209
|
+
if (!allowed) {
|
|
210
|
+
this._isClosing = false;
|
|
184
211
|
return false;
|
|
185
|
-
|
|
212
|
+
}
|
|
186
213
|
emitMoniEvent(this, 'moni-close');
|
|
187
214
|
this._dialog.classList.remove('expanded');
|
|
188
215
|
this._dialog.style.transform = 'translateY(100%)';
|
|
@@ -195,10 +222,18 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
195
222
|
this._dialog.addEventListener('transitionend', onEnd);
|
|
196
223
|
setTimeout(onEnd, 300);
|
|
197
224
|
});
|
|
198
|
-
this.
|
|
225
|
+
this._isClosing = false;
|
|
226
|
+
this._isDragging = false;
|
|
227
|
+
this._lastDeltaY = 0;
|
|
228
|
+
if (this._dialog) {
|
|
229
|
+
this._dialog.classList.remove('dragging', 'expanded');
|
|
230
|
+
this._dialog.style.transform = '';
|
|
231
|
+
this._dialog.style.height = '';
|
|
232
|
+
}
|
|
199
233
|
this._skipRequestClose = true;
|
|
200
234
|
this.open = false;
|
|
201
235
|
this._skipRequestClose = false;
|
|
236
|
+
this.dispatchEvent(new Event('close', { bubbles: true, composed: true }));
|
|
202
237
|
emitMoniEvent(this, 'moni-closed');
|
|
203
238
|
return true;
|
|
204
239
|
}
|
|
@@ -234,14 +269,22 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
234
269
|
this._isDragging = true;
|
|
235
270
|
this._startY = e.clientY; // Almacenamos la coordenada inicial Y para calcular el delta luego
|
|
236
271
|
this._sheetHeight = this._dialog.getBoundingClientRect().height; // Capturamos la altura actual
|
|
237
|
-
this.
|
|
272
|
+
this._lastDeltaY = 0;
|
|
238
273
|
const isExpanded = this._dialog.classList.contains('expanded');
|
|
239
274
|
// Si no estaba expandido previamente, guardamos su altura por defecto para usarla como punto de retorno.
|
|
240
275
|
if (!isExpanded) {
|
|
241
276
|
this._defaultHeight = this._sheetHeight;
|
|
242
277
|
}
|
|
278
|
+
else if (!this._defaultHeight) {
|
|
279
|
+
this._defaultHeight = Math.min(this._sheetHeight * 0.6, 384);
|
|
280
|
+
}
|
|
243
281
|
// setPointerCapture enruta todos los eventos subsecuentes a este target
|
|
244
|
-
|
|
282
|
+
try {
|
|
283
|
+
target.setPointerCapture(e.pointerId);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
// Ignorar si el navegador ya maneja el puntero de forma nativa
|
|
287
|
+
}
|
|
245
288
|
this._dialog.classList.add('dragging'); // Desactiva las transiciones CSS para un seguimiento suave 1:1
|
|
246
289
|
}
|
|
247
290
|
/**
|
|
@@ -254,6 +297,7 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
254
297
|
return;
|
|
255
298
|
// Diferencia en píxeles desde donde se inició el click
|
|
256
299
|
const deltaY = e.clientY - this._startY;
|
|
300
|
+
this._lastDeltaY = deltaY;
|
|
257
301
|
const isExpanded = this._dialog.classList.contains('expanded');
|
|
258
302
|
const maxHeight = this._getMaxHeightPx();
|
|
259
303
|
if (isExpanded) {
|
|
@@ -273,7 +317,6 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
273
317
|
else {
|
|
274
318
|
if (deltaY > 0) {
|
|
275
319
|
// Arrastrando hacia abajo desde su estado inicial: lo empujamos visualmente hacia abajo (translate)
|
|
276
|
-
this._currentTranslationY = deltaY;
|
|
277
320
|
this._dialog.style.transform = `translateY(${deltaY}px)`;
|
|
278
321
|
this._dialog.style.height = '';
|
|
279
322
|
}
|
|
@@ -288,16 +331,25 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
288
331
|
/**
|
|
289
332
|
* Finaliza la interacción de arrastre, evaluando si el bottom-sheet debe
|
|
290
333
|
* expandirse, regresar a su estado original o cerrarse completamente según la
|
|
291
|
-
* inercia o la
|
|
334
|
+
* inercia, la distancia arrastrada o si se soltó fuera/hacia la parte inferior de la pantalla.
|
|
292
335
|
*/
|
|
293
336
|
_onPointerUp(e) {
|
|
294
337
|
if (!this._isDragging)
|
|
295
338
|
return;
|
|
296
339
|
const target = e.target;
|
|
297
|
-
|
|
340
|
+
try {
|
|
341
|
+
if (target && target.hasPointerCapture?.(e.pointerId)) {
|
|
342
|
+
target.releasePointerCapture(e.pointerId);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// Ignorar si la captura ya fue liberada o perdida por el navegador
|
|
347
|
+
}
|
|
298
348
|
this._isDragging = false;
|
|
299
349
|
this._dialog.classList.remove('dragging'); // Reactivamos las transiciones CSS
|
|
300
|
-
|
|
350
|
+
// Evaluamos la posición exacta al momento de soltar
|
|
351
|
+
const hasValidY = typeof e.clientY === 'number' && e.clientY > 0;
|
|
352
|
+
const deltaY = hasValidY ? (e.clientY - this._startY) : this._lastDeltaY;
|
|
301
353
|
// Si se movió más de 5px, bloqueamos clics accidentales para evitar disparar eventos subyacentes
|
|
302
354
|
if (Math.abs(deltaY) > 5) {
|
|
303
355
|
this._justDragged = true;
|
|
@@ -307,11 +359,18 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
307
359
|
}
|
|
308
360
|
const isExpanded = this._dialog.classList.contains('expanded');
|
|
309
361
|
const maxHeight = this._getMaxHeightPx();
|
|
362
|
+
const defaultHeight = this._defaultHeight || (this._sheetHeight * 0.6) || 300;
|
|
363
|
+
// Solo se considera off-screen si al SOLTAR el puntero se encuentra en o más allá del límite inferior
|
|
364
|
+
const isOffScreenBottom = hasValidY && typeof window !== 'undefined' && e.clientY >= (window.innerHeight - 30);
|
|
310
365
|
if (isExpanded) {
|
|
311
366
|
// Evaluamos la altura final tras soltar el ratón/dedo
|
|
312
367
|
const finalHeight = this._sheetHeight - deltaY;
|
|
313
|
-
|
|
314
|
-
|
|
368
|
+
const shouldClose = isOffScreenBottom ||
|
|
369
|
+
deltaY > this._sheetHeight * 0.4 ||
|
|
370
|
+
finalHeight < (defaultHeight - 80) ||
|
|
371
|
+
finalHeight < 100;
|
|
372
|
+
if (shouldClose) {
|
|
373
|
+
// Si se soltó significativamente hacia abajo o fuera de pantalla, lo cerramos
|
|
315
374
|
this._animateAndClose().then((closed) => {
|
|
316
375
|
if (!closed) {
|
|
317
376
|
// Si se canceló el evento, regresamos a su lugar
|
|
@@ -320,20 +379,20 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
320
379
|
}
|
|
321
380
|
});
|
|
322
381
|
}
|
|
323
|
-
else if (finalHeight < (maxHeight +
|
|
382
|
+
else if (finalHeight < (maxHeight + defaultHeight) / 2) {
|
|
324
383
|
// Si se soltó a medio camino, lo colapsamos a su altura normal
|
|
325
384
|
this._dialog.classList.remove('expanded');
|
|
326
385
|
this._dialog.style.transform = '';
|
|
327
386
|
this._dialog.style.height = '';
|
|
328
387
|
}
|
|
329
388
|
else {
|
|
330
|
-
// Si no se arrastró
|
|
389
|
+
// Si se subió de nuevo hacia arriba o no se arrastró suficiente, retorna a estado expandido (snap back)
|
|
331
390
|
this._dialog.style.transform = '';
|
|
332
391
|
this._dialog.style.height = '';
|
|
333
392
|
}
|
|
334
393
|
}
|
|
335
394
|
else {
|
|
336
|
-
if (deltaY < 0) {
|
|
395
|
+
if (deltaY < 0 && !isOffScreenBottom) {
|
|
337
396
|
// Evaluamos el arrastre hacia arriba desde el estado colapsado
|
|
338
397
|
const finalHeight = this._sheetHeight - deltaY;
|
|
339
398
|
if (finalHeight > this._sheetHeight + 80) {
|
|
@@ -344,8 +403,11 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
344
403
|
this._dialog.style.height = '';
|
|
345
404
|
}
|
|
346
405
|
else {
|
|
347
|
-
// Evaluamos el
|
|
348
|
-
|
|
406
|
+
// Evaluamos si al soltar superó el umbral de descarte o se soltó fuera de la pantalla
|
|
407
|
+
const threshold = Math.min(80, this._sheetHeight * 0.25);
|
|
408
|
+
const shouldClose = isOffScreenBottom ||
|
|
409
|
+
deltaY > threshold;
|
|
410
|
+
if (shouldClose) {
|
|
349
411
|
this._animateAndClose().then((closed) => {
|
|
350
412
|
if (!closed) {
|
|
351
413
|
this._dialog.style.transform = '';
|
|
@@ -353,26 +415,31 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
353
415
|
});
|
|
354
416
|
}
|
|
355
417
|
else {
|
|
356
|
-
//
|
|
418
|
+
// Si el usuario bajó pero subió de nuevo antes de soltar (o no superó el umbral), regresa a abierto
|
|
357
419
|
this._dialog.style.transform = '';
|
|
358
420
|
}
|
|
359
421
|
}
|
|
360
422
|
}
|
|
423
|
+
this._lastDeltaY = 0;
|
|
361
424
|
}
|
|
362
425
|
/**
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
426
|
+
* Maneja la cancelación del puntero o interrupción del SO.
|
|
427
|
+
* Si el usuario ya lo había arrastrado hacia abajo o hacia fuera de la pantalla,
|
|
428
|
+
* evalúa el cierre en lugar de simplemente cancelar y dejar la hoja colgada.
|
|
366
429
|
*/
|
|
367
430
|
_onPointerCancel(e) {
|
|
368
|
-
if (
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
431
|
+
if (this._isDragging) {
|
|
432
|
+
this._onPointerUp(e);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Se ejecuta cuando se pierde la captura de puntero (por ejemplo al arrastrar fuera
|
|
437
|
+
* del viewport del navegador o perder el foco de la ventana).
|
|
438
|
+
*/
|
|
439
|
+
_onLostPointerCapture(e) {
|
|
440
|
+
if (this._isDragging) {
|
|
441
|
+
this._onPointerUp(e);
|
|
442
|
+
}
|
|
376
443
|
}
|
|
377
444
|
/**
|
|
378
445
|
* Handler de clicks en la superficie del modal (Backdrop).
|
|
@@ -410,6 +477,14 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
410
477
|
}
|
|
411
478
|
if (changed.has('open')) {
|
|
412
479
|
if (this.open) {
|
|
480
|
+
this._isClosing = false;
|
|
481
|
+
this._isDragging = false;
|
|
482
|
+
this._lastDeltaY = 0;
|
|
483
|
+
if (this._dialog) {
|
|
484
|
+
this._dialog.classList.remove('dragging', 'expanded');
|
|
485
|
+
this._dialog.style.transform = '';
|
|
486
|
+
this._dialog.style.height = '';
|
|
487
|
+
}
|
|
413
488
|
if (changed.get('open') !== undefined) {
|
|
414
489
|
emitMoniEvent(this, 'moni-open');
|
|
415
490
|
}
|
|
@@ -424,8 +499,13 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
424
499
|
}
|
|
425
500
|
}
|
|
426
501
|
else {
|
|
502
|
+
this._isClosing = false;
|
|
503
|
+
this._isDragging = false;
|
|
504
|
+
this._lastDeltaY = 0;
|
|
427
505
|
if (this._dialog) {
|
|
428
|
-
this._dialog.classList.remove('expanded');
|
|
506
|
+
this._dialog.classList.remove('expanded', 'dragging');
|
|
507
|
+
this._dialog.style.transform = '';
|
|
508
|
+
this._dialog.style.height = '';
|
|
429
509
|
}
|
|
430
510
|
if (this.positioning === 'body' && this._originalParent && this.parentNode === document.body) {
|
|
431
511
|
this._originalParent.insertBefore(this, this._originalSibling);
|
|
@@ -433,6 +513,60 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
433
513
|
this._originalSibling = null;
|
|
434
514
|
}
|
|
435
515
|
}
|
|
516
|
+
// Sincroniza el estado declarativo (open/modal) con la API
|
|
517
|
+
// imperativa del <dialog> nativo. Sin esto el ?open del template
|
|
518
|
+
// solo abre el diálogo como no-modal: sin top layer, sin
|
|
519
|
+
// ::backdrop y sin bloqueo del fondo. Mismo patrón que
|
|
520
|
+
// moni-dialog y moni-side-sheet, con guards typeof para
|
|
521
|
+
// entornos sin HTMLDialogElement (jsdom).
|
|
522
|
+
//
|
|
523
|
+
// NOTA: no se lee dialog.open como guarda porque render() ya
|
|
524
|
+
// aplicó ?open antes de updated() y siempre reflejaría el nuevo
|
|
525
|
+
// estado. Se decide por la transición (changed): close() sobre
|
|
526
|
+
// un diálogo ya cerrado es no-op por spec, igual que showModal()
|
|
527
|
+
// sobre uno ya modal lanza InvalidStateError (tolerado).
|
|
528
|
+
if (this._dialog) {
|
|
529
|
+
const nativo = this._dialog;
|
|
530
|
+
if (this.open) {
|
|
531
|
+
if (this.modal && typeof nativo.showModal === 'function') {
|
|
532
|
+
let yaEsModal = false;
|
|
533
|
+
try {
|
|
534
|
+
yaEsModal = this._dialog.matches(':modal');
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
yaEsModal = false;
|
|
538
|
+
}
|
|
539
|
+
if (!yaEsModal) {
|
|
540
|
+
try {
|
|
541
|
+
nativo.showModal();
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
// Otro actor ya lo abrió.
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
else if (!this.modal && typeof nativo.show === 'function') {
|
|
549
|
+
try {
|
|
550
|
+
nativo.show();
|
|
551
|
+
}
|
|
552
|
+
catch {
|
|
553
|
+
// Otro actor ya lo abrió.
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
else if ((this.modal && typeof nativo.showModal !== 'function') ||
|
|
557
|
+
(!this.modal && typeof nativo.show !== 'function')) {
|
|
558
|
+
this._dialog.open = true;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else if (typeof nativo.close === 'function') {
|
|
562
|
+
try {
|
|
563
|
+
nativo.close();
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
// Ya estaba cerrado.
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
436
570
|
}
|
|
437
571
|
}
|
|
438
572
|
/**
|
|
@@ -486,6 +620,10 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
486
620
|
transform: translateY(0);
|
|
487
621
|
}
|
|
488
622
|
|
|
623
|
+
dialog::backdrop {
|
|
624
|
+
background-color: rgb(0 0 0 / 0.5);
|
|
625
|
+
}
|
|
626
|
+
|
|
489
627
|
dialog.fixed {
|
|
490
628
|
position: fixed;
|
|
491
629
|
inset: auto 0 0 0;
|
|
@@ -608,6 +746,7 @@ let MoniBottomSheet = class MoniBottomSheet extends MoniElement {
|
|
|
608
746
|
@pointermove=${this._onPointerMove}
|
|
609
747
|
@pointerup=${this._onPointerUp}
|
|
610
748
|
@pointercancel=${this._onPointerCancel}
|
|
749
|
+
@lostpointercapture=${this._onLostPointerCapture}
|
|
611
750
|
>
|
|
612
751
|
${this.handle ? html `<div class="handle" aria-hidden="true"></div>` : nothing}
|
|
613
752
|
<header part="header">
|
package/docs/api.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "@moni-labs/moni-ui",
|
|
3
|
-
"generatedAt": "2026-
|
|
3
|
+
"generatedAt": "2026-09-06T10:02:20.078Z",
|
|
4
4
|
"componentCount": 49,
|
|
5
5
|
"components": [
|
|
6
6
|
{
|
|
@@ -327,7 +327,18 @@
|
|
|
327
327
|
"reflect": true
|
|
328
328
|
}
|
|
329
329
|
],
|
|
330
|
-
"methods": [
|
|
330
|
+
"methods": [
|
|
331
|
+
{
|
|
332
|
+
"name": "show",
|
|
333
|
+
"signature": "show()",
|
|
334
|
+
"description": "Abre el bottom sheet."
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"name": "close",
|
|
338
|
+
"signature": "close()",
|
|
339
|
+
"description": "Cierra el bottom sheet ejecutando la animación fluida hacia abajo."
|
|
340
|
+
}
|
|
341
|
+
],
|
|
331
342
|
"events": [],
|
|
332
343
|
"slots": [
|
|
333
344
|
{
|
|
Binary file
|
|
Binary file
|
|
@@ -102,7 +102,8 @@ No declara eventos propios.
|
|
|
102
102
|
|
|
103
103
|
## Métodos públicos
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
- `show()` — Abre el bottom sheet.
|
|
106
|
+
- `close()` — Cierra el bottom sheet ejecutando la animación fluida hacia abajo.
|
|
106
107
|
|
|
107
108
|
## CSS Parts
|
|
108
109
|
|