@finsweet/webflow-apps-utils 1.0.4 → 1.0.5

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 (35) hide show
  1. package/dist/providers/GlobalProvider.stories.d.ts +5 -0
  2. package/dist/providers/GlobalProvider.stories.js +419 -0
  3. package/dist/providers/GlobalProviderDemo.svelte +266 -0
  4. package/dist/providers/GlobalProviderDemo.svelte.d.ts +3 -0
  5. package/dist/providers/configuratorUtils.d.ts +11 -14
  6. package/dist/providers/configuratorUtils.js +68 -115
  7. package/dist/providers/index.d.ts +1 -1
  8. package/dist/providers/index.js +1 -1
  9. package/dist/router/Router.stories.js +519 -2
  10. package/dist/stores/forms/Form.stories.d.ts +5 -0
  11. package/dist/stores/forms/Form.stories.js +342 -0
  12. package/dist/stores/forms/FormDemo.svelte +545 -0
  13. package/dist/stores/forms/FormDemo.svelte.d.ts +18 -0
  14. package/dist/ui/components/button/Button.svelte +1 -1
  15. package/dist/ui/components/copy-text/CopyText.stories.js +1 -1
  16. package/dist/ui/components/copy-text/CopyText.svelte +17 -19
  17. package/dist/ui/components/layout/Layout.svelte +38 -5
  18. package/dist/ui/components/layout/Layout.svelte.d.ts +24 -1
  19. package/dist/ui/components/layout/examples/ExampleLayout.svelte +12 -12
  20. package/dist/ui/components/section/Section.svelte +4 -2
  21. package/dist/ui/index.css +6 -2
  22. package/dist/utils/diff-mapper/DiffMapper.stories.d.ts +5 -0
  23. package/dist/utils/diff-mapper/DiffMapper.stories.js +185 -0
  24. package/dist/utils/diff-mapper/DiffMapperDemo.svelte +351 -0
  25. package/dist/utils/diff-mapper/DiffMapperDemo.svelte.d.ts +18 -0
  26. package/dist/utils/diff-mapper/deepDiffMapper.d.ts +31 -0
  27. package/dist/utils/diff-mapper/deepDiffMapper.js +264 -0
  28. package/dist/utils/diff-mapper/index.d.ts +1 -0
  29. package/dist/utils/diff-mapper/index.js +1 -0
  30. package/dist/utils/index.d.ts +1 -0
  31. package/dist/utils/index.js +1 -0
  32. package/package.json +1 -1
  33. package/dist/providers/GlobalProvider.mdx +0 -322
  34. package/dist/router/Router.mdx +0 -958
  35. package/dist/stores/docs/Form.mdx +0 -542
@@ -9,32 +9,56 @@
9
9
  WarningCircleOutlineIcon
10
10
  } from '../../icons';
11
11
 
12
+ import Section from '../section/Section.svelte';
12
13
  import { Tooltip } from '../tooltip';
13
14
  import { EditModeMessage } from './common';
14
15
  import type { LayoutTab } from './types';
15
16
 
16
17
  interface LayoutProps extends HTMLAttributes<HTMLDivElement> {
18
+ /** The currently active tab path */
17
19
  activeTab: string;
20
+ /** Array of available tabs to display in the navbar */
18
21
  tabs: LayoutTab[];
22
+ /** Function to handle tab switching */
19
23
  switchTab: (tab: string) => void;
24
+ /** Unique key for the form context */
20
25
  formKey: string;
21
- editMode?: boolean;
26
+ /** Whether to show the edit mode message banner */
27
+ showEditModeMessage?: boolean;
28
+ /** Whether to display the footer section */
22
29
  showFooter?: boolean;
30
+ /** Whether to display the sidebar */
23
31
  showSidebar?: boolean;
32
+ /** Whether to display the tab navigation bar */
24
33
  showTabs?: boolean;
34
+ /** The height at which the main content area becomes scrollable */
35
+ mainContentScrollableAt?: number;
36
+ /** Whether to display the preview bar */
25
37
  showPreviewBar?: boolean;
38
+ /** Width of the sidebar (CSS value) */
26
39
  sidebarWidth?: string;
40
+ /** Whether to use container mode (100% dimensions) instead of viewport mode */
27
41
  containerMode?: boolean;
42
+ /** Size variant for the footer */
28
43
  footerSize?: 'normal' | 'large';
44
+ /** Array of notification objects for tab status indicators */
29
45
  notifications?: Array<{
46
+ /** Tab path this notification applies to */
30
47
  path: string;
48
+ /** Whether the notification indicates success */
31
49
  success: boolean;
50
+ /** Notification message content */
32
51
  message: string;
52
+ /** Whether to show the notification badge */
33
53
  showNotification: boolean;
34
54
  }>;
55
+ /** Sidebar content snippet */
35
56
  sidebar?: Snippet;
57
+ /** Main content area snippet */
36
58
  main?: Snippet;
59
+ /** Preview bar content snippet */
37
60
  previewBar?: Snippet;
61
+ /** Footer content snippet */
38
62
  footer?: Snippet;
39
63
  }
