@finsweet/webflow-apps-utils 1.0.1 → 1.0.3

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.
Files changed (230) hide show
  1. package/README.md +162 -1
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +1 -0
  4. package/dist/providers/GlobalProvider.mdx +322 -0
  5. package/dist/providers/GlobalProvider.svelte +58 -0
  6. package/dist/providers/GlobalProvider.svelte.d.ts +4 -0
  7. package/dist/providers/configuratorUtils.d.ts +37 -0
  8. package/dist/providers/configuratorUtils.js +219 -0
  9. package/dist/providers/globalContext.svelte.d.ts +18 -0
  10. package/dist/providers/globalContext.svelte.js +439 -0
  11. package/dist/providers/index.d.ts +5 -0
  12. package/dist/providers/index.js +7 -0
  13. package/dist/providers/types.d.ts +103 -0
  14. package/dist/providers/types.js +6 -0
  15. package/dist/router/README.md +2 -2
  16. package/dist/stores/index.d.ts +0 -1
  17. package/dist/stores/index.js +0 -1
  18. package/dist/types/webflow.d.ts +31 -47
  19. package/dist/ui/components/LoadingScreen.svelte +2 -1
  20. package/dist/ui/components/button/Button.svelte +4 -1
  21. package/dist/ui/components/button-group/ButtonGroup.stories.js +112 -0
  22. package/dist/ui/components/{ButtonGroup.svelte → button-group/ButtonGroup.svelte} +20 -33
  23. package/dist/ui/components/button-group/ButtonGroup.svelte.d.ts +13 -0
  24. package/dist/ui/components/button-group/index.d.ts +2 -0
  25. package/dist/ui/components/button-group/index.js +1 -0
  26. package/dist/ui/components/button-group/types.d.ts +32 -0
  27. package/dist/ui/components/checkbox/Checkbox.stories.d.ts +55 -0
  28. package/dist/ui/components/checkbox/Checkbox.stories.js +162 -0
  29. package/dist/ui/components/checkbox/Checkbox.svelte +141 -0
  30. package/dist/ui/components/checkbox/Checkbox.svelte.d.ts +4 -0
  31. package/dist/ui/components/checkbox/index.d.ts +2 -0
  32. package/dist/ui/components/checkbox/index.js +1 -0
  33. package/dist/ui/components/checkbox/types.d.ts +32 -0
  34. package/dist/ui/components/controlled-buttons/ControlledButtons.stories.d.ts +32 -0
  35. package/dist/ui/components/controlled-buttons/ControlledButtons.stories.js +152 -0
  36. package/dist/ui/components/{buttons/FooterButton.svelte → controlled-buttons/ControlledButtons.svelte} +18 -67
  37. package/dist/ui/components/controlled-buttons/ControlledButtons.svelte.d.ts +4 -0
  38. package/dist/ui/components/controlled-buttons/index.d.ts +2 -0
  39. package/dist/ui/components/controlled-buttons/index.js +1 -0
  40. package/dist/ui/components/{buttons → controlled-buttons}/types.d.ts +11 -4
  41. package/dist/ui/components/divider/Divider.stories.svelte +134 -0
  42. package/dist/ui/components/{clickable/Clickable.stories.svelte.d.ts → divider/Divider.stories.svelte.d.ts} +4 -4
  43. package/dist/ui/components/divider/Divider.svelte +30 -0
  44. package/dist/ui/components/divider/Divider.svelte.d.ts +4 -0
  45. package/dist/ui/components/divider/index.d.ts +2 -0
  46. package/dist/ui/components/divider/index.js +1 -0
  47. package/dist/ui/components/divider/types.d.ts +23 -0
  48. package/dist/ui/components/divider/types.js +1 -0
  49. package/dist/ui/components/iframe/Iframe.stories.svelte +122 -0
  50. package/dist/ui/components/{ToggleItem.svelte.d.ts → iframe/Iframe.stories.svelte.d.ts} +7 -8
  51. package/dist/ui/components/iframe/Iframe.svelte +75 -0
  52. package/dist/ui/components/iframe/Iframe.svelte.d.ts +4 -0
  53. package/dist/ui/components/iframe/index.d.ts +2 -0
  54. package/dist/ui/components/iframe/index.js +1 -0
  55. package/dist/ui/components/iframe/types.d.ts +38 -0
  56. package/dist/ui/components/iframe/types.js +1 -0
  57. package/dist/ui/components/index.d.ts +12 -39
  58. package/dist/ui/components/index.js +12 -39
  59. package/dist/ui/components/input/Input.stories.d.ts +24 -0
  60. package/dist/ui/components/input/Input.stories.js +98 -0
  61. package/dist/ui/components/input/Input.svelte +321 -80
  62. package/dist/ui/components/input/types.d.ts +27 -1
  63. package/dist/ui/components/layout/Layout.stories.svelte +3 -3
  64. package/dist/ui/components/layout/Layout.svelte +3 -5
  65. package/dist/ui/components/layout/common/EditModeMessage.svelte +24 -12
  66. package/dist/ui/components/layout/{ExampleLayout.svelte → examples/ExampleLayout.svelte} +34 -22
  67. package/dist/ui/components/layout/examples/Wrapper.svelte +9 -0
  68. package/dist/ui/components/{NoSettingsNeeded.svelte.d.ts → layout/examples/Wrapper.svelte.d.ts} +3 -3
  69. package/dist/ui/components/layout/examples/index.d.ts +2 -0
  70. package/dist/ui/components/layout/examples/index.js +2 -0
  71. package/dist/ui/components/layout/index.d.ts +2 -1
  72. package/dist/ui/components/layout/index.js +2 -1
  73. package/dist/ui/components/modal/Example.svelte +320 -0
  74. package/dist/ui/components/modal/Example.svelte.d.ts +3 -0
  75. package/dist/ui/components/modal/Modal.stories.svelte +18 -0
  76. package/dist/ui/components/modal/Modal.stories.svelte.d.ts +26 -0
  77. package/dist/ui/components/modal/Modal.svelte +490 -0
  78. package/dist/ui/components/modal/Modal.svelte.d.ts +130 -0
  79. package/dist/ui/components/modal/index.d.ts +2 -0
  80. package/dist/ui/components/modal/index.js +1 -0
  81. package/dist/ui/components/modal/types.d.ts +75 -0
  82. package/dist/ui/components/modal/types.js +1 -0
  83. package/dist/ui/components/notification/Notification.stories.svelte +228 -0
  84. package/dist/ui/components/{ToggleList.svelte.d.ts → notification/Notification.stories.svelte.d.ts} +9 -21
  85. package/dist/ui/components/notification/Notification.svelte +289 -0
  86. package/dist/ui/components/notification/Notification.svelte.d.ts +67 -0
  87. package/dist/ui/components/notification/index.d.ts +2 -0
  88. package/dist/ui/components/notification/index.js +1 -0
  89. package/dist/ui/components/notification/types.d.ts +68 -0
  90. package/dist/ui/components/notification/types.js +1 -0
  91. package/dist/ui/components/section/Section.stories.svelte +263 -0
  92. package/dist/ui/components/section/Section.stories.svelte.d.ts +27 -0
  93. package/dist/ui/components/section/Section.svelte +324 -0
  94. package/dist/ui/components/section/Section.svelte.d.ts +5 -0
  95. package/dist/ui/components/section/index.d.ts +2 -0
  96. package/dist/ui/components/section/index.js +1 -0
  97. package/dist/ui/components/section/types.d.ts +106 -0
  98. package/dist/ui/components/section/types.js +1 -0
  99. package/dist/ui/components/{ImageUpload.svelte → shared/ImageUpload.svelte} +3 -3
  100. package/dist/ui/components/{SelectBodyOrDivBlock.svelte → shared/SelectBodyOrDivBlock.svelte} +1 -1
  101. package/dist/ui/components/shared/index.d.ts +2 -0
  102. package/dist/ui/components/shared/index.js +2 -0
  103. package/dist/ui/icons/LineChartIcon.svelte +8 -0
  104. package/dist/ui/icons/LineChartIcon.svelte.d.ts +26 -0
  105. package/dist/ui/icons/index.d.ts +2 -1
  106. package/dist/ui/icons/index.js +2 -1
  107. package/dist/ui/index.css +33 -5
  108. package/dist/utils/api/checkIfAppModeIsDesign.d.ts +1 -2
  109. package/dist/utils/api/checkIfAppModeIsDesign.js +1 -2
  110. package/dist/utils/api/clipboard/handlePaste.d.ts +6 -37
  111. package/dist/utils/api/clipboard/handlePaste.js +2 -6
  112. package/dist/utils/api/getAllAssets.d.ts +1 -2
  113. package/dist/utils/api/getAllAssets.js +1 -2
  114. package/dist/utils/api/getFinsweetComponentsEnvironment.d.ts +1 -2
  115. package/dist/utils/api/getFinsweetComponentsEnvironment.js +3 -6
  116. package/dist/utils/api/index.d.ts +0 -1
  117. package/dist/utils/api/index.js +0 -1
  118. package/dist/utils/api/insertWithXSCP.d.ts +1 -2
  119. package/dist/utils/api/insertWithXSCP.js +1 -2
  120. package/dist/utils/auth/crossWindowLogin.d.ts +3 -0
  121. package/dist/utils/auth/crossWindowLogin.js +3 -0
  122. package/dist/utils/auth/index.d.ts +9 -25
  123. package/dist/utils/auth/index.js +9 -25
  124. package/dist/utils/browser-storage/localStorage.d.ts +4 -12
  125. package/dist/utils/browser-storage/localStorage.js +4 -12
  126. package/dist/utils/browser-storage/sessionStorage.d.ts +4 -12
  127. package/dist/utils/browser-storage/sessionStorage.js +4 -12
  128. package/dist/utils/custom-code/api.d.ts +3 -7
  129. package/dist/utils/custom-code/api.js +3 -7
  130. package/dist/utils/helpers/cleanupTooltipMessage.d.ts +1 -2
  131. package/dist/utils/helpers/cleanupTooltipMessage.js +1 -2
  132. package/dist/utils/helpers/goto.d.ts +1 -4
  133. package/dist/utils/helpers/goto.js +2 -7
  134. package/dist/utils/helpers/index.d.ts +1 -0
  135. package/dist/utils/helpers/index.js +1 -0
  136. package/dist/utils/helpers/noop.d.ts +1 -1
  137. package/dist/utils/helpers/noop.js +1 -1
  138. package/dist/utils/helpers/numbers.d.ts +4 -14
  139. package/dist/utils/helpers/numbers.js +4 -14
  140. package/dist/utils/helpers/objectsToModuleExports.d.ts +1 -3
  141. package/dist/utils/helpers/objectsToModuleExports.js +1 -3
  142. package/dist/utils/helpers/trimText.d.ts +1 -8
  143. package/dist/utils/helpers/trimText.js +1 -8
  144. package/dist/utils/index.d.ts +4 -0
  145. package/dist/utils/index.js +4 -0
  146. package/dist/utils/logger/index.d.ts +0 -2
  147. package/dist/utils/logger/index.js +0 -2
  148. package/dist/utils/webflow-canvas/attributes/getAllWebflowElementAttributes.d.ts +1 -3
  149. package/dist/utils/webflow-canvas/attributes/getAllWebflowElementAttributes.js +1 -3
  150. package/dist/utils/webflow-canvas/attributes/getInstanceNamesFromObject.d.ts +1 -5
  151. package/dist/utils/webflow-canvas/attributes/getInstanceNamesFromObject.js +1 -5
  152. package/dist/utils/webflow-canvas/attributes/getWebflowElementAttribute.d.ts +1 -4
  153. package/dist/utils/webflow-canvas/attributes/getWebflowElementAttribute.js +1 -4
  154. package/dist/utils/webflow-canvas/attributes/getWebflowElementTextContent.d.ts +1 -3
  155. package/dist/utils/webflow-canvas/attributes/getWebflowElementTextContent.js +1 -3
  156. package/dist/utils/webflow-canvas/attributes/removeWebflowElementAttribute.d.ts +1 -4
  157. package/dist/utils/webflow-canvas/attributes/removeWebflowElementAttribute.js +1 -4
  158. package/dist/utils/webflow-canvas/attributes/setStyles.d.ts +1 -3
  159. package/dist/utils/webflow-canvas/attributes/setStyles.js +1 -3
  160. package/dist/utils/webflow-canvas/attributes/setWebflowElementAttribute.d.ts +1 -8
  161. package/dist/utils/webflow-canvas/attributes/setWebflowElementAttribute.js +1 -13
  162. package/dist/utils/webflow-canvas/findInstanceElement.d.ts +0 -6
  163. package/dist/utils/webflow-canvas/findInstanceElement.js +1 -7
  164. package/dist/utils/webflow-canvas/getAllPages.d.ts +3 -10
  165. package/dist/utils/webflow-canvas/getAllPages.js +3 -10
  166. package/dist/utils/webflow-canvas/getSiteStagingUrl.d.ts +1 -4
  167. package/dist/utils/webflow-canvas/getSiteStagingUrl.js +1 -4
  168. package/dist/utils/webflow-canvas/index.d.ts +1 -0
  169. package/dist/utils/webflow-canvas/index.js +1 -0
  170. package/package.json +9 -2
  171. package/dist/stores/globalStore.d.ts +0 -10
  172. package/dist/stores/globalStore.js +0 -10
  173. package/dist/ui/components/ButtonGroup.svelte.d.ts +0 -28
  174. package/dist/ui/components/Checkbox.svelte +0 -94
  175. package/dist/ui/components/Checkbox.svelte.d.ts +0 -36
  176. package/dist/ui/components/Copy.svelte +0 -329
  177. package/dist/ui/components/Copy.svelte.d.ts +0 -35
  178. package/dist/ui/components/CustomModal.svelte +0 -192
  179. package/dist/ui/components/CustomModal.svelte.d.ts +0 -45
  180. package/dist/ui/components/DisableInEditMode.svelte +0 -66
  181. package/dist/ui/components/DisableInEditMode.svelte.d.ts +0 -33
  182. package/dist/ui/components/Divider.svelte +0 -31
  183. package/dist/ui/components/Divider.svelte.d.ts +0 -31
  184. package/dist/ui/components/Header.svelte +0 -30
  185. package/dist/ui/components/Header.svelte.d.ts +0 -20
  186. package/dist/ui/components/Iframe.svelte +0 -89
  187. package/dist/ui/components/Iframe.svelte.d.ts +0 -40
  188. package/dist/ui/components/InjectComponent.svelte +0 -297
  189. package/dist/ui/components/InjectComponent.svelte.d.ts +0 -27
  190. package/dist/ui/components/Modal.svelte +0 -139
  191. package/dist/ui/components/Modal.svelte.d.ts +0 -42
  192. package/dist/ui/components/Navbar.svelte +0 -132
  193. package/dist/ui/components/Navbar.svelte.d.ts +0 -29
  194. package/dist/ui/components/NoSettingsNeeded.svelte +0 -31
  195. package/dist/ui/components/Notification.svelte +0 -193
  196. package/dist/ui/components/Notification.svelte.d.ts +0 -64
  197. package/dist/ui/components/PlusMinusButton.svelte +0 -91
  198. package/dist/ui/components/PlusMinusButton.svelte.d.ts +0 -22
  199. package/dist/ui/components/PreviewBar.svelte +0 -40
  200. package/dist/ui/components/PreviewBar.svelte.d.ts +0 -20
  201. package/dist/ui/components/ScrollableContent.svelte +0 -18
  202. package/dist/ui/components/ScrollableContent.svelte.d.ts +0 -31
  203. package/dist/ui/components/Section.svelte +0 -97
  204. package/dist/ui/components/Section.svelte.d.ts +0 -50
  205. package/dist/ui/components/Spacer.svelte +0 -9
  206. package/dist/ui/components/Spacer.svelte.d.ts +0 -22
  207. package/dist/ui/components/SpinnerPlusMinus.svelte +0 -75
  208. package/dist/ui/components/SpinnerPlusMinus.svelte.d.ts +0 -23
  209. package/dist/ui/components/SpinnerUpDown.svelte +0 -194
  210. package/dist/ui/components/SpinnerUpDown.svelte.d.ts +0 -31
  211. package/dist/ui/components/Tabs.svelte +0 -71
  212. package/dist/ui/components/Tabs.svelte.d.ts +0 -26
  213. package/dist/ui/components/ToggleItem.svelte +0 -29
  214. package/dist/ui/components/ToggleList.svelte +0 -57
  215. package/dist/ui/components/buttons/FooterButton.svelte.d.ts +0 -10
  216. package/dist/ui/components/buttons/index.d.ts +0 -5
  217. package/dist/ui/components/buttons/index.js +0 -5
  218. package/dist/ui/components/clickable/Clickable.stories.svelte +0 -213
  219. package/dist/ui/components/clickable/Clickable.svelte +0 -93
  220. package/dist/ui/components/clickable/Clickable.svelte.d.ts +0 -4
  221. package/dist/ui/components/clickable/index.d.ts +0 -2
  222. package/dist/ui/components/clickable/index.js +0 -1
  223. package/dist/ui/components/clickable/types.d.ts +0 -17
  224. package/dist/utils/api/copyPaste/index.d.ts +0 -18
  225. /package/dist/ui/components/{buttons → button-group}/types.js +0 -0
  226. /package/dist/ui/components/{clickable → checkbox}/types.js +0 -0
  227. /package/dist/{utils/api/copyPaste/index.js → ui/components/controlled-buttons/types.js} +0 -0
  228. /package/dist/ui/components/layout/{ExampleLayout.svelte.d.ts → examples/ExampleLayout.svelte.d.ts} +0 -0
  229. /package/dist/ui/components/{ImageUpload.svelte.d.ts → shared/ImageUpload.svelte.d.ts} +0 -0
  230. /package/dist/ui/components/{SelectBodyOrDivBlock.svelte.d.ts → shared/SelectBodyOrDivBlock.svelte.d.ts} +0 -0
