You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			112 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Svelte
		
	
			
		
		
	
	
			112 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Svelte
		
	
| <script lang="ts">
 | |
|   import { tick, onDestroy } from 'svelte';
 | |
|   import { createEventDispatcher } from 'svelte';
 | |
| 
 | |
|   export let open: boolean = false;
 | |
|   export let ariaLabel: string = 'Diálogo';
 | |
|   export let id: string | undefined;
 | |
| 
 | |
|   const dispatch = createEventDispatcher();
 | |
|   let panelEl: HTMLElement | null = null;
 | |
|   let lastActive: Element | null = null;
 | |
| 
 | |
|   function close() {
 | |
|     open = false;
 | |
|     dispatch('closed');
 | |
|   }
 | |
| 
 | |
|   function handleKeydown(e: KeyboardEvent) {
 | |
|     if (e.key === 'Escape') {
 | |
|       e.preventDefault();
 | |
|       close();
 | |
|     } else if (e.key === 'Tab' && panelEl) {
 | |
|       const focusables = Array.from(
 | |
|         panelEl.querySelectorAll<HTMLElement>(
 | |
|           'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
 | |
|         )
 | |
|       ).filter((el) => el.offsetParent !== null);
 | |
|       if (focusables.length === 0) {
 | |
|         e.preventDefault();
 | |
|         return;
 | |
|       }
 | |
|       const first = focusables[0];
 | |
|       const last = focusables[focusables.length - 1];
 | |
|       const active = document.activeElement as HTMLElement | null;
 | |
|       if (e.shiftKey) {
 | |
|         if (active === first || !panelEl.contains(active)) {
 | |
|           e.preventDefault();
 | |
|           last.focus();
 | |
|         }
 | |
|       } else {
 | |
|         if (active === last) {
 | |
|           e.preventDefault();
 | |
|           first.focus();
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   $: if (open) {
 | |
|     lastActive = document.activeElement;
 | |
|     tick().then(() => {
 | |
|       panelEl?.focus();
 | |
|       document.body.style.overflow = 'hidden';
 | |
|     });
 | |
|   } else {
 | |
|     document.body.style.overflow = '';
 | |
|     if (lastActive instanceof HTMLElement) {
 | |
|       tick().then(() => lastActive?.focus());
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   onDestroy(() => {
 | |
|     document.body.style.overflow = '';
 | |
|   });
 | |
| </script>
 | |
| 
 | |
| {#if open}
 | |
|   <div class="popover-overlay" on:click={close} />
 | |
|   <div
 | |
|     class="popover-panel"
 | |
|     role="dialog"
 | |
|     aria-modal="true"
 | |
|     {id}
 | |
|     aria-label={ariaLabel}
 | |
|     tabindex="-1"
 | |
|     bind:this={panelEl}
 | |
|     on:keydown={handleKeydown}
 | |
|   >
 | |
|     <slot />
 | |
|   </div>
 | |
| {/if}
 | |
| 
 | |
| <style>
 | |
|   .popover-overlay {
 | |
|     position: fixed;
 | |
|     inset: 0;
 | |
|     background: rgba(0, 0, 0, 0.35);
 | |
|     z-index: 1000;
 | |
|   }
 | |
|   .popover-panel {
 | |
|     position: fixed;
 | |
|     z-index: 1001;
 | |
|     top: 50%;
 | |
|     left: 50%;
 | |
|     transform: translate(-50%, -50%);
 | |
|     max-width: min(420px, 92vw);
 | |
|     width: 92vw;
 | |
|     background: var(--color-surface);
 | |
|     color: var(--color-text);
 | |
|     border: 1px solid var(--color-border);
 | |
|     border-radius: 10px;
 | |
|     box-shadow: var(--shadow-lg);
 | |
|     padding: 12px;
 | |
|     outline: none;
 | |
|   }
 | |
|   @media (prefers-color-scheme: dark) {
 | |
|     .popover-overlay {
 | |
|       background: rgba(0, 0, 0, 0.5);
 | |
|     }
 | |
|   }
 | |
| </style>
 |