@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.
- package/dist/ui/components/copy-text/CopyText.stories.js +4 -1
- package/dist/ui/components/select/Select.stories.d.ts +15 -0
- package/dist/ui/components/select/Select.stories.js +68 -0
- package/dist/ui/components/select/Select.svelte +47 -9
- package/dist/ui/components/select/SelectItemsDisabledStory.svelte +84 -0
- package/dist/ui/components/select/SelectItemsDisabledStory.svelte.d.ts +11 -0
- package/dist/ui/components/select/types.d.ts +14 -0
- package/package.json +4 -5
|
@@ -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' : ''} {
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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"
|