@@ -74,6 +74,22 @@ declare const meta: {
74
74
  control: string;
75
75
  description: string;
76
76
  };
77
+ showSteppers: {
78
+ control: string;
79
+ description: string;
80
+ };
81
+ step: {
82
+ control: string;
83
+ description: string;
84
+ };
85
+ min: {
86
+ control: string;
87
+ description: string;
88
+ };
89
+ max: {
90
+ control: string;
91
+ description: string;
92
+ };
77
93
  };
78
94
  };
79
95
  export default meta;
@@ -84,6 +100,14 @@ export declare const WithPlaceholder: Story;
84
100
  export declare const EmailInput: Story;
85
101
  export declare const PasswordInput: Story;
86
102
  export declare const NumberInput: Story;
103
+ export declare const NumberWithSteppers: Story;
104
+ export declare const SteppersWithCustomStep: Story;
105
+ export declare const SteppersWithMinMax: Story;
106
+ export declare const SteppersWithDecimals: Story;
107
+ export declare const SteppersAtMinValue: Story;
108
+ export declare const SteppersAtMaxValue: Story;
109
+ export declare const SteppersDisabled: Story;
110
+ export declare const SteppersReadonly: Story;
87
111
  export declare const Disabled: Story;
88
112
  export declare const ReadOnly: Story;