40
64
 
@@ -43,7 +67,8 @@
43
67
  tabs,
44
68
  switchTab,
45
69
  formKey,
46
- editMode = false,
70
+ mainContentScrollableAt,
71
+ showEditModeMessage = false,
47
72
  showFooter = true,
48
73
  showSidebar = true,
49
74
  showTabs = true,
@@ -216,8 +241,16 @@
216
241
  <div class="main-content" data-area="main">
217
242
  {#if main}
218
243
  <div class="main-content-container">
219
- <EditModeMessage />
220
- {@render main()}
244
+ {#if showEditModeMessage}
245
+ <EditModeMessage />
246
+ {/if}
247
+ {#if mainContentScrollableAt}
248
+ <Section height={`${mainContentScrollableAt}px`} scrollable padding="0">
249
+ {@render main()}
250
+ </Section>
251
+ {:else}
252
+ {@render main()}
253
+ {/if}
221
254
  </div>
222
255
  {:else}
223
256
  <div class="main-placeholder">
@@ -231,7 +264,7 @@
231
264
  <li>Show Tabs: {showTabs}</li>
232
265
  <li>Show Preview Bar: {showPreviewBar}</li>
233
266
  <li>Show Footer: {showFooter}</li>
234
- <li>Edit Mode: {editMode}</li>
267
+ <li>Edit Mode: {showEditModeMessage}</li>
235
268
  </ul>
236
269
  </div>
237
270
  </div>
@@ -2,27 +2,50 @@ import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  import type { LayoutTab } from './types';
4
4
  interface LayoutProps extends HTMLAttributes<HTMLDivElement> {
5
+ /** The currently active tab path */
5
6
  activeTab: string;
7
+ /** Array of available tabs to display in the navbar */
6
8
  tabs: LayoutTab[];
9
+ /** Function to handle tab switching */
7
10
  switchTab: (tab: string) => void;
11
+ /** Unique key for the form context */
8
12
  formKey: string;
9
- editMode?: boolean;
13
+ /** Whether to show the edit mode message banner */
14
+ showEditModeMessage?: boolean;
15
+ /** Whether to display the footer section */
10
16
  showFooter?: boolean;
17
+ /** Whether to display the sidebar */
11
18
  showSidebar?: boolean;
19
+ /** Whether to display the tab navigation bar */
12
20
  showTabs?: boolean;
21
+ /** The height at which the main content area becomes scrollable */
22
+ mainContentScrollableAt?: number;
23
+ /** Whether to display the preview bar */
13
24
  showPreviewBar?: boolean;
25
+ /** Width of the sidebar (CSS value) */
14
26
  sidebarWidth?: string;
27
+ /** Whether to use container mode (100% dimensions) instead of viewport mode */
15
28
  containerMode?: boolean;
29
+ /** Size variant for the footer */
16
30
  footerSize?: 'normal' | 'large';
31
+ /** Array of notification objects for tab status indicators */
17
32
  notifications?: Array<{
33
+ /** Tab path this notification applies to */
18
34
  path: string;
35
+ /** Whether the notification indicates success */
19
36
  success: boolean;
37
+ /** Notification message content */
20
38
  message: string;
39
+ /** Whether to show the notification badge */
21
40
  showNotification: boolean;
22
41
  }>;
42
+ /** Sidebar content snippet */
23
43
  sidebar?: Snippet;
44
+ /** Main content area snippet */
24
45
  main?: Snippet;
46
+ /** Preview bar content snippet */
25
47
  previewBar?: Snippet;
48
+ /** Footer content snippet */
26
49
  footer?: Snippet;
27
50
  }
28
51
  declare const Layout: import("svelte").Component<LayoutProps, {}, "">;
@@ -18,20 +18,20 @@
18
18
  let showTabs = $state(true);
19
19
  let showPreviewBar = $state(true);
20
20
  let showFooter = $state(true);
21
- let editMode = $state(false);
21
+ let showEditModeMessage = $state(false);
22
22
  let sidebarWidth = $state('239px');
23
23
  let footerSize = $state<'normal' | 'large'>('normal');
24
24
  let showNotifications = $state(false);
25
25
  let notificationSuccess = $state(true);
26
26
  let inputValue = $state('');
27
27
 
28
- // Get app context and keep editMode in sync
28
+ // Get app context and keep showEditModeMessage in sync
29
29
  const appContext = useAppContext();
30
30
 
31
- // Subscribe to context changes to keep local editMode in sync
31
+ // Subscribe to context changes to keep local showEditModeMessage in sync
32
32
  appContext.subscribe((data) => {
33
- if (data?.editMode !== undefined && data.editMode !== editMode) {
34
- editMode = data.editMode;
33
+ if (data?.editMode !== undefined && data.editMode !== showEditModeMessage) {
34
+ showEditModeMessage = data.editMode;
35
35
  }
36
36
  });
37
37
 
@@ -92,8 +92,8 @@
92
92
  watchOptions: { watchAll: true, debounceMs: 50 }
93
93
  }
94
94
  });
95
- // Initialize local editMode state
96
- editMode = true;
95
+ // Initialize local showEditModeMessage state
96
+ showEditModeMessage = true;
97
97
  });
