@finsweet/webflow-apps-utils 1.0.53 → 1.0.54

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.
@@ -76,6 +76,14 @@ declare const meta: {
76
76
  control: string;
77
77
  description: string;
78
78
  };
79
+ closeOnEscape: {
80
+ control: string;
81
+ description: string;
82
+ };
83
+ closeOnClickOutside: {
84
+ control: string;
85
+ description: string;
86
+ };
79
87
  };
80
88
  };
81
89
  export default meta;
@@ -90,6 +98,9 @@ export declare const CustomDimensions: Story;
90
98
  export declare const CompactSelect: Story;
91
99
  export declare const WideSelect: Story;
92
100
  export declare const PreventDeselection: Story;
101
+ export declare const DisableEscapeClose: Story;
102
+ export declare const DisableClickOutsideClose: Story;
103
+ export declare const DisableAllCloseBehaviors: Story;
93
104
  export declare const MixedOptions: Story;
94
105
  export declare const TopPlacement: Story;
95
106
  export declare const LeftPlacement: Story;
@@ -123,6 +123,14 @@ const meta = {
123
123
  invalid: {
124
124
  control: 'boolean',
125
125
  description: 'Whether the select is in an invalid state'
126
+ },
127
+ closeOnEscape: {
128
+ control: 'boolean',
129
+ description: 'Whether the dropdown closes when pressing the Escape key'
130
+ },
131
+ closeOnClickOutside: {
132
+ control: 'boolean',
133
+ description: 'Whether the dropdown closes when clicking outside the component'
126
134
  }
127
135
  }
128
136
  };
@@ -241,6 +249,49 @@ export const PreventDeselection = {
241
249
  }
242
250
  }
243
251
  };
252
+ export const DisableEscapeClose = {
253
+ args: {
254
+ options: basicOptions,
255
+ defaultText: 'Press Escape (disabled)',
256
+ closeOnEscape: false
257
+ },
258
+ parameters: {
259
+ docs: {
260
+ description: {
261
+ story: 'Prevents the dropdown from closing when the Escape key is pressed. Users must select an option or click the button again to close.'
262
+ }
263
+ }
264
+ }
265
+ };
266
+ export const DisableClickOutsideClose = {
267
+ args: {
268
+ options: basicOptions,
269
+ defaultText: 'Click outside (disabled)',
270
+ closeOnClickOutside: false
271
+ },
272
+ parameters: {
273
+ docs: {
274
+ description: {
275
+ story: 'Prevents the dropdown from closing when clicking outside the component. Users must select an option or click the button again to close.'
276
+ }
277
+ }
278
+ }
279
+ };
280
+ export const DisableAllCloseBehaviors = {
281
+ args: {
282
+ options: basicOptions,
283
+ defaultText: 'Must select to close',
284
+ closeOnEscape: false,
285
+ closeOnClickOutside: false
286
+ },
287
+ parameters: {
288
+ docs: {
289
+ description: {
290
+ story: 'Disables both Escape key and click outside behaviors. The dropdown can only be closed by selecting an option or clicking the select button. Useful for modal-like contexts where you want to force a selection.'
291
+ }
292
+ }
293
+ }
294
+ };
244
295
  export const MixedOptions = {
245
296
  args: {
246
297
  options: mixedOptions,
@@ -42,6 +42,8 @@
42
42
  alert = null,
43
43
  invalid = false,
44
44
  className = '',
45
+ closeOnEscape = true,
46
+ closeOnClickOutside = true,
45
47
  onchange,
46
48
  children,
47
49
  footer
@@ -94,6 +96,28 @@
94
96
  }
95
97
  });
96
98
 
99
+ // Handle global Escape key when dropdown is open
100
+ $effect(() => {
101
+ const handleGlobalKeyDown = (event: KeyboardEvent) => {
102
+ if (event.key === 'Escape' && closeOnEscape && isOpen) {
103
+ event.preventDefault();
104
+ closeDropdown();
105
+ // Remove focus to prevent focus ring after closing
106
+ if (document.activeElement instanceof HTMLElement) {
107
+ document.activeElement.blur();
108
+ }
109
+ }
110
+ };
111
+
112
+ if (isOpen) {
113
+ document?.addEventListener('keydown', handleGlobalKeyDown);
114
+ }
115
+
116
+ return () => {
117
+ document?.removeEventListener('keydown', handleGlobalKeyDown);
118
+ };
119
+ });
120
+
97
121
  // Computed states