89
113
  export declare const Invalid: Story;
@@ -69,6 +69,22 @@ const meta = {
69
69
  minLength: {
70
70
  control: 'number',
71
71
  description: 'Minimum input length'
72
+ },
73
+ showSteppers: {
74
+ control: 'boolean',
75
+ description: 'Shows increment/decrement buttons for number inputs (requires type="number")'
76
+ },
77
+ step: {
78
+ control: 'number',
79
+ description: 'Step value for increment/decrement (default: 1)'
80
+ },
81
+ min: {
82
+ control: 'number',
83
+ description: 'Minimum value for number input'
84
+ },
85
+ max: {
86
+ control: 'number',
87
+ description: 'Maximum value for number input'
72
88
  }
73
89
  }
74
90
  };
@@ -109,6 +125,88 @@ export const NumberInput = {
109
125
  placeholder: 'Enter a number...'
110
126
  }
111
127
  };
128
+ // Stepper functionality
129
+ export const NumberWithSteppers = {
130
+ args: {
131
+ type: 'number',
132
+ showSteppers: true,
133
+ value: '10',
134
+ placeholder: 'Enter a number...'
135
+ }
136
+ };
137
+ export const SteppersWithCustomStep = {
138
+ args: {
139
+ type: 'number',
140
+ showSteppers: true,
141
+ value: '5',
142
+ step: 5,
143
+ placeholder: 'Increments by 5...'
144
+ }
145
+ };
146
+ export const SteppersWithMinMax = {
147
+ args: {
148
+ type: 'number',
149
+ showSteppers: true,
150
+ value: '50',
151
+ min: 0,
152
+ max: 100,
153
+ step: 10,
154
+ placeholder: 'Range: 0-100, Step: 10'
155
+ }
156
+ };
157
+ export const SteppersWithDecimals = {
158
+ args: {
159
+ type: 'number',
160
+ showSteppers: true,
161
+ value: '1.5',
162
+ step: 0.5,
163
+ min: 0,
164
+ max: 10,
165
+ placeholder: 'Decimal steps...'
166
+ }
167
+ };
168
+ export const SteppersAtMinValue = {
169
+ args: {
170
+ type: 'number',
171
+ showSteppers: true,
172
+ value: '0',
173
+ min: 0,
174
+ max: 100,
175
+ step: 1,
176
+ placeholder: 'At minimum value'
177
+ }
178
+ };
179
+ export const SteppersAtMaxValue = {
180
+ args: {
181
+ type: 'number',
182
+ showSteppers: true,
183
+ value: '100',
184
+ min: 0,
185
+ max: 100,
186
+ step: 1,
187
+ placeholder: 'At maximum value'
188
+ }
189
+ };
190
+ export const SteppersDisabled = {
191
+ args: {
192
+ type: 'number',
193
+ showSteppers: true,
194
+ value: '25',
195
+ disabled: true,
196
+ step: 5,
197
+ placeholder: 'Disabled with steppers'
198
+ }
199
+ };
200
+ export const SteppersReadonly = {
201
+ args: {
202
+ type: 'number',
203
+ showSteppers: true,
204
+ value: '75',
205
+ readonly: true,
206
+ step: 5,
207
+ placeholder: 'Readonly with steppers'
208
+ }
209
+ };
112
210
  // States
