@finsweet/webflow-apps-utils 1.0.3 → 1.0.4
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/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/router/Router.mdx +958 -0
- package/dist/router/Router.stories.d.ts +6 -0
- package/dist/router/Router.stories.js +47 -0
- package/dist/router/examples/RouterExample.svelte +271 -0
- package/dist/router/examples/RouterExample.svelte.d.ts +18 -0
- package/dist/router/examples/index.d.ts +4 -0
- package/dist/router/examples/index.js +4 -0
- package/dist/router/examples/pages/AboutPage.svelte +568 -0
- package/dist/router/examples/pages/AboutPage.svelte.d.ts +13 -0
- package/dist/router/examples/pages/HomePage.svelte +200 -0
- package/dist/router/examples/pages/HomePage.svelte.d.ts +14 -0
- package/dist/router/examples/pages/NotFoundPage.svelte +307 -0
- package/dist/router/examples/pages/NotFoundPage.svelte.d.ts +17 -0
- package/dist/router/hooks.svelte.d.ts +2 -2
- package/dist/router/index.d.ts +3 -0
- package/dist/router/index.js +3 -0
- package/dist/router/{Link.svelte → providers/Link.svelte} +1 -1
- package/dist/router/{Route.svelte → providers/Route.svelte} +1 -1
- package/dist/router/{Route.svelte.d.ts → providers/Route.svelte.d.ts} +1 -1
- package/dist/router/{Router.svelte → providers/RouterProvider.svelte} +22 -5
- package/dist/router/{Router.svelte.d.ts → providers/RouterProvider.svelte.d.ts} +8 -4
- package/dist/router/providers/index.d.ts +3 -0
- package/dist/router/providers/index.js +3 -0
- package/dist/router/{index.svelte.d.ts → router.svelte.d.ts} +1 -3
- package/dist/router/{index.svelte.js → router.svelte.js} +1 -4
- package/dist/stores/docs/Form.mdx +542 -0
- package/dist/stores/forms.d.ts +41 -4
- package/dist/stores/forms.js +86 -32
- package/dist/types/customCode.d.ts +1 -1
- package/dist/types/window.d.ts +1 -0
- package/dist/ui/components/copy-text/CopyText.stories.d.ts +70 -0
- package/dist/ui/components/copy-text/CopyText.stories.js +241 -0
- package/dist/ui/components/copy-text/CopyText.svelte +249 -0
- package/dist/ui/components/copy-text/CopyText.svelte.d.ts +4 -0
- package/dist/ui/components/copy-text/index.d.ts +2 -0
- package/dist/ui/components/copy-text/index.js +1 -0
- package/dist/ui/components/copy-text/types.d.ts +52 -0
- package/dist/ui/components/copy-text/types.js +1 -0
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/input/Input.stories.d.ts +9 -0
- package/dist/ui/components/input/Input.stories.js +78 -0
- package/dist/ui/components/input/Input.svelte +39 -3
- package/dist/ui/components/input/types.d.ts +6 -0
- package/dist/ui/components/layout/Layout.svelte +7 -59
- package/dist/ui/components/layout/Layout.svelte.d.ts +2 -2
- package/dist/ui/components/layout/examples/ExampleLayout.svelte +22 -17
- package/dist/ui/components/layout/index.d.ts +1 -1
- package/dist/ui/components/layout/test-helpers/TestLayoutWithFooter.svelte +20 -0
- package/dist/ui/components/layout/test-helpers/TestLayoutWithFooter.svelte.d.ts +7 -0
- package/dist/ui/components/layout/types.d.ts +1 -10
- package/dist/ui/components/notification/Notification.stories.svelte +12 -1
- package/dist/ui/components/notification/Notification.svelte +10 -5
- package/dist/ui/components/notification/Notification.svelte.d.ts +1 -1
- package/dist/ui/components/notification/types.d.ts +1 -1
- package/dist/ui/components/section/Section.svelte +4 -2
- package/dist/ui/components/section/types.d.ts +8 -0
- package/dist/ui/components/text/Text.stories.svelte +67 -1
- package/dist/ui/components/text/Text.svelte +209 -8
- package/dist/ui/components/text/types.d.ts +4 -0
- package/dist/utils/animations/factory.d.ts +7 -0
- package/dist/utils/animations/factory.js +101 -0
- package/dist/utils/animations/index.d.ts +7 -0
- package/dist/utils/animations/index.js +62 -0
- package/dist/utils/animations/types.d.ts +39 -0
- package/dist/utils/animations/types.js +1 -0
- package/dist/utils/custom-code/configs.d.ts +22 -0
- package/dist/utils/custom-code/configs.js +40 -0
- package/dist/utils/custom-code/index.d.ts +1 -0
- package/dist/utils/custom-code/index.js +1 -0
- package/dist/utils/helpers/capitalizeFirstLetter.d.ts +4 -0
- package/dist/utils/helpers/capitalizeFirstLetter.js +9 -0
- package/dist/utils/helpers/getTimeNow.d.ts +4 -0
- package/dist/utils/helpers/getTimeNow.js +8 -0
- package/dist/utils/helpers/index.d.ts +4 -0
- package/dist/utils/helpers/index.js +4 -0
- package/dist/utils/helpers/minifyCode.d.ts +10 -0
- package/dist/utils/helpers/minifyCode.js +73 -0
- package/dist/utils/helpers/objectsToModuleExports.d.ts +1 -1
- package/dist/utils/helpers/objectsToModuleExports.js +1 -0
- package/dist/utils/helpers/toHumanReadableList.d.ts +4 -0
- package/dist/utils/helpers/toHumanReadableList.js +11 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/webflow-canvas/getAllChildren.d.ts +16 -0
- package/dist/utils/webflow-canvas/getAllChildren.js +65 -0
- package/dist/utils/webflow-canvas/getElementClassList.d.ts +9 -0
- package/dist/utils/webflow-canvas/getElementClassList.js +19 -0
- package/dist/utils/webflow-canvas/index.d.ts +2 -0
- package/dist/utils/webflow-canvas/index.js +2 -0
- package/package.json +6 -1
- package/dist/router/README.md +0 -397
- /package/dist/router/{Link.svelte.d.ts → providers/Link.svelte.d.ts} +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
import { CopyIcon, EyeIcon } from '../../icons';
|
|
6
|
+
import { trimExtraSpaces } from '../../../utils/helpers';
|
|
7
|
+
|
|
8
|
+
import type { CopyTextProps } from './types';
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
content,
|
|
12
|
+
title,
|
|
13
|
+
disabled = false,
|
|
14
|
+
raw = false,
|
|
15
|
+
hidden = false,
|
|
16
|
+
comment = '',
|
|
17
|
+
tooltip = 'Click to copy',
|
|
18
|
+
onNotify,
|
|
19
|
+
onCopy,
|
|
20
|
+
onError,
|
|
21
|
+
header,
|
|
22
|
+
footer,
|
|
23
|
+
class: className = '',
|
|
24
|
+
...restProps
|
|
25
|
+
}: CopyTextProps = $props();
|
|
26
|
+
|
|
27
|
+
// Component state
|
|
28
|
+
let isCopied = $state(false);
|
|
29
|
+
let isCooldown = $state(false);
|
|
30
|
+
|
|
31
|
+
// Simple computed values to avoid infinite loops
|
|
32
|
+
function getProcessedContent() {
|
|
33
|
+
if (raw) {
|
|
34
|
+
return comment ? `<!-- ${comment} -->\n${content}` : content;
|
|
35
|
+
}
|
|
36
|
+
return trimExtraSpaces(content) ?? '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getCopyButtonClasses() {
|
|
40
|
+
return [
|
|
41
|
+
'copy-button',
|
|
42
|
+
disabled ? 'copy-button--disabled' : '',
|
|
43
|
+
isCopied ? 'copy-button--copied' : '',
|
|
44
|
+
className || ''
|
|
45
|
+
]
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.join(' ');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Handles notifications with cooldown to prevent spam
|
|
52
|
+
*/
|
|
53
|
+
const handleNotification = (type: 'Success' | 'Error', message: string) => {
|
|
54
|
+
if (!isCooldown) {
|
|
55
|
+
onNotify?.({ type, message });
|
|
56
|
+
isCooldown = true;
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
isCooldown = false;
|
|
59
|
+
}, 1000);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Simple click handler without reactive effects to avoid infinite loops
|
|
64
|
+
function handleClick() {
|
|
65
|
+
if (disabled) return;
|
|
66
|
+
|
|
67
|
+
// Use modern clipboard API instead of ClipboardJS to avoid issues
|
|
68
|
+
const textToCopy = getProcessedContent();
|
|
69
|
+
|
|
70
|
+
if (navigator.clipboard) {
|
|
71
|
+
navigator.clipboard
|
|
72
|
+
.writeText(textToCopy)
|
|
73
|
+
.then(() => {
|
|
74
|
+
isCopied = true;
|
|
75
|
+
handleNotification('Success', 'Copied to clipboard!');
|
|
76
|
+
onCopy?.(textToCopy);
|
|
77
|
+
|
|
78
|
+
// Reset copied state after 2 seconds
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
isCopied = false;
|
|
81
|
+
}, 2000);
|
|
82
|
+
})
|
|
83
|
+
.catch(() => {
|
|
84
|
+
const errorMessage = 'Failed to copy. Please try again.';
|
|
85
|
+
handleNotification('Error', errorMessage);
|
|
86
|
+
onError?.(errorMessage);
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
// Fallback for browsers that don't support navigator.clipboard
|
|
90
|
+
const errorMessage = 'Clipboard not supported in this browser';
|
|
91
|
+
handleNotification('Error', errorMessage);
|
|
92
|
+
onError?.(errorMessage);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
{#if !hidden}
|
|
98
|
+
<div class="copy-text" {...restProps}>
|
|
99
|
+
{#if header}
|
|
100
|
+
<div class="copy-text__header">
|
|
101
|
+
{@render header()}
|
|
102
|
+
</div>
|
|
103
|
+
{:else if title}
|
|
104
|
+
<div class="copy-text__header">
|
|
105
|
+
<h3 class="copy-text__title">{title}</h3>
|
|
106
|
+
</div>
|
|
107
|
+
{/if}
|
|
108
|
+
|
|
109
|
+
<div
|
|
110
|
+
class={getCopyButtonClasses()}
|
|
111
|
+
role="button"
|
|
112
|
+
tabindex="0"
|
|
113
|
+
aria-label={disabled ? 'Copy disabled' : tooltip}
|
|
114
|
+
title={tooltip}
|
|
115
|
+
onclick={handleClick}
|
|
116
|
+
onkeydown={(e) => {
|
|
117
|
+
if ((e.key === 'Enter' || e.key === ' ') && !disabled) {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
handleClick();
|
|
120
|
+
}
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
<div class="copy-button__content" id="copy-content">
|
|
124
|
+
{getProcessedContent()}
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div class="copy-button__icon" aria-hidden="true">
|
|
128
|
+
{#if disabled}
|
|
129
|
+
<EyeIcon />
|
|
130
|
+
{:else}
|
|
131
|
+
<CopyIcon size={16} />
|
|
132
|
+
{/if}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{#if footer}
|
|
137
|
+
<div class="copy-text__footer">
|
|
138
|
+
{@render footer()}
|
|
139
|
+
</div>
|
|
140
|
+
{/if}
|
|
141
|
+
</div>
|
|
142
|
+
{/if}
|
|
143
|
+
|
|
144
|
+
<style>
|
|
145
|
+
.copy-text {
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-direction: column;
|
|
148
|
+
gap: 12px;
|
|
149
|
+
width: 100%;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.copy-text__header {
|
|
153
|
+
display: flex;
|
|
154
|
+
align-items: center;
|
|
155
|
+
gap: 8px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.copy-text__title {
|
|
159
|
+
margin: 0;
|
|
160
|
+
color: var(--text-color-primary, #ffffff);
|
|
161
|
+
font-family: Inter, sans-serif;
|
|
162
|
+
font-size: 12px;
|
|
163
|
+
font-weight: 500;
|
|
164
|
+
line-height: 16px;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.copy-button {
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: flex-start;
|
|
170
|
+
justify-content: space-between;
|
|
171
|
+
gap: 8px;
|
|
172
|
+
padding: 6px 8px;
|
|
173
|
+
border-radius: 4px;
|
|
174
|
+
background: var(
|
|
175
|
+
--button-secondary-background,
|
|
176
|
+
linear-gradient(180deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.1) 100%),
|
|
177
|
+
rgba(255, 255, 255, 0.08)
|
|
178
|
+
);
|
|
179
|
+
box-shadow:
|
|
180
|
+
0px 0.5px 0.5px 0px rgba(255, 255, 255, 0.12) inset,
|
|
181
|
+
0px 0.5px 1px 0px #000;
|
|
182
|
+
color: var(--text-color-secondary, #d9d9d9);
|
|
183
|
+
font-family: Inter, sans-serif;
|
|
184
|
+
font-size: 11px;
|
|
185
|
+
font-weight: 500;
|
|
186
|
+
line-height: 16px;
|
|
187
|
+
cursor: pointer;
|
|
188
|
+
transition: all 0.2s ease-in-out;
|
|
189
|
+
outline: none;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.copy-button:hover:not(.copy-button--disabled) {
|
|
193
|
+
background: var(
|
|
194
|
+
--button-secondary-background-hover,
|
|
195
|
+
linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0.14) 100%),
|
|
196
|
+
rgba(255, 255, 255, 0.12)
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.copy-button:focus-visible {
|
|
201
|
+
outline: 2px solid var(--focus-color, #0066cc);
|
|
202
|
+
outline-offset: 2px;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.copy-button--disabled {
|
|
206
|
+
background: none;
|
|
207
|
+
border: 1px solid var(--border-color-disabled, rgba(255, 255, 255, 0.2));
|
|
208
|
+
box-shadow: none;
|
|
209
|
+
opacity: 0.75;
|
|
210
|
+
cursor: not-allowed;
|
|
211
|
+
color: var(--text-color-disabled, #999999);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.copy-button__content {
|
|
215
|
+
display: flex;
|
|
216
|
+
align-items: flex-start;
|
|
217
|
+
flex: 1;
|
|
218
|
+
overflow: auto;
|
|
219
|
+
white-space: pre-wrap;
|
|
220
|
+
word-wrap: break-word;
|
|
221
|
+
max-width: 600px;
|
|
222
|
+
min-height: max-content;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.copy-button__content:disabled {
|
|
226
|
+
cursor: not-allowed;
|
|
227
|
+
color: var(--text-color-disabled, #999999);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.copy-button__icon {
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
height: 16px;
|
|
234
|
+
flex-shrink: 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.copy-text__footer {
|
|
238
|
+
display: flex;
|
|
239
|
+
flex-direction: column;
|
|
240
|
+
gap: 8px;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Responsive design */
|
|
244
|
+
@media (max-width: 768px) {
|
|
245
|
+
.copy-button__content {
|
|
246
|
+
max-width: 300px;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as CopyText } from './CopyText.svelte';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
/**
|
|
4
|
+
* Notification function type for copy operations
|
|
5
|
+
*/
|
|
6
|
+
export interface NotificationFunction {
|
|
7
|
+
(options: {
|
|
8
|
+
type: 'Success' | 'Error';
|
|
9
|
+
message: string;
|
|
10
|
+
}): void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Props for the CopyText component
|
|
14
|
+
*/
|
|
15
|
+
export interface CopyTextProps extends HTMLAttributes<HTMLDivElement> {
|
|
16
|
+
/** The content to be copied to clipboard */
|
|
17
|
+
content: string;
|
|
18
|
+
/** Optional title/heading text to display above the copy area */
|
|
19
|
+
title?: string;
|
|
20
|
+
/** Whether the copy functionality is disabled */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** Whether to show the content in raw format (with HTML) or cleaned */
|
|
23
|
+
raw?: boolean;
|
|
24
|
+
/** Whether the component is in a hidden state */
|
|
25
|
+
hidden?: boolean;
|
|
26
|
+
/** Optional comment to prepend to copied content when in raw mode */
|
|
27
|
+
comment?: string;
|
|
28
|
+
/** Custom tooltip text for the copy button */
|
|
29
|
+
tooltip?: string;
|
|
30
|
+
/** Optional notification function to call on copy success/error */
|
|
31
|
+
onNotify?: NotificationFunction;
|
|
32
|
+
/** Callback fired when content is successfully copied */
|
|
33
|
+
onCopy?: (content: string) => void;
|
|
34
|
+
/** Callback fired when copy fails */
|
|
35
|
+
onError?: (error: string) => void;
|
|
36
|
+
/** Optional snippet for custom header content */
|
|
37
|
+
header?: Snippet;
|
|
38
|
+
/** Optional snippet for additional content below the copy area */
|
|
39
|
+
footer?: Snippet;
|
|
40
|
+
/** Custom CSS classes */
|
|
41
|
+
class?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Event types for clipboard operations
|
|
45
|
+
*/
|
|
46
|
+
export interface CopyEvent {
|
|
47
|
+
text: string;
|
|
48
|
+
clearSelection: () => void;
|
|
49
|
+
}
|
|
50
|
+
export interface CopyErrorEvent {
|
|
51
|
+
message: string;
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -90,6 +90,10 @@ declare const meta: {
|
|
|
90
90
|
control: string;
|
|
91
91
|
description: string;
|
|
92
92
|
};
|
|
93
|
+
debounce: {
|
|
94
|
+
control: string;
|
|
95
|
+
description: string;
|
|
96
|
+
};
|
|
93
97
|
};
|
|
94
98
|
};
|
|
95
99
|
export default meta;
|
|
@@ -131,3 +135,8 @@ export declare const WithMinLength: Story;
|
|
|
131
135
|
export declare const InteractiveExample: Story;
|
|
132
136
|
export declare const ComplexExample: Story;
|
|
133
137
|
export declare const FormFieldExample: Story;
|
|
138
|
+
export declare const WithDebounce: Story;
|
|
139
|
+
export declare const FastDebounce: Story;
|
|
140
|
+
export declare const SlowDebounce: Story;
|
|
141
|
+
export declare const NoDebounce: Story;
|
|
142
|
+
export declare const DebounceWithSteppers: Story;
|
|
@@ -85,6 +85,10 @@ const meta = {
|
|
|
85
85
|
max: {
|
|
86
86
|
control: 'number',
|
|
87
87
|
description: 'Maximum value for number input'
|
|
88
|
+
},
|
|
89
|
+
debounce: {
|
|
90
|
+
control: 'number',
|
|
91
|
+
description: 'Debounce delay in milliseconds for input events (0 = no debounce)'
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
};
|
|
@@ -433,3 +437,77 @@ export const FormFieldExample = {
|
|
|
433
437
|
invalid: true
|
|
434
438
|
}
|
|
435
439
|
};
|
|
440
|
+
// Debounce examples
|
|
441
|
+
export const WithDebounce = {
|
|
442
|
+
args: {
|
|
443
|
+
placeholder: 'Type fast to see debouncing...',
|
|
444
|
+
debounce: 300,
|
|
445
|
+
oninput: (value) => console.log('Debounced input:', value)
|
|
446
|
+
},
|
|
447
|
+
parameters: {
|
|
448
|
+
docs: {
|
|
449
|
+
description: {
|
|
450
|
+
story: 'Input with 300ms debounce. The oninput event will only fire after you stop typing for 300ms. Check the console to see the difference.'
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
export const FastDebounce = {
|
|
456
|
+
args: {
|
|
457
|
+
placeholder: 'Fast debounce (100ms)',
|
|
458
|
+
debounce: 100,
|
|
459
|
+
oninput: (value) => console.log('Fast debounced input:', value)
|
|
460
|
+
},
|
|
461
|
+
parameters: {
|
|
462
|
+
docs: {
|
|
463
|
+
description: {
|
|
464
|
+
story: 'Input with a faster 100ms debounce for more responsive but still controlled input handling.'
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
export const SlowDebounce = {
|
|
470
|
+
args: {
|
|
471
|
+
placeholder: 'Slow debounce (1000ms)',
|
|
472
|
+
debounce: 1000,
|
|
473
|
+
oninput: (value) => console.log('Slow debounced input:', value)
|
|
474
|
+
},
|
|
475
|
+
parameters: {
|
|
476
|
+
docs: {
|
|
477
|
+
description: {
|
|
478
|
+
story: 'Input with a slower 1000ms debounce for heavy operations that should only run after the user has finished typing.'
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
export const NoDebounce = {
|
|
484
|
+
args: {
|
|
485
|
+
placeholder: 'No debounce (immediate)',
|
|
486
|
+
debounce: 0,
|
|
487
|
+
oninput: (value) => console.log('Immediate input:', value)
|
|
488
|
+
},
|
|
489
|
+
parameters: {
|
|
490
|
+
docs: {
|
|
491
|
+
description: {
|
|
492
|
+
story: 'Input with no debouncing (debounce: 0). Events fire immediately on every keystroke.'
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
export const DebounceWithSteppers = {
|
|
498
|
+
args: {
|
|
499
|
+
type: 'number',
|
|
500
|
+
showSteppers: true,
|
|
501
|
+
value: '10',
|
|
502
|
+
debounce: 500,
|
|
503
|
+
placeholder: 'Debounced number input',
|
|
504
|
+
oninput: (value) => console.log('Debounced stepper input:', value)
|
|
505
|
+
},
|
|
506
|
+
parameters: {
|
|
507
|
+
docs: {
|
|
508
|
+
description: {
|
|
509
|
+
story: 'Number input with steppers and debouncing. Both typing and stepper clicks are debounced.'
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
};
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
step = 1,
|
|
29
29
|
min = undefined,
|
|
30
30
|
max = undefined,
|
|
31
|
+
debounce = 0,
|
|
31
32
|
oninput,
|
|
32
33
|
onblur,
|
|
33
34
|
onfocus,
|
|
@@ -59,6 +60,9 @@
|
|
|
59
60
|
// Track HTML5 validation state
|
|
60
61
|
let isValidationInvalid = $state(false);
|
|
61
62
|
|
|
63
|
+
// Debounce timer for input events
|
|
64
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = $state(null);
|
|
65
|
+
|
|
62
66
|
// Use internal value for reactive state management
|
|
63
67
|
let currInputValue = $derived(internalValue);
|
|
64
68
|
let computedColor = $derived(currInputValue ? 'var(--actionPrimaryText)' : 'var(--text3)');
|
|
@@ -227,6 +231,37 @@
|
|
|
227
231
|
}
|
|
228
232
|
});
|
|
229
233
|
|
|
234
|
+
// Cleanup debounce timer on component destruction
|
|
235
|
+
$effect(() => {
|
|
236
|
+
return () => {
|
|
237
|
+
if (debounceTimer) {
|
|
238
|
+
clearTimeout(debounceTimer);
|
|
239
|
+
debounceTimer = null;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Debounced oninput handler
|
|
246
|
+
*/
|
|
247
|
+
const debouncedOninput = (inputValue: string) => {
|
|
248
|
+
if (debounce > 0) {
|
|
249
|
+
// Clear existing timer
|
|
250
|
+
if (debounceTimer) {
|
|
251
|
+
clearTimeout(debounceTimer);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Set new timer
|
|
255
|
+
debounceTimer = setTimeout(() => {
|
|
256
|
+
oninput?.(inputValue);
|
|
257
|
+
debounceTimer = null;
|
|
258
|
+
}, debounce);
|
|
259
|
+
} else {
|
|
260
|
+
// No debouncing, call immediately
|
|
261
|
+
oninput?.(inputValue);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
230
265
|
/**
|
|
231
266
|
* Handles input events
|
|
232
267
|
*/
|
|
@@ -252,7 +287,8 @@
|
|
|
252
287
|
// Update validation state for real-time feedback
|
|
253
288
|
updateValidationState();
|
|
254
289
|
|
|
255
|
-
oninput
|
|
290
|
+
// Call debounced oninput handler
|
|
291
|
+
debouncedOninput(inputValue);
|
|
256
292
|
};
|
|
257
293
|
|
|
258
294
|
/**
|
|
@@ -316,7 +352,7 @@
|
|
|
316
352
|
|
|
317
353
|
// Trigger change events
|
|
318
354
|
onValueChange?.(newValue);
|
|
319
|
-
|
|
355
|
+
debouncedOninput(newStringValue);
|
|
320
356
|
};
|
|
321
357
|
|
|
322
358
|
/**
|
|
@@ -345,7 +381,7 @@
|
|
|
345
381
|
|
|
346
382
|
// Trigger change events
|
|
347
383
|
onValueChange?.(newValue);
|
|
348
|
-
|
|
384
|
+
debouncedOninput(newStringValue);
|
|
349
385
|
};
|
|
350
386
|
|
|
351
387
|
/**
|
|
@@ -94,6 +94,12 @@ export interface InputProps extends Omit<HTMLInputAttributes, 'onblur' | 'onfocu
|
|
|
94
94
|
* Maximum value for number input.
|
|
95
95
|
*/
|
|
96
96
|
max?: number;
|
|
97
|
+
/**
|
|
98
|
+
* Debounce delay in milliseconds for input events.
|
|
99
|
+
* When set, oninput will be debounced by this amount.
|
|
100
|
+
* Default is no debouncing (0).
|
|
101
|
+
*/
|
|
102
|
+
debounce?: number;
|
|
97
103
|
/**
|
|
98
104
|
* Event handler for input changes - receives the trimmed string value
|
|
99
105
|
*/
|
|
@@ -11,13 +11,12 @@
|
|
|
11
11
|
|
|
12
12
|
import { Tooltip } from '../tooltip';
|
|
13
13
|
import { EditModeMessage } from './common';
|
|
14
|
-
import type {
|
|
14
|
+
import type { LayoutTab } from './types';
|
|
15
15
|
|
|
16
16
|
interface LayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
17
17
|
activeTab: string;
|
|
18
18
|
tabs: LayoutTab[];
|
|
19
19
|
switchTab: (tab: string) => void;
|
|
20
|
-
footer?: LayoutFooter;
|
|
21
20
|
formKey: string;
|
|
22
21
|
editMode?: boolean;
|
|
23
22
|
showFooter?: boolean;
|
|
@@ -36,13 +35,13 @@
|
|
|
36
35
|
sidebar?: Snippet;
|
|
37
36
|
main?: Snippet;
|
|
38
37
|
previewBar?: Snippet;
|
|
38
|
+
footer?: Snippet;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
let {
|
|
42
42
|
activeTab,
|
|
43
43
|
tabs,
|
|
44
44
|
switchTab,
|
|
45
|
-
footer = { buttons: [] },
|
|
46
45
|
formKey,
|
|
47
46
|
editMode = false,
|
|
48
47
|
showFooter = true,
|
|
@@ -56,12 +55,12 @@
|
|
|
56
55
|
sidebar,
|
|
57
56
|
main,
|
|
58
57
|
previewBar,
|
|
58
|
+
footer,
|
|
59
59
|
class: className = '',
|
|
60
60
|
...restProps
|
|
61
61
|
}: LayoutProps = $props();
|
|
62
62
|
|
|
63
63
|
// Derived states
|
|
64
|
-
let hasFooterButtons = $derived(footer?.buttons && footer.buttons.length > 0);
|
|
65
64
|
let containerStyles = $derived(
|
|
66
65
|
containerMode ? 'height: 100%; width: 100%;' : 'height: 100vh; width: 100vw;'
|
|
67
66
|
);
|
|
@@ -93,7 +92,7 @@
|
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
// Handle footer row
|
|
96
|
-
if (showFooter &&
|
|
95
|
+
if (showFooter && footer) {
|
|
97
96
|
if (showSidebar) {
|
|
98
97
|
areas.push('"sidebar footer"');
|
|
99
98
|
} else {
|
|
@@ -118,7 +117,7 @@
|
|
|
118
117
|
|
|
119
118
|
rows.push('1fr'); // main content area takes remaining space
|
|
120
119
|
|
|
121
|
-
if (showFooter &&
|
|
120
|
+
if (showFooter && footer) {
|
|
122
121
|
rows.push(footerHeight);
|
|
123
122
|
}
|
|
124
123
|
|
|
@@ -240,21 +239,9 @@
|
|
|
240
239
|
</div>
|
|
241
240
|
|
|
242
241
|
<!-- Footer -->
|
|
243
|
-
{#if showFooter &&
|
|
242
|
+
{#if showFooter && footer}
|
|
244
243
|
<div class="footer" data-area="footer">
|
|
245
|
-
{
|
|
246
|
-
<button
|
|
247
|
-
class="footer-button footer-button--{button.variant}"
|
|
248
|
-
disabled={editMode}
|
|
249
|
-
onclick={button.onClick}
|
|
250
|
-
>
|
|
251
|
-
{#if button.icon}
|
|
252
|
-
{@const Icon = button.icon}
|
|
253
|
-
<Icon />
|
|
254
|
-
{/if}
|
|
255
|
-
{button.text}
|
|
256
|
-
</button>
|
|
257
|
-
{/each}
|
|
244
|
+
{@render footer()}
|
|
258
245
|
</div>
|
|
259
246
|
{/if}
|
|
260
247
|
</div>
|
|
@@ -507,43 +494,4 @@
|
|
|
507
494
|
height: 100%;
|
|
508
495
|
overflow: hidden;
|
|
509
496
|
}
|
|
510
|
-
|
|
511
|
-
.footer-button {
|
|
512
|
-
display: flex;
|
|
513
|
-
padding: 4px 8px;
|
|
514
|
-
justify-content: center;
|
|
515
|
-
align-items: center;
|
|
516
|
-
gap: 2px;
|
|
517
|
-
border-radius: 4px;
|
|
518
|
-
border: 1px solid var(--border-border-1, #363636);
|
|
519
|
-
background: var(--background2);
|
|
520
|
-
color: var(--text1);
|
|
521
|
-
text-align: center;
|
|
522
|
-
font-family: Inter;
|
|
523
|
-
font-size: 11px;
|
|
524
|
-
font-style: normal;
|
|
525
|
-
font-weight: 400;
|
|
526
|
-
line-height: 16px;
|
|
527
|
-
cursor: pointer;
|
|
528
|
-
transition: all 0.2s ease-in-out;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
.footer-button--primary {
|
|
532
|
-
background: var(--action-action-primary-background-hover, #1280ee);
|
|
533
|
-
color: var(--actionPrimaryText);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
.footer-button--secondary {
|
|
537
|
-
background: var(--background2);
|
|
538
|
-
color: var(--text1);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
.footer-button:hover:not(:disabled) {
|
|
542
|
-
opacity: 0.8;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
.footer-button:disabled {
|
|
546
|
-
opacity: 0.5;
|
|
547
|
-
cursor: not-allowed;
|
|
548
|
-
}
|
|
549
497
|
</style>
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
-
import type {
|
|
3
|
+
import type { LayoutTab } from './types';
|
|
4
4
|
interface LayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
5
|
activeTab: string;
|
|
6
6
|
tabs: LayoutTab[];
|
|
7
7
|
switchTab: (tab: string) => void;
|
|
8
|
-
footer?: LayoutFooter;
|
|
9
8
|
formKey: string;
|
|
10
9
|
editMode?: boolean;
|
|
11
10
|
showFooter?: boolean;
|
|
@@ -24,6 +23,7 @@ interface LayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
24
23
|
sidebar?: Snippet;
|
|
25
24
|
main?: Snippet;
|
|
26
25
|
previewBar?: Snippet;
|
|
26
|
+
footer?: Snippet;
|
|
27
27
|
}
|
|
28
28
|
declare const Layout: import("svelte").Component<LayoutProps, {}, "">;
|
|
29
29
|
type Layout = ReturnType<typeof Layout>;
|