98
98
  </script>
99
99
 
@@ -182,12 +182,12 @@
182
182
  <div class="toggle-control">
183
183
  <input
184
184
  type="checkbox"
185
- bind:checked={editMode}
185
+ bind:checked={showEditModeMessage}
186
186
  id="edit-mode"
187
187
  class="checkbox-input"
188
188
  onchange={() => {
189
189
  const current = appContext.get();
190
- appContext.set({ ...current, editMode });
190
+ appContext.set({ ...current, editMode: showEditModeMessage });
191
191
  }}
192
192
  />
193
193
  <label for="edit-mode" class="checkbox-label">Edit Mode</label>
@@ -227,7 +227,7 @@
227
227
  {tabs}
228
228
  {switchTab}
229
229
  formKey="dummy-layout"
230
- {editMode}
230
+ {showEditModeMessage}
231
231
  {showFooter}
232
232
  {showSidebar}
233
233
  {showTabs}
@@ -329,11 +329,11 @@
329
329
  variant="secondary"
330
330
  icon={CheckCircleIcon}
331
331
  onclick={handleSaveClick}
332
- disabled={editMode}
332
+ disabled={showEditModeMessage}
333
333
  >
334
334
  Save Changes
335
335
  </Button>
336
- <Button variant="primary" onclick={handleApplyClick} disabled={editMode}>
336
+ <Button variant="primary" onclick={handleApplyClick} disabled={showEditModeMessage}>
337
337
  Apply Settings
338
338
  </Button>
339
339
  {/snippet}
@@ -140,7 +140,7 @@
140
140
  scrollbars: {
141
141
  theme: 'os-theme-dark',
142
142
  visibility: 'auto',
143
- autoHide: 'never',
143
+ autoHide: 'leave',
144
144
  autoHideDelay: 800
145
145
  }
146
146
  };
@@ -186,6 +186,7 @@
186
186
  tooltipIconColor="var(--yellowText)"
187
187
  message={defaultDisabledMessage}
188
188
  width={disabledTooltipWidth}
189
+ class="not-allowed"
189
190
  >
