@finsweet/webflow-apps-utils 1.0.56 → 1.0.58

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.
@@ -78,7 +78,10 @@ export const RawContentWithComment = {
78
78
  content: '<script>\n // Your custom code here\n console.log("Hello from script!");\n</script>',
79
79
  title: 'Script with Comment',
80
80
  raw: true,
81
- comment: 'Add this script to your site'
81
+ comment: 'Add this script to your site',
82
+ onCopy: (content) => {
83
+ console.log('Copied content:', content);
84
+ }
82
85
  }
83
86
  };
84
87
  export const MultilineContent = {
@@ -84,6 +84,18 @@ declare const meta: {
84
84
  control: string;
85
85
  description: string;
86
86
  };
87
+ onOpen: {
88
+ action: string;
89
+ description: string;
90
+ };
91
+ itemsDisabled: {
92
+ control: string;
93
+ description: string;
94
+ };
95
+ itemsDisabledMessage: {
96
+ control: string;
97
+ description: string;
98
+ };
87
99
  };
88
100
  };
89
101
  export default meta;
@@ -119,4 +131,7 @@ export declare const InvalidState: Story;
119
131
  export declare const InvalidWithAlert: Story;
120
132
  export declare const ValidationStates: Story;
121
133
  export declare const FormValidationExample: Story;
134
+ export declare const ItemsDisabled: Story;
135
+ export declare const ItemsDisabledWithSearch: Story;
136
+ export declare const AsyncRefreshOnOpen: Story;
122
137
  export declare const WithFooter: Story;
@@ -1,5 +1,6 @@
1
1
  import { CheckIcon, UndoIcon } from '../../icons';
2
2
  import Select from './Select.svelte';
3
+ import SelectItemsDisabledStory from './SelectItemsDisabledStory.svelte';
3
4
  import SelectWithFooterStory from './SelectWithFooterStory.svelte';
4
5
  // Mock options for stories
5
6
  const basicOptions = [
@@ -131,6 +132,18 @@ const meta = {
131
132
  closeOnClickOutside: {
132
133
  control: 'boolean',
133
134
  description: 'Whether the dropdown closes when clicking outside the component'
135
+ },
136
+ onOpen: {
137
+ action: 'onOpen',
138
+ description: 'Callback fired when the dropdown opens'
139
+ },
140
+ itemsDisabled: {
141
+ control: 'boolean',
142
+ description: 'Disables all dropdown items and search while the dropdown remains open'
143
+ },
144
+ itemsDisabledMessage: {
145
+ control: 'text',
146
+ description: 'Overlay message shown when itemsDisabled is true'
134
147
  }
135
148
  }
136
149
  };
@@ -619,6 +632,61 @@ export const FormValidationExample = {
619
632
  }
620
633
  }
621
634
  };
635
+ // Items disabled
636
+ export const ItemsDisabled = {
637
+ args: {
638
+ options: basicOptions,
639
+ itemsDisabled: true,
640
+ itemsDisabledMessage: 'Items are currently unavailable',
641
+ defaultText: 'Items disabled'
642
+ },
643
+ parameters: {
644
+ docs: {
645
+ description: {
646
+ story: 'Disables all dropdown items and search input. The dropdown still opens and closes normally, but no selection or filtering can occur.'
647
+ }
648
+ }
649
+ }
650
+ };
651
+ export const ItemsDisabledWithSearch = {
652
+ args: {
653
+ options: basicOptions,
654
+ itemsDisabled: true,
655
+ enableSearch: true,
656
+ defaultText: 'Search disabled too'
657
+ },
658
+ parameters: {
659
+ docs: {
660
+ description: {
661
+ story: 'When itemsDisabled is true, the search input is also disabled along with all items.'
662
+ }
663
+ }
664
+ }
665
+ };
666
+ // Async refresh on open example
667
+ export const AsyncRefreshOnOpen = {
668
+ render: () => ({
669
+ Component: SelectItemsDisabledStory,
670
+ props: {
671
+ options: basicOptions,
672
+ defaultText: 'Open to refresh',
673
+ dropdownWidth: '250px',
674
+ dropdownHeight: '200px',
675
+ enableSearch: true
676
+ }
677
+ }),
678
+ args: {
679
+ options: basicOptions,
680
+ defaultText: 'Open to refresh'
681
+ },
682
+ parameters: {
683
+ docs: {
684
+ description: {
685
+ story: 'Demonstrates the `onOpen` + `itemsDisabled` pattern: opening the dropdown triggers a simulated 2s async refresh. Items and search are disabled during the refresh, then re-enabled with fresh data.'
686
+ }
687
+ }
688
+ }
689
+ };
622
690
  // Footer snippet example