113
211
  export const Disabled = {
114
212
  args: {
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import { onMount } from 'svelte';
4
4
 
5
+ import { ChevronIcon } from '../../icons';
5
6
  import { Text, Tooltip } from '..';
6
7
  import type { InputProps } from './types.js';
7
8
 
@@ -23,33 +24,80 @@
23
24
  type = 'text',
24
25
  alert = null,
25
26
  pill = null,
27
+ showSteppers = false,
28
+ step = 1,
29
+ min = undefined,
30
+ max = undefined,
26
31
  oninput,
27
32
  onblur,
28
33
  onfocus,
29
34
  onkeydown,
35
+ onValueChange,
30
36
  class: className = '',
31
37
  children,
32
38
  ...restProps
33
39
  }: InputProps = $props();
34
40
 
41
+ // Validation: showSteppers can only be used with type="number"
42
+ if (showSteppers && type !== 'number') {
43
+ throw new Error('showSteppers can only be used when type="number"');
44
+ }
45
+
46
+ // Validation: showSteppers and units cannot be used together
47
+ if (showSteppers && units) {
48
+ throw new Error('showSteppers and units cannot be used together');
49
+ }
50
+
35
51
  // Component state using Svelte 5 runes
36
52
  let inputElement: HTMLInputElement | undefined = $state();
37
53
  let pillElement: HTMLSpanElement | undefined = $state();
38
54
  let pillWidth = $state(0);
39
55
 
40
- // Use writable derived for reactive state management
41
- let currInputValue = $derived.by(() => value);
56
+ // Internal reactive state for input value that updates with steppers
57
+ let internalValue = $derived(value);
58
+
59
+ // Track HTML5 validation state
60
+ let isValidationInvalid = $state(false);
61
+
62
+ // Use internal value for reactive state management
63
+ let currInputValue = $derived(internalValue);
42
64
  let computedColor = $derived(currInputValue ? 'var(--actionPrimaryText)' : 'var(--text3)');
43
65
 
44
66
  let hasPill = $derived(currInputValue && pill);
45
67
  let hasAlert = $derived(alert?.message);
46
68
 
69
+ // Parse numeric value for stepper operations
70
+ let numericValue = $derived.by(() => {
71
+ if (!showSteppers || !currInputValue) return 0;
72
+ const parsed = parseFloat(currInputValue);
73
+ return isNaN(parsed) ? 0 : parsed;
74
+ });
75
+
76
+ // Check if increment/decrement buttons should be disabled
77
+ let canIncrement = $derived.by(() => {
78
+ if (!showSteppers || disabled || readonly) return false;
79
+ if (max !== undefined) return numericValue < max;
80
+ return true;
81
+ });
82
+
83
+ let canDecrement = $derived.by(() => {
84
+ if (!showSteppers || disabled || readonly) return false;
85
+ if (min !== undefined) return numericValue > min;
86
+ return true;
87
+ });
88
+
89
+ // Sync external value prop changes to internal state
90
+ $effect(() => {
91
+ internalValue = value;
92
+ });
93
+
47
94
  let wrapperClasses = $derived(
48
95
  `
49
96
  webflow-input-wrapper
50
97
  ${units ? 'units' : ''}
98
+ ${showSteppers ? 'steppers' : ''}
51
99
  ${disabled ? 'disabled' : ''}
52
- ${invalid || hasAlert ? 'invalid' : ''}
100
+ ${invalid || hasAlert || isValidationInvalid ? 'invalid' : ''}
53
101
  ${className}
54
102
  `
55
103
  .trim()
@@ -134,6 +182,51 @@
134
182
  }
135
183
  });