190
191
  {#snippet target()}
191
192
  {@render sectionContent()}
@@ -271,6 +272,7 @@
271
272
  /* Disabled in edit mode styles */
272
273
  .section-wrap.disabled-in-edit-mode {
273
274
  pointer-events: none;
275
+ cursor: not-allowed;
274
276
  }
275
277
 
276
278
  .section-wrap.disabled-in-edit-mode :global(.label-popup .labels span) {
@@ -301,7 +303,7 @@
301
303
 
302
304
  /* OverlayScrollbars dark theme customization using design system */
303
305
  .section-wrap :global(.os-scrollbar) {
304
- --os-size: var(--sb-size, 6px);
306
+ --os-size: var(--sb-size);
305
307
  --os-padding-perpendicular: 2px;
306
308
  --os-padding-axis: 2px;
307
309
  }
package/dist/ui/index.css CHANGED
@@ -4,7 +4,7 @@
4
4
  /* custom scrollbar related */
5
5
  --sb-track-color: #1e1e1e;
6
6
  --sb-thumb-color: #373737;
7
- --sb-size: 6px;
7
+ --sb-size: 8px;
8
8
 
9
9
  /* Webflow colors */
10
10
  --background1: #292929;
@@ -264,9 +264,13 @@ label {
264
264
  .disabled,
265
265
  .login-required {
266
266
  cursor: not-allowed !important;
267
- opacity: 0.75 !important;
267
+ opacity: 0.8 !important;
268
268
  }
269
269
 
270
+ /* Used for disabled sections */
271
+ .not-allowed {
272
+ cursor: not-allowed !important;
273
+ }
270
274
  /* OverlayScrollbars Global Theme */
271
275
  :root {
272
276
  /* OverlayScrollbars theme variables using design system colors */
@@ -0,0 +1,5 @@
1
+ import type { Meta, StoryObj } from '@storybook/sveltekit';
2
+ declare const meta: Meta;
3
+ export default meta;
4
+ type Story = StoryObj<typeof meta>;
5
+ export declare const Interactive: Story;
@@ -0,0 +1,185 @@
1
+ import DiffMapperDemo from './DiffMapperDemo.svelte';
2
+ const meta = {
3
+ title: 'Utils/DiffMapper',
4
+ component: DiffMapperDemo,
5
+ parameters: {
6
+ layout: 'centered',
7
+ backgrounds: {
8
+ default: 'dark',
9
+ values: [
10
+ { name: 'dark', value: '#292929' },
11
+ { name: 'light', value: '#ffffff' }
12
+ ]
13
+ },
14
+ docs: {
15
+ description: {
16
+ component: `
17
+ # DiffMapper Utilities
18
+
19
+ The DiffMapper utility provides intelligent object comparison with type coercion, whitespace handling, and advanced diff detection. Built specifically for configurator state management and general object comparison needs.
20
+
21
+ ## Features
22
+
23
+ - **Smart Type Coercion**: Handles string/number/boolean conversions intelligently
24
+ - **Whitespace Handling**: Automatically trims and compares string values
25
+ - **Deep Comparison**: Recursively compares nested objects and arrays
26
+ - **Performance Optimized**: Includes caching and circular reference detection
27
+ - **Detailed Diff Output**: Provides granular change information
28
+ - **Type Safety**: Full TypeScript support with proper type definitions
29
+
30
+ ## API Reference
31
+
32
+ ### Core Functions
33
+
34
+ #### \`hasChangesViaDiff<T>(current: T, updated: T): boolean\`
35
+
36
+ Determines if there are any meaningful changes between two objects.
37
+
38
+ \`\`\`typescript
39
+ const config1 = { theme: 'dark', notifications: true };
40
+ const config2 = { theme: 'light', notifications: true };
41
+
42
+ const hasChanges = hasChangesViaDiff(config1, config2); // true
43
+ \`\`\`
44
+
45
+ **Features:**
46
+ - Automatic caching for performance
47
+ - Type coercion handling
48
+ - Whitespace normalization
49
+ - Deep nested comparison
50
+
51
+ #### \`getDetailedDiff<T>(current: T, updated: T): DiffResult | DiffMap\`
52
+
53
+ Returns a detailed diff map showing exactly what changed.
54
+
55
+ \`\`\`typescript
56
+ const user1 = {
57
+ profile: { name: 'John', age: 30 },
58
+ settings: { theme: 'dark' }
59
+ };
60
+
61
+ const user2 = {
62
+ profile: { name: 'John', age: 31 },
63
+ settings: { theme: 'light', notifications: true }
64
+ };
65
+
66
+ const diff = getDetailedDiff(user1, user2);
67
+ // Returns detailed diff structure showing exactly what changed
68
+ \`\`\`
69
+
70
+ ### Diff Types
71
+
72
+ \`\`\`typescript
73
+ enum DiffType {
74
+ UNCHANGED = 'UNCHANGED', // Value is identical
75
+ UPDATED = 'UPDATED', // Value was modified
76
+ CREATED = 'CREATED', // Property was added
77
+ DELETED = 'DELETED' // Property was removed
78
+ }
79
+ \`\`\`
80
+
81
+ ## Type Coercion Examples
82
+
83
+ The diff mapper intelligently handles type coercion:
84
+
85
+ ### String/Number Coercion
86
+ \`\`\`typescript
87
+ // These are considered UNCHANGED
88
+ hasChangesViaDiff({ value: '42' }, { value: 42 }); // false
89
+ hasChangesViaDiff({ value: '0' }, { value: 0 }); // false
90
+ hasChangesViaDiff({ value: '' }, { value: 0 }); // false
91
+ \`\`\`
92
+
93
+ ### String/Boolean Coercion
94
+ \`\`\`typescript
95
+ // These are considered UNCHANGED
96
+ hasChangesViaDiff({ active: 'true' }, { active: true }); // false
97
+ hasChangesViaDiff({ active: 'false' }, { active: false }); // false
98
+ hasChangesViaDiff({ active: '' }, { active: false }); // false
99
+ \`\`\`
100
+
101
+ ### Whitespace Handling
102
+ \`\`\`typescript
103
+ // These are considered UNCHANGED
104
+ hasChangesViaDiff({ name: 'John' }, { name: ' John ' }); // false
105
+ hasChangesViaDiff({ value: '42' }, { value: ' 42 ' }); // false
106
+
107
+ // But these are UPDATED (whitespace-only strings ≠ numbers)
108
+ hasChangesViaDiff({ value: ' ' }, { value: 0 }); // true
109
+ hasChangesViaDiff({ value: ' ' }, { value: false }); // true
110
+ \`\`\`
111
+
112
+ ## Performance Features
113
+
114
+ ### Caching Strategy
115
+ Results are cached for 1 second to avoid expensive re-computation:
116
+
117
+ \`\`\`typescript
118
+ const cacheKey = JSON.stringify([current, updated]);
119
+ const cached = diffCache.get(cacheKey);
120
+
121
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
122
+ return cached.result;
123
+ }
124
+ \`\`\`
125
+
126
+ ### Memory Management
127
+ Automatic cache cleanup when size exceeds 100 entries or TTL expires.
128
+
129
+ ## GlobalProvider Integration
130
+
131
+ The diff mapper is seamlessly integrated with the GlobalProvider system:
132
+
133
+ \`\`\`typescript
134
+ import { useConfiguratorContext } from '../../providers';
135
+
136
+ const configurator = useConfiguratorContext<MyConfigType>();
137
+
138
+ // Automatic change detection using diff mapper
139
+ const hasChanged = configurator.hasChanged; // Uses hasChangesViaDiff internally
140
+
141
+ // Manual comparison
142
+ const currentConfig = configurator.configurator;
143
+ const cachedConfig = configurator.configuratorCache;
144
+ const changes = getDetailedDiff(currentConfig, cachedConfig);
145
+ \`\`\`
146
+
147
+ ## Usage with GlobalProvider
148
+
149
+ For manual configurator state management:
150
+
151
+ \`\`\`typescript
152
+ import {
153
+ createDefaultConfiguratorState,
154
+ hasConfiguratorChanged,
155
+ validateWatchOptions,
156
+ extractKeys,
157
+ createDebouncedUpdate
158
+ } from '../../providers';
159
+
160
+ // Create default configurator state
161
+ const defaultState = createDefaultConfiguratorState<MyConfigType>();
162
+
163
+ // Manual change detection with watch options
164
+ const watchOptions = { watchKeys: ['theme'], debounceMs: 100 };
165
+ const hasChanged = hasConfiguratorChanged(current, cached, watchOptions);
166
+ \`\`\`
167
+
168
+ Click the buttons in the interactive demo below to see these features in action!
169
+ `
170
+ }
171
+ }
172
+ },
173
+ tags: ['autodocs']
174
+ };
175
+ export default meta;
176
+ export const Interactive = {
177
+ name: 'Interactive Demo',
178
+ parameters: {
179
+ docs: {
180
+ description: {
181
+ story: 'Click the buttons to run diff examples and see results. All functionality is combined in a single interactive demo.'
182
+ }
183
+ }
184
+ }
185
+ };