@finsweet/webflow-apps-utils 1.0.56 → 1.0.57

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;
@@ -393,6 +399,8 @@
393
399
  isFocused = true;
394
400
  update();
395
401
 
402
+ onOpen?.();
403
+
396
404
  const selectedItemButton = tooltip.querySelector(
397
405
  `.dropdown-item[aria-selected="true"]`
398
406
  ) as HTMLElement;
@@ -536,8 +544,20 @@
536
544
  <div
537
545
  class="dropdown-items-scroll"
538
546
  style="max-height:{dropdownHeight};"
547
+ class:disabled={itemsDisabled}
539
548
  onmouseleave={clearHoverState}
540
549
  >
550
+ {#if itemsDisabled && itemsDisabledMessage}
551
+ <div class="items-disabled-overlay" role="status">
552
+ <Text
553
+ loading
554
+ class="items-disabled-text"
555
+ label={itemsDisabledMessage}
556
+ fontSize="normal"
557
+ fontColor="var(--text2)"
558
+ />
559
+ </div>
560
+ {/if}
541
561
  {#if selectedLabel}
542
562
  <div class="selected">
543
563
  <div class="label">
@@ -551,6 +571,7 @@
551
571
  <input
552
572
  type="text"
553
573
  placeholder="Search"
574
+ disabled={itemsDisabled}
554
575
  oninput={(e) => {
555
576
  e.stopPropagation();
556
577
  e.preventDefault();
@@ -569,11 +590,14 @@
569
590
  aria-selected={value === selected && selected?.trim() !== '' ? 'true' : 'false'}
570
591
  id={`${itemId}-list-${indexId}-${id}`}
571
592
  data-value={value}
572
- class="dropdown-item {isDisabled ? 'disabled' : ''} {className}"
593
+ class="dropdown-item {isDisabled || itemsDisabled ? 'disabled' : ''} {itemsDisabled
594
+ ? 'items-disabled'
595
+ : ''} {className}"
573
596
  role="option"
597
+ aria-disabled={isDisabled || itemsDisabled}
574
598
  onclick={(e) => {
575
599
  e.stopPropagation();
576
- if (isDisabled) return;
600
+ if (isDisabled || itemsDisabled) return;
577
601
  handleSelect(value, label, e.currentTarget);
578
602
  }}
579
603
  onkeydown={(e) => {
@@ -656,10 +680,6 @@
656
680
  padding: 0;
657
681
  }
658
682
 
659
- .dropdown-item.disabled {
660
- opacity: 0.75;
661
- cursor: not-allowed;
662
- }
663
683
  .label .label-description-title,
664
684
  .label .label-description {
665
685
  height: max-content;
@@ -749,7 +769,7 @@
749
769
  width: 100%;
750
770
  }
751
771
 
752
- .dropdown-list :global(.dropdown-item.hover-state) {
772
+ .dropdown-list :global(.dropdown-item.hover-state:not(.disabled):not(.items-disabled)) {
753
773
  background: var(--background5);
754
774
  }
755
775
 
@@ -773,6 +793,7 @@
773
793
  }
774
794
 
775
795
  .dropdown-items-scroll {
796
+ position: relative;
776
797
  display: flex;
777
798
  flex-direction: column;
778
799
  align-items: flex-start;
@@ -781,6 +802,18 @@
781
802
  overflow-y: auto;
782
803
  }
783
804
 
805
+ .items-disabled-overlay {
806
+ position: absolute;
807
+ inset: 0;
808
+ display: flex;
809
+ align-items: center;
810
+ justify-content: center;
811
+ background: rgba(0, 0, 0, 0.55);
812
+ z-index: 1;
813
+ padding: 12px;
814
+ text-align: center;
815
+ }
816
+
784
817
  .dropdown-footer {
785
818
  display: flex;
786
819
  align-items: center;
@@ -861,4 +894,9 @@
861
894
  width: 10px;
862
895
  height: 10px;
863
896
  }
897
+
898
+ .dropdown-item.disabled {
899
+ opacity: 0.75;
900
+ cursor: not-allowed;
901
+ }
864
902
  </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.57",
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"