136
184
 
185
+ /**
186
+ * Updates validation state based on HTML5 input validity
187
+ */
188
+ const updateValidationState = () => {
189
+ if (inputElement && type === 'number') {
190
+ // Check if the input has any validation errors
191
+ isValidationInvalid = !inputElement.validity.valid;
192
+ } else {
193
+ isValidationInvalid = false;
194
+ }
195
+ };
196
+
197
+ // Update validation state when input value changes
198
+ $effect(() => {
199
+ if (inputElement) {
200
+ // Use setTimeout to ensure the input element has been updated
201
+ setTimeout(updateValidationState, 0);
202
+ }
203
+ });
204
+
205
+ // Listen for form reset events to update internal state
206
+ $effect(() => {
207
+ if (inputElement) {
208
+ const form = inputElement.closest('form');
209
+ if (form) {
210
+ const handleFormReset = () => {
211
+ // Use setTimeout to allow the form reset to complete first
212
+ setTimeout(() => {
213
+ if (inputElement) {
214
+ const resetValue = inputElement.value;
215
+ internalValue = resetValue;
216
+ updateValidationState();
217
+ }
218
+ }, 0);
219
+ };
220
+
221
+ form.addEventListener('reset', handleFormReset);
222
+
223
+ return () => {
224
+ form.removeEventListener('reset', handleFormReset);
225
+ };
226
+ }
227
+ }
228
+ });
229
+
137
230
  /**
138
231
  * Handles input events
139
232
  */