98
122
  let hasAlert = $derived(alert?.message);
99
123
 
@@ -159,6 +183,8 @@
159
183
  * Dismiss dropdown when clicking outside of it.
160
184
  */
161
185
  const dismissTooltip = (event: Event): void => {
186
+ if (!closeOnClickOutside) return;
187
+
162
188
  const isClickInside = dropdownWrapper?.contains(event.target as Node);
163
189
 
164
190
  if (!isClickInside) {
@@ -247,7 +273,13 @@
247
273
  break;
248
274
  }
249
275
  case 'Escape':
250
- closeDropdown();
276
+ if (closeOnEscape) {
277
+ closeDropdown();
278
+ // Remove focus to prevent focus ring after closing
279
+ if (document.activeElement instanceof HTMLElement) {
280
+ document.activeElement.blur();
281
+ }
282
+ }
251
283
  break;
252
284
  }
253
285
 
@@ -301,7 +333,9 @@
301
333
 
302
334
  if (!dropdownItems || !target) return instances;
303
335
 
304
- document?.addEventListener('click', dismissTooltip);
336
+ if (closeOnClickOutside) {
337
+ document?.addEventListener('click', dismissTooltip);
338
+ }
305
339
  instances.push(setupDropdown(target, dropdownItems));
306
340
 
307
341
  return instances;
@@ -312,6 +346,9 @@
312
346
  */
313
347
  const cleanupDropdownInstances = (instances: DropdownInstance[]) => {
314
348
  instances.forEach((instance) => instance.cleanup());
349
+ if (closeOnClickOutside) {
350
+ document?.removeEventListener('click', dismissTooltip);
351
+ }
315
352
  };
316
353
 
317
354
  /**
@@ -679,6 +716,10 @@
679
716
  display: flex;
680
717
  align-items: center;
681
718
  gap: 4px;
719
+ outline: none; /* Remove default focus outline since we have custom focus styling */
720
+ }
721
+ .dropdown:focus-visible {
722
+ outline: none; /* Prevent browser's default focus ring */
682
723
  }
683
724
  .dropdown.disabled {
684
725
  cursor: not-allowed !important;
@@ -712,6 +753,10 @@
712
753
  background: var(--background3);
713
754
  box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.15);
714
755
  z-index: 99999;
756
+ outline: none; /* Remove focus outline */
757
+ }
758
+ .dropdown-list:focus-visible {
759
+ outline: none; /* Prevent browser's default focus ring */
715
760
  }
716
761
 
717
762
  .dropdown-items-scroll {
@@ -778,6 +823,10 @@
778
823
  font-weight: 400;
779
824
  line-height: 16px;
780
825
  border: 1px solid transparent;
826
+ outline: none; /* Remove focus outline */
827
+ }
828
+ .dropdown-item:focus-visible {
829
+ outline: none; /* Prevent browser's default focus ring */
781
830
  }
782
831
  .dropdown-item .icon,
783
832
  .dropdown-list .selected .icon {
@@ -49,6 +49,16 @@ export interface SelectProps {
49
49
  * Alert configuration for showing validation messages
50
50
  */
51
51
  alert?: AlertConfig | null;
52
+ /**
53
+ * If true, the dropdown will close when the Escape key is pressed
54
+ * @default true
55
+ */
56
+ closeOnEscape?: boolean;
57
+ /**
58
+ * If true, the dropdown will close when clicking outside the component
59
+ * @default true
60
+ */
61
+ closeOnClickOutside?: boolean;
52
62
  /**
53
63
  * If true, the select will be invalid
54
64
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finsweet/webflow-apps-utils",
3
- "version": "1.0.53",
3
+ "version": "1.0.54",
4
4
  "description": "Shared utilities for Webflow apps",
5
5
  "homepage": "https://github.com/finsweet/webflow-apps-utils",
6
6
  "repository": {