@warkypublic/svelix 0.1.20 → 0.1.22

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.
@@ -91,6 +91,8 @@
91
91
  let backdropZ = $state(1090);
92
92
  let dropdownZ = $state(1100);
93
93
  let pointerInteractingWithDropdown = $state(false);
94
+ let insideDialog = $state(false);
95
+ const effectiveDisablePortal = $derived(disablePortal || insideDialog);
94
96
  // Plain variable — NOT $state to avoid deep proxy on the complex virtualizer object.
95
97
  let rawVirtualizer: SvelteVirtualizer<
96
98
  HTMLDivElement,
@@ -342,6 +344,24 @@
342
344
  }, 0);
343
345
  }
344
346
 
347
+ function onBackdropPointerDown(e: PointerEvent) {
348
+ // Consume the pointer sequence so it cannot activate elements behind the backdrop.
349
+ e.preventDefault();
350
+ e.stopPropagation();
351
+ store.setOpened(false);
352
+ }
353
+
354
+ function onOptionPointerDown(e: PointerEvent, index: number) {
355
+ // Commit selection before blur/focusout closes the popover on some browsers.
356
+ e.preventDefault();
357
+ e.stopPropagation();
358
+ onOptionSubmit(index);
359
+ }
360
+
361
+ function syncInsideDialogFlag() {
362
+ insideDialog = !!anchorEl?.closest('dialog, [role="dialog"]');
363
+ }
364
+
345
365
  // Public API via bind:this
346
366
  export function clear() {
347
367
  onClear();
@@ -364,7 +384,7 @@
364
384
  }
365
385
 
366
386
  $effect(() => {
367
- if (!$store.opened || disablePortal || disabled) {
387
+ if (!$store.opened || effectiveDisablePortal || disabled) {
368
388
  return;
369
389
  }
370
390
 
@@ -394,7 +414,7 @@
394
414
 
395
415
  const overlay = registerOverlay({
396
416
  kind: "popover",
397
- mount: disablePortal ? "inline" : "portal",
417
+ mount: effectiveDisablePortal ? "inline" : "portal",
398
418
  });
399
419
  backdropZ = overlay.layer.backdrop;
400
420
  dropdownZ = overlay.layer.content;
@@ -405,6 +425,32 @@
405
425
  dropdownZ = 1100;
406
426
  };
407
427
  });
428
+
429
+ $effect(() => {
430
+ anchorEl;
431
+ storeOpened;
432
+ syncInsideDialogFlag();
433
+ });
434
+
435
+ $effect(() => {
436
+ if (!$store.opened || !effectiveDisablePortal || disabled || typeof window === "undefined") {
437
+ return;
438
+ }
439
+
440
+ const onDocumentPointerDown = (event: PointerEvent) => {
441
+ const target = event.target as Node | null;
442
+ if (!target) return;
443
+
444
+ const insideAnchor = !!anchorEl?.contains(target);
445
+ const insideDropdown = !!dropdownEl?.contains(target);
446
+ if (!insideAnchor && !insideDropdown) {
447
+ store.setOpened(false);
448
+ }
449
+ };
450
+
451
+ window.addEventListener("pointerdown", onDocumentPointerDown, true);
452
+ return () => window.removeEventListener("pointerdown", onDocumentPointerDown, true);
453
+ });
408
454
  </script>
409
455
 
410
456
  <div
@@ -444,28 +490,30 @@
444
490
  }}
445
491
  />
446
492
 
447
- <Portal disabled={disablePortal}>
493
+ <Portal disabled={effectiveDisablePortal}>
448
494
  {#if $store.opened}
449
- <!-- svelte-ignore a11y_click_events_have_key_events -->
450
- <!-- svelte-ignore a11y_no_static_element_interactions -->
451
- <div
452
- class="fixed inset-0"
453
- onclick={() => store.setOpened(false)}
454
- style:z-index={backdropZ}
455
- ></div>
495
+ {#if !effectiveDisablePortal}
496
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
497
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
498
+ <div
499
+ class="fixed inset-0"
500
+ onpointerdown={onBackdropPointerDown}
501
+ style:z-index={backdropZ}
502
+ ></div>
503
+ {/if}
456
504
 
457
505
  <div
458
506
  bind:this={dropdownEl}
459
- class={`${disablePortal
507
+ class={`${effectiveDisablePortal
460
508
  ? "absolute left-0 right-0 top-full mt-1"
461
509
  : "fixed"} card bg-surface-50-950 shadow-lg border border-surface-300-700 overflow-hidden`}
462
510
  role="listbox"
463
511
  aria-label={label ?? "Options"}
464
512
  onpointerdown={markDropdownPointerInteraction}
465
513
  style:z-index={dropdownZ}
466
- style:top={!disablePortal ? `${popupTop}px` : undefined}
467
- style:left={!disablePortal ? `${popupLeft}px` : undefined}
468
- style:width={!disablePortal ? `${popupWidth}px` : undefined}
514
+ style:top={!effectiveDisablePortal ? `${popupTop}px` : undefined}
515
+ style:left={!effectiveDisablePortal ? `${popupLeft}px` : undefined}
516
+ style:width={!effectiveDisablePortal ? `${popupWidth}px` : undefined}
469
517
  >
470
518
  {#if $store.boxerData.length > 0}
471
519
  <div
@@ -496,7 +544,7 @@
496
544
  role="option"
497
545
  aria-selected={isSelected}
498
546
  tabindex="-1"
499
- onclick={() => onOptionSubmit(vRow.index)}
547
+ onpointerdown={(e) => onOptionPointerDown(e, vRow.index)}
500
548
  onkeydown={(e) => onDropdownItemKeydown(e, vRow.index)}
501
549
  >
502
550
  {#if multiSelect}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warkypublic/svelix",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "Svelte 5 component library with Skeleton UI and Tailwind CSS",
5
5
  "license": "Apache-2.0",
6
6
  "exports": {
@@ -83,7 +83,7 @@
83
83
  },
84
84
  "scripts": {
85
85
  "dev": "vite dev",
86
- "build": "vite build",
86
+ "build": "vite build && pnpm run package",
87
87
  "preview": "vite preview",
88
88
  "package": "svelte-kit sync && svelte-package && mkdir -p dist/css && cp src/lib/css/tailwind-source.css dist/css/tailwind-source.css && publint",
89
89
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",