@@ -141,10 +234,24 @@
141
234
  const target = event.target as HTMLInputElement;
142
235
  const inputValue = target?.value?.trim() || '';
143
236
 
237
+ // Update internal state for reactivity
238
+ internalValue = inputValue;
239
+
144
240
  if (pill) {
145
241
  setTimeout(updatePillPosition, 0);
146
242
  }
147
243
 
244
+ // Call numeric value change handler for any number input when value is numeric
245
+ if (type === 'number' && inputValue) {
246
+ const parsed = parseFloat(inputValue);
247
+ if (!isNaN(parsed)) {
248
+ onValueChange?.(parsed);
249
+ }
250
+ }
251
+
252
+ // Update validation state for real-time feedback
253
+ updateValidationState();
254
+
148
255
  oninput?.(inputValue);
149
256
  };
150
257
 
@@ -169,9 +276,78 @@
169
276
  * Handles keydown events
170
277
  */
171
278
  const handleKeydown = (event: KeyboardEvent) => {
279
+ // Handle arrow key increments/decrements when steppers are enabled
280
+ if (showSteppers && !disabled && !readonly) {
281
+ if (event.key === 'ArrowUp') {
282
+ event.preventDefault();
283
+ handleIncrement();
284
+ } else if (event.key === 'ArrowDown') {
285
+ event.preventDefault();
286
+ handleDecrement();
287
+ }
288
+ }
289
+
172
290
  onkeydown?.(event);
173
291
  };
174
292
 
293
+ /**
294
+ * Handles increment button click
295
+ */
296
+ const handleIncrement = () => {
297
+ if (!canIncrement) return;
298
+
299
+ const currentNum = numericValue;
300
+ let newValue = currentNum + step;
301
+
302
+ // Apply max constraint
303
+ if (max !== undefined && newValue > max) {
304
+ newValue = max;
305
+ }
306
+
307
+ const newStringValue = newValue.toString();
308
+
309
+ // Update internal state for reactivity
310
+ internalValue = newStringValue;
311
+
312
+ // Update the input element value directly
313
+ if (inputElement) {
314
+ inputElement.value = newStringValue;
315
+ }
316
+
317
+ // Trigger change events
318
+ onValueChange?.(newValue);
319
+ oninput?.(newStringValue);
320
+ };
321
+
322
+ /**
323
+ * Handles decrement button click
324
+ */
325
+ const handleDecrement = () => {
326
+ if (!canDecrement) return;
327
+
328
+ const currentNum = numericValue;
329
+ let newValue = currentNum - step;
330
+
331
+ // Apply min constraint
332
+ if (min !== undefined && newValue < min) {
333
+ newValue = min;
334
+ }
335
+
336
+ const newStringValue = newValue.toString();
337
+
338
+ // Update internal state for reactivity
339
+ internalValue = newStringValue;
340
+
341
+ // Update the input element value directly
342
+ if (inputElement) {
343
+ inputElement.value = newStringValue;
344
+ }
345
+
346
+ // Trigger change events
347
+ onValueChange?.(newValue);
348
+ oninput?.(newStringValue);
349
+ };
350
+
175
351
  /**
176
352
  * Gets the tooltip background color based on alert type
177
353
  */
@@ -190,6 +366,78 @@
190
366
  };
