@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.
- 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 +48 -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;
|
|
@@ -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' : ''} {
|
|
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.
|
|
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.
|
|
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"
|