623
691
  const providerOptions = [
624
692
  { label: 'Facebook', value: 'facebook' },
@@ -44,6 +44,9 @@
44
44
  className = '',
45
45
  closeOnEscape = true,
46
46
  closeOnClickOutside = true,
47
+ onOpen,
48
+ itemsDisabled = false,
49
+ itemsDisabledMessage = '',
47
50
  onchange,
48
51
  children,
49
52
  footer
@@ -164,7 +167,7 @@
164
167
  * Handles the option selection.
165
168
  */
166
169
  const handleSelect = (value: string, label = defaultText, element: HTMLButtonElement) => {
167
- if (disabled) return;
170
+ if (disabled || itemsDisabled) return;
168
171
  updateActiveElement(element);
169
172
 
170
173
  if (selected === value && !preventNoSelection) {
@@ -256,18 +259,21 @@
256
259
  const handleKeyDown = (event: KeyboardEvent): void => {
257
260
  if (!isOpen || !dropdownItems) return;
258
261
 
259
- const items = Array.from(dropdownItems.querySelectorAll('.dropdown-item'));
262
+ const items = Array.from(dropdownItems.querySelectorAll('.dropdown-item:not(.items-disabled)'));
260
263
  let currentIndex = lastHoveredItem ? items.indexOf(lastHoveredItem) : -1;
261
264
  let newIndex = -1;
262
265
 
263
266
  switch (event.key) {
264
267
  case 'ArrowDown':
268
+ if (itemsDisabled) break;
265
269
  newIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
266
270
  break;
267
271
  case 'ArrowUp':
272
+ if (itemsDisabled) break;
268
273
  newIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
269
274
  break;
270
275
  case 'Enter': {
276
+ if (itemsDisabled) break;
271
277
  const selectedItem = items[currentIndex] as HTMLButtonElement;
272
278
  selectedItem.click();
273
279
  break;
@@ -385,6 +391,7 @@
385
391
 
386
392
  const showDropdown = () => {
387
393
  if (disabled) return;
394
+ if (isOpen) return;
388
395
 
389
396
  // Show the dropdown FIRST so elements are focusable before calling focus()
390
397
  tooltip.setAttribute('aria-hidden', 'false');
@@ -393,6 +400,8 @@
393
400
  isFocused = true;
394
401
  update();
395
402
 
403
+ onOpen?.();
404
+
396
405
  const selectedItemButton = tooltip.querySelector(
397
406
  `.dropdown-item[aria-selected="true"]`
398
407
  ) as HTMLElement;
@@ -536,8 +545,20 @@
536
545
  <div
537
546
  class="dropdown-items-scroll"
538
547
  style="max-height:{dropdownHeight};"
548
+ class:disabled={itemsDisabled}
539
549
  onmouseleave={clearHoverState}
540
550
  >
551
+ {#if itemsDisabled && itemsDisabledMessage}
552
+ <div class="items-disabled-overlay" role="status">
553
+ <Text
554
+ loading
555
+ class="items-disabled-text"
556
+ label={itemsDisabledMessage}
557
+ fontSize="normal"
558
+ fontColor="var(--text2)"
559
+ />
560
+ </div>
561
+ {/if}
541
562
  {#if selectedLabel}
542
563
  <div class="selected">
543
564
  <div class="label">
@@ -551,6 +572,7 @@
551
572
  <input
552
573
  type="text"
553
574
  placeholder="Search"
575
+ disabled={itemsDisabled}
554
576
  oninput={(e) => {
555
577
  e.stopPropagation();
556
578
  e.preventDefault();
@@ -569,11 +591,14 @@
569
591
  aria-selected={value === selected && selected?.trim() !== '' ? 'true' : 'false'}
570
592
  id={`${itemId}-list-${indexId}-${id}`}
571
593
  data-value={value}
572
- class="dropdown-item {isDisabled ? 'disabled' : ''} {className}"
594
+ class="dropdown-item {isDisabled || itemsDisabled ? 'disabled' : ''} {itemsDisabled
595
+ ? 'items-disabled'
596
+ : ''} {className}"
573
597
  role="option"
598
+ aria-disabled={isDisabled || itemsDisabled}
574
599
  onclick={(e) => {
575
600
  e.stopPropagation();
576
- if (isDisabled) return;
601
+ if (isDisabled || itemsDisabled) return;
577
602
  handleSelect(value, label, e.currentTarget);
578
603
  }}
579
604
  onkeydown={(e) => {
@@ -656,10 +681,6 @@
656
681
  padding: 0;
657
682
  }
658
683
 
659
- .dropdown-item.disabled {
660
- opacity: 0.75;
661
- cursor: not-allowed;
662
- }
663
684
  .label .label-description-title,
664
685
  .label .label-description {
665
686
  height: max-content;
@@ -749,7 +770,7 @@
749
770
  width: 100%;
750
771
  }
751
772
 
752
- .dropdown-list :global(.dropdown-item.hover-state) {
773
+ .dropdown-list :global(.dropdown-item.hover-state:not(.disabled):not(.items-disabled)) {
753
774
  background: var(--background5);
754
775
  }
755
776
 
@@ -773,6 +794,7 @@
773
794
  }
774
795
 
775
796
  .dropdown-items-scroll {
797
+ position: relative;
776
798
  display: flex;
777
799
  flex-direction: column;
778
800
  align-items: flex-start;
@@ -781,6 +803,18 @@
781
803
  overflow-y: auto;
782
804
  }
783
805
 
806
+ .items-disabled-overlay {
807
+ position: absolute;
808
+ inset: 0;
809
+ display: flex;
810
+ align-items: center;
811
+ justify-content: center;
812
+ background: rgba(0, 0, 0, 0.55);
813
+ z-index: 1;
814
+ padding: 12px;
815
+ text-align: center;
816
+ }
817
+
784
818
  .dropdown-footer {
785
819
  display: flex;
786
820
  align-items: center;
@@ -861,4 +895,9 @@
861
895
  width: 10px;
862
896
  height: 10px;
863
897
  }
898
+
899
+ .dropdown-item.disabled {
900
+ opacity: 0.75;
901
+ cursor: not-allowed;
902
+ }
864
903
  </style>
@@ -0,0 +1,84 @@
1
+ <script lang="ts">
2
+ import Select from './Select.svelte';
3
+ import type { SelectOption } from './types.js';
4
+
5
+ interface Props {
6
+ options: SelectOption[];
7
+ defaultText?: string;
8
+ dropdownWidth?: string;
9
+ dropdownHeight?: string;
10
+ enableSearch?: boolean;
11
+ }
12
+
13
+ let {
14
+ options: initialOptions,
15
+ defaultText = 'Select',
16
+ dropdownWidth = '200px',
17
+ dropdownHeight = '200px',
18
+ enableSearch = false
19
+ }: Props = $props();
20
+
21
+ let itemsDisabled = $state(false);
22
+ let currentOptions = $state<SelectOption[]>(initialOptions);
23
+ let selected = $state<string | null>(null);
24
+ let statusText = $state('Idle');
25
+
26
+ const handleOpen = () => {
27
+ itemsDisabled = true;
28
+ statusText = 'Refreshing items...';
29
+
30
+ // Simulate an async refresh (e.g. fetching updated options from an API)
31
+ setTimeout(() => {
32
+ currentOptions = [
33
+ { label: 'Refreshed Option A', value: 'refreshed-a' },
34
+ { label: 'Refreshed Option B', value: 'refreshed-b' },
35
+ { label: 'Refreshed Option C', value: 'refreshed-c' },
36
+ { label: 'Refreshed Option D', value: 'refreshed-d' }
37
+ ];
38
+ itemsDisabled = false;
39
+ statusText = 'Items refreshed!';
40
+ }, 4000);
41
+ };
42
+ </script>
43
+
44
+ <div class="story-container">
45
+ <div class="status">Status: {statusText}</div>
46
+ <Select
47
+ options={currentOptions}
48
+ {defaultText}
49
+ {dropdownWidth}
50
+ {dropdownHeight}
51
+ {enableSearch}
52
+ {itemsDisabled}
53
+ itemsDisabledMessage="Refreshing items..."
54
+ onOpen={handleOpen}
55
+ bind:selected
56
+ />
57
+ <div class="info">
58
+ Open the dropdown to trigger a simulated 2s refresh. Items will be disabled during the refresh.
59
+ </div>
60
+ </div>
61
+
62
+ <style>
63
+ .story-container {
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: 12px;
67
+ align-items: flex-start;
68
+ }
69
+
70
+ .status {
71
+ font-size: 12px;
72
+ color: var(--text2);
73
+ padding: 4px 8px;
74
+ border-radius: 4px;
75
+ background: var(--background3);
76
+ }
77
+
78
+ .info {
79
+ font-size: 11px;
80
+ color: var(--text2);
81
+ max-width: 250px;
82
+ line-height: 1.4;
83
+ }
84
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { SelectOption } from './types.js';
2
+ interface Props {
3
+ options: SelectOption[];
4
+ defaultText?: string;
5
+ dropdownWidth?: string;
6
+ dropdownHeight?: string;
7
+ enableSearch?: boolean;
8
+ }
9
+ declare const SelectItemsDisabledStory: import("svelte").Component<Props, {}, "">;
10
+ type SelectItemsDisabledStory = ReturnType<typeof SelectItemsDisabledStory>;
11
+ export default SelectItemsDisabledStory;
@@ -64,6 +64,20 @@ export interface SelectProps {
64
64
  */
65
65
  invalid?: boolean;
66
66
  className?: string;
67
+ /**
68
+ * Callback fired when the dropdown opens
69
+ */
70
+ onOpen?: () => void;
71
+ /**
72
+ * When true, disables all dropdown items and search input.
73
+ * Useful for showing a loading/refreshing state while items are being updated.
74
+ */
75
+ itemsDisabled?: boolean;
76
+ /**
77
+ * Message displayed as an overlay when itemsDisabled is true.
78
+ * Helps communicate to users why items are unavailable (e.g. "Refreshing...").
79
+ */
80
+ itemsDisabledMessage?: string;
67
81
  onchange?: SelectChangeHandler;
68
82
  children?: Snippet;
69
83
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finsweet/webflow-apps-utils",
3
- "version": "1.0.56",
3
+ "version": "1.0.58",
4
4
  "description": "Shared utilities for Webflow apps",
5
5
  "homepage": "https://github.com/finsweet/webflow-apps-utils",
6
6
  "repository": {
@@ -49,7 +49,7 @@
49
49
  "@storybook/addon-vitest": "9.1.8",
50
50
  "@storybook/sveltekit": "9.1.8",
51
51
  "@sveltejs/adapter-auto": "6.1.0",
52
- "@sveltejs/kit": "2.43.2",
52
+ "@sveltejs/kit": "2.53.1",
53
53
  "@sveltejs/package": "2.5.4",
54
54
  "@sveltejs/vite-plugin-svelte": "5.1.1",
55
55
  "@testing-library/jest-dom": "6.8.0",
@@ -75,11 +75,11 @@
75
75
  "prettier-plugin-svelte": "3.4.0",
76
76
  "publint": "0.3.13",
77
77
  "storybook": "9.1.8",
78
- "svelte": "5.39.5",
78
+ "svelte": "5.53.3",
79
79
  "svelte-check": "4.3.2",
80
80
  "typescript": "5.9.2",
81
81
  "typescript-eslint": "8.44.1",
82
- "vite": "7.1.7",
82
+ "vite": "7.1.11",
83
83
  "vitest": "3.2.4"
84
84
  },
85
85
  "keywords": [
@@ -97,7 +97,6 @@
97
97
  "luxon": "3.7.2",
98
98
  "motion": "10.18.0",
99
99
  "svelte-routing": "2.13.0",
100
- "swiper": "11.2.10",
101
100
  "terser": "5.44.0",
102
101
  "uuid": "11.1.0",
103
102
  "zod": "3.25.76"