191
367
  </script>
192
368
 
369
+ {#snippet inputField()}
370
+ <input
371
+ bind:this={inputElement}
372
+ {id}
373
+ class={inputClasses}
374
+ {placeholder}
375
+ {readonly}
376
+ {disabled}
377
+ {type}
378
+ {min}
379
+ {max}
380
+ {step}
381
+ maxlength={maxLength}
382
+ minlength={minLength}
383
+ value={currInputValue}
384
+ defaultValue={value}
385
+ oninput={handleInput}
386
+ onfocus={handleFocus}
387
+ onkeydown={handleKeydown}
388
+ onblur={handleBlur}
389
+ style="font-size: {fontSize}; color: {computedColor};"
390
+ {...restProps}
391
+ />
392
+ {/snippet}
393
+
394
+ {#snippet inputWrapper()}
395
+ <div class={wrapperClasses} style="height: {height}; width: {width};" role="group">
396
+ {#if currInputValue && pill}
397
+ <span
398
+ class="pill"
399
+ class:blue={pill === 'blue'}
400
+ class:gray={pill === 'gray'}
401
+ bind:this={pillElement}
402
+ style="width: {pillWidth}px;"
403
+ ></span>
404
+ {/if}
405
+
406
+ {@render inputField()}
407
+
408
+ {#if units && !showSteppers}
409
+ <div class="input-units-steppers">{units}</div>
410
+ {/if}
411
+
412
+ {#if showSteppers && !units}
413
+ <div class="input-units-steppers steppers">
414
+ <button
415
+ type="button"
416
+ class="stepper-button stepper-up"
417
+ disabled={!canIncrement}
418
+ onclick={handleIncrement}
419
+ aria-label="Increment value"
420
+ >
421
+ <span class="stepper-button-icon">&#8963;</span>
422
+ </button>
423
+ <button
424
+ type="button"
425
+ class="stepper-button stepper-down"
426
+ disabled={!canDecrement}
427
+ onclick={handleDecrement}
428
+ aria-label="Decrement value"
429
+ >
430
+ <span class="stepper-button-icon">&#8963;</span>
431
+ </button>
432
+ </div>
433
+ {/if}
434
+
435
+ {#if children}
436
+ {@render children()}
437
+ {/if}
438
+ </div>
439
+ {/snippet}
440
+
193
441
  {#if hasAlert}
194
442
  <Tooltip
195
443
  message={alert?.message || ''}
@@ -206,85 +454,11 @@
206
454
  class="input-tooltip"
207
455
  >
208
456
  {#snippet target()}
209
- <div class={wrapperClasses} style="height: {height}; width: {width};" role="group">
210
- {#if currInputValue && pill}
211
- <span
212
- class="pill"
213
- class:blue={pill === 'blue'}
214
- class:gray={pill === 'gray'}
215
- bind:this={pillElement}
216
- style="width: {pillWidth}px;"
217
- ></span>
218
- {/if}
219
-
220
- <input
221
- bind:this={inputElement}
222
- {id}
223
- class={inputClasses}
224
- {placeholder}
225
- {readonly}
226
- {disabled}
227
- {type}
228
- maxlength={maxLength}
229
- minlength={minLength}
230
- value={currInputValue}
231
- oninput={handleInput}
232
- onfocus={handleFocus}
233
- onkeydown={handleKeydown}
234
- onblur={handleBlur}
235
- style="font-size: {fontSize}; color: {computedColor};"
236
- {...restProps}
237
- />
238
-
239
- {#if units}
240
- <div class="input-units">{units}</div>
241
- {/if}
242
-
243
- {#if children}
244
- {@render children()}
245
- {/if}
246
- </div>
457
+ {@render inputWrapper()}
247
458
  {/snippet}
248
459
  </Tooltip>
249
460
  {:else}
250
- <div class={wrapperClasses} style="height: {height}; width: {width};" role="group">
251
- {#if currInputValue && pill}
252
- <span
253
- class="pill"
254
- class:blue={pill === 'blue'}
255
- class:gray={pill === 'gray'}
256
- bind:this={pillElement}
257
- style="width: {pillWidth}px;"
258
- ></span>
259
- {/if}
260
-
261
- <input
262
- bind:this={inputElement}
263
- {id}
264
- class={inputClasses}
265
- {placeholder}
266
- {readonly}
267
- {disabled}
268
- {type}
269
- maxlength={maxLength}
270
- minlength={minLength}
271
- value={currInputValue}
272
- oninput={handleInput}
273
- onfocus={handleFocus}
274
- onkeydown={handleKeydown}
275
- onblur={handleBlur}
276
- style="font-size: {fontSize}; color: {computedColor};"
277
- {...restProps}
278
- />
279
-
280
- {#if units}
281
- <div class="input-units">{units}</div>
282
- {/if}
283
-
284
- {#if children}
285
- {@render children()}
286
- {/if}
287
- </div>
461
+ {@render inputWrapper()}
288
462
  {/if}
289
463
 
290
464
  <style>
@@ -324,10 +498,21 @@
324
498
  grid-template-columns: 1fr auto;
325
499
  padding: 0 0 0 4px;
326
500
  }
501
+
502
+ .webflow-input-wrapper.steppers {
503
+ grid-template-columns: 1fr auto;
504
+ padding: 0 0 0 4px;
505
+ }
506
+
327
507
  .webflow-input-wrapper.units .webflow-input {
328
508
  padding-right: 4px;
329
509
  }
330
- .input-units {
510
+
511
+ .webflow-input-wrapper.steppers .webflow-input {
512
+ padding-right: 4px;
513
+ }
514
+
515
+ .input-units-steppers {
331
516
  display: flex;
332
517
  align-items: center;
333
518
  justify-content: center;
@@ -346,6 +531,62 @@
346
531
  text-align: center;
347
532
  opacity: 0.75;
348
533
  }
534
+
535
+ .input-units-steppers.steppers {
536
+ justify-content: center;
537
+ flex-direction: column;
538
+ width: 16px;
539
+ gap: 1px;
540
+ background-color: var(--border1);
541
+ margin-top: -2px;
542
+ margin-bottom: -2px;
543
+ }
544
+
545
+ .stepper-button {
546
+ border: none;
547
+ width: 16px;
548
+ background-color: var(--actionSecondaryBackground);
549
+ color: var(--text2);
550
+ font-size: 0.8em;
551
+ line-height: 1;
552
+ height: 48%;
553
+ display: flex;
554
+ align-items: center;
555
+ justify-content: center;
556
+ padding: 0;
557
+ cursor: pointer;
558
+ transition: background-color 0.2s ease;
559
+ }
560
+
561
+ .stepper-button-icon {
562
+ width: 8px;
563
+ height: 5px;
564
+ }
565
+
566
+ .stepper-button:hover:not(:disabled) {
567
+ background-color: var(--hoverColor);
568
+ }
569
+
570
+ .stepper-button:disabled {
571
+ opacity: 0.75;
572
+ cursor: not-allowed;
573
+ }
574
+
575
+ .stepper-up {
576
+ transform: rotate(0deg);
577
+ border-radius: 0 1px 0 0;
578
+ }
579
+
580
+ .stepper-down {
581
+ transform: rotate(180deg);
582
+ border-radius: 1px 0 0 0;
583
+ }
584
+
585
+ .stepper-button :global(svg) {
586
+ width: 16px;
587
+ height: 16px;
588
+ }
589
+
349
590
  .webflow-input {
350
591
  padding: 0;
351
592
  margin: 0;
@@ -28,7 +28,8 @@ export interface InputProps extends Omit<HTMLInputAttributes, 'onblur' | 'onfocu
28
28
  */
29
29
  color?: string;
30
30
  /**
31
- * Optional: If set it will add an element to the right of the input field and show the unit
31
+ * Optional: If set it will add an element to the right of the input field and show the unit.
32
+ * Cannot be used together with showSteppers
32
33
  */
33
34
  units?: string;
34
35
  /**
@@ -76,6 +77,23 @@ export interface InputProps extends Omit<HTMLInputAttributes, 'onblur' | 'onfocu
76
77
  * Possible values: 'blue', 'gray', or null/undefined for no pill
77
78
  */
78
79
  pill?: PillVariant;
80
+ /**
81
+ * Shows increment/decrement buttons for number inputs.
82
+ * Only available when type="number"
83
+ */
84
+ showSteppers?: boolean;
85
+ /**
86
+ * Step value for increment/decrement buttons.
87
+ */
88
+ step?: number;
89
+ /**
90
+ * Minimum value for number input.
91
+ */
92
+ min?: number;
93
+ /**
94
+ * Maximum value for number input.
95
+ */
96
+ max?: number;
79
97
  /**
80
98
  * Event handler for input changes - receives the trimmed string value
81
99
  */
@@ -92,6 +110,11 @@ export interface InputProps extends Omit<HTMLInputAttributes, 'onblur' | 'onfocu
92
110
  * Event handler for keydown events - receives the standard KeyboardEvent
93
111
  */
94
112
  onkeydown?: (event: KeyboardEvent) => void;
113
+ /**
114
+ * Event handler for numeric value changes - receives the numeric value
115
+ * Only called when showSteppers is true and value is a valid number
116
+ */
117
+ onValueChange?: (value: number) => void;
95
118
  /**
96
119
  * Additional CSS classes
97
120
  */
@@ -107,3 +130,6 @@ export interface InputEvent {
107
130
  export interface BlurEvent {
108
131
  value: string;
109
132
  }
133
+ export interface NumericValueChangeEvent {
134
+ value: number;
135
+ }
@@ -1,11 +1,11 @@
1
1
  <script module lang="ts">
2
2
  import { defineMeta } from '@storybook/addon-svelte-csf';
3
3
 
4
- import ExampleLayout from './ExampleLayout.svelte';
4
+ import { Wrapper } from './examples';
5
5
 
6
6
  const { Story } = defineMeta({
7
7
  title: 'UI/Layout',
8
- component: ExampleLayout,
8
+ component: Wrapper,
9
9
  tags: ['autodocs'],
10
10
  parameters: {
11
11
  layout: 'centered',
@@ -20,5 +20,5 @@
20
20
  </script>
21
21
 
22
22
  <Story name="Default">
23
- <ExampleLayout />
23
+ <Wrapper />
24
24
  </Story>