@finsweet/webflow-apps-utils 1.0.54 → 1.0.56

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.
@@ -101,8 +101,8 @@
101
101
  const handleGlobalKeyDown = (event: KeyboardEvent) => {
102
102
  if (event.key === 'Escape' && closeOnEscape && isOpen) {
103
103
  event.preventDefault();
104
+ event.stopPropagation();
104
105
  closeDropdown();
105
- // Remove focus to prevent focus ring after closing
106
106
  if (document.activeElement instanceof HTMLElement) {
107
107
  document.activeElement.blur();
108
108
  }
@@ -334,7 +334,7 @@
334
334
  if (!dropdownItems || !target) return instances;
335
335
 
336
336
  if (closeOnClickOutside) {
337
- document?.addEventListener('click', dismissTooltip);
337
+ document?.addEventListener('click', dismissTooltip, true);
338
338
  }
339
339
  instances.push(setupDropdown(target, dropdownItems));
340
340
 
@@ -347,7 +347,7 @@
347
347
  const cleanupDropdownInstances = (instances: DropdownInstance[]) => {
348
348
  instances.forEach((instance) => instance.cleanup());
349
349
  if (closeOnClickOutside) {
350
- document?.removeEventListener('click', dismissTooltip);
350
+ document?.removeEventListener('click', dismissTooltip, true);
351
351
  }
352
352
  };
353
353
 
@@ -386,35 +386,40 @@
386
386
  const showDropdown = () => {
387
387
  if (disabled) return;
388
388
 
389
+ // Show the dropdown FIRST so elements are focusable before calling focus()
389
390
  tooltip.setAttribute('aria-hidden', 'false');
391
+ tooltip.style.display = 'flex';
392
+ isOpen = true;
393
+ isFocused = true;
390
394
  update();
391
395
 
392
396
  const selectedItemButton = tooltip.querySelector(
393
397
  `.dropdown-item[aria-selected="true"]`
394
398
  ) as HTMLElement;
395
399
  const firstItemButton = tooltip.querySelector('.dropdown-item') as HTMLElement;
396
- const itemToFocus = selectedItemButton || firstItemButton;
397
400
 
398
401
  const searchInput = tooltip?.querySelector<HTMLInputElement>('input[type="text"]');
399
- if (searchInput && enableSearch) {
402
+ if (selectedItemButton) {
403
+ selectedItemButton.focus();
404
+ if (lastHoveredItem) {
405
+ lastHoveredItem.classList.remove('hover-state');
406
+ lastHoveredItem.setAttribute('tabindex', '-1');
407
+ }
408
+ lastHoveredItem = selectedItemButton;
409
+ lastHoveredItem.classList.add('hover-state');
410
+ lastHoveredItem.setAttribute('tabindex', '0');
411
+ } else if (searchInput && enableSearch) {
400
412
  searchInput.focus();
401
- } else {
402
- if (itemToFocus) {
403
- itemToFocus.focus();
404
-
405
- if (lastHoveredItem) {
406
- lastHoveredItem.classList.remove('hover-state');
407
- lastHoveredItem.setAttribute('tabindex', '-1');
408
- }
409
- lastHoveredItem = itemToFocus;
410
- lastHoveredItem.classList.add('hover-state');
411
- lastHoveredItem.setAttribute('tabindex', '0');
413
+ } else if (firstItemButton) {
414
+ firstItemButton.focus();
415
+ if (lastHoveredItem) {
416
+ lastHoveredItem.classList.remove('hover-state');
417
+ lastHoveredItem.setAttribute('tabindex', '-1');
412
418
  }
419
+ lastHoveredItem = firstItemButton;
420
+ lastHoveredItem.classList.add('hover-state');
421
+ lastHoveredItem.setAttribute('tabindex', '0');
413
422
  }
414
-
415
- tooltip.style.display = 'flex';
416
- isOpen = true;
417
- isFocused = true;
418
423
  };
419
424
 
420
425
  const options: EventOption[] = [['click', showDropdown]].filter(Boolean) as EventOption[];
@@ -477,10 +482,13 @@
477
482
  </script>
478
483
 
479
484
  {#snippet selectWrapper()}
485
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
486
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
480
487
  <div
481
488
  class="dropdown-wrapper {className}"
482
489
  bind:this={dropdownWrapper}
483
490
  style="{hide ? 'display:none;' : ''} width: {width};"
491
+ onclick={(e) => e.stopPropagation()}
484
492
  >
485
493
  <div
486
494
  class="dropdown"
@@ -509,12 +517,14 @@
509
517
  </div>
510
518
  </div>
511
519
 
520
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
512
521
  <div
513
522
  tabindex={disabled || isOpen ? -1 : 0}
514
523
  class="dropdown-list"
515
524
  class:has-footer={footer}
516
525
  role="listbox"
517
526
  style="width:{dropdownWidth};"
527
+ onclick={(e) => e.stopPropagation()}
518
528
  onkeydown={(e) => {
519
529
  e.stopPropagation();
520
530
  e.preventDefault();
@@ -569,6 +579,9 @@
569
579
  onkeydown={(e) => {
570
580
  e.stopPropagation();
571
581
  e.preventDefault();
582
+ if (e.key === 'Escape' && closeOnEscape) {
583
+ closeDropdown();
584
+ }
572
585
  }}
573
586
  onmouseenter={handleMouseEnter}
574
587
  aria-hidden={!isOpen}
@@ -0,0 +1,18 @@
1
+ import SelectInModalStory from './SelectInModalStory.svelte';
2
+ const meta = {
3
+ title: 'Ui/Select/InModal',
4
+ component: SelectInModalStory,
5
+ parameters: {
6
+ layout: 'centered',
7
+ docs: {
8
+ description: {
9
+ component: 'Testing Modal with Select component to ensure proper event handling'
10
+ }
11
+ }
12
+ },
13
+ tags: ['autodocs']
14
+ };
15
+ export default meta;
16
+ export const Default = {
17
+ args: {}
18
+ };
@@ -0,0 +1,76 @@
1
+ <script lang="ts">
2
+ import { Button } from '../button';
3
+ import Modal from '../modal/Modal.svelte';
4
+ import Select from './Select.svelte';
5
+ import type { SelectOption } from './types';
6
+
7
+ interface Props {
8
+ initialOpen?: boolean;
9
+ }
10
+
11
+ let { initialOpen = false }: Props = $props();
12
+
13
+ let modalOpen = $state(initialOpen);
14
+ let selectedValue = $state<string | null>(null);
15
+
16
+ const options: SelectOption[] = [
17
+ { label: 'Option 1', value: 'option1' },
18
+ { label: 'Option 2', value: 'option2' },
19
+ { label: 'Option 3', value: 'option3' },
20
+ { label: 'Option 4', value: 'option4' },
21
+ { label: 'Option 5', value: 'option5' }
22
+ ];
23
+
24
+ const handleOpenModal = () => {
25
+ modalOpen = true;
26
+ };
27
+
28
+ const handleCloseModal = () => {
29
+ console.log('Modal closing');
30
+ modalOpen = false;
31
+ };
32
+ </script>
33
+
34
+ <div style="padding: 20px;">
35
+ <Button onclick={handleOpenModal}>Open Modal with Select</Button>
36
+
37
+ <Modal
38
+ open={modalOpen}
39
+ onOpenChange={(open) => {
40
+ console.log('Modal onOpenChange:', open);
41
+ modalOpen = open;
42
+ }}
43
+ title="Select Example Inside Modal"
44
+ showFooter={false}
45
+ width="400px"
46
+ closeOnOverlayClick={false}
47
+ closeOnEscape={false}
48
+ >
49
+ <div style="padding: 20px; display: flex; flex-direction: column; gap: 16px;">
50
+ <p style="color: var(--text1); margin: 0;">Test the following behaviors:</p>
51
+ <ul style="color: var(--text2); margin: 0; padding-left: 20px;">
52
+ <li>Click outside the modal (overlay) - modal stays open (disabled)</li>
53
+ <li>Press Escape with select closed - modal stays open (disabled)</li>
54
+ <li>Press Escape with select open - should close select only</li>
55
+ <li>Click outside select but inside modal - should close select only</li>
56
+ <li>Use "Close Modal" button to close the modal</li>
57
+ </ul>
58
+
59
+ <Select
60
+ {options}
61
+ bind:selected={selectedValue}
62
+ defaultText="Choose an option"
63
+ width="100%"
64
+ dropdownWidth="100%"
65
+ />
66
+
67
+ {#if selectedValue}
68
+ <p style="color: var(--text1); margin: 0;">
69
+ Selected: {selectedValue}
70
+ </p>
71
+ {/if}
72
+
73
+ <Button onclick={handleCloseModal} variant="secondary">Close Modal</Button>
74
+ </div>
75
+ </Modal>
76
+ </div>
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ initialOpen?: boolean;
3
+ }
4
+ declare const SelectInModalStory: import("svelte").Component<Props, {}, "">;
5
+ type SelectInModalStory = ReturnType<typeof SelectInModalStory>;
6
+ export default SelectInModalStory;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finsweet/webflow-apps-utils",
3
- "version": "1.0.54",
3
+ "version": "1.0.56",
4
4
  "description": "Shared utilities for Webflow apps",
5
5
  "homepage": "https://github.com/finsweet/webflow-apps-utils",
6
6
  "repository": {