sveltacular 1.0.0 → 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/README.md CHANGED
@@ -12,17 +12,23 @@ npm i sveltacular
12
12
 
13
13
  ## Quick Start
14
14
 
15
+ First, import the default styles to get all CSS variables and base styles:
16
+
15
17
  ```svelte
16
18
  <script lang="ts">
19
+ import 'sveltacular/styles.css';
17
20
  import { Button } from 'sveltacular';
18
21
  </script>
19
22
 
20
23
  <Button variant="primary" label="Hello World" />
21
24
  ```
22
25
 
26
+ **Note**: The styles import is required for components to render with default styling. If you prefer to provide your own theme, you can skip this import and define your own CSS variables (see [Theming](#theming) below).
27
+
23
28
  ## Component Catalog
24
29
 
25
30
  ### Forms
31
+
26
32
  - **Button** - Multiple variants (primary, secondary, positive, danger, outline)
27
33
  - **TextBox** - Text input with validation and formatting options
28
34
  - **NumberBox** - Number input with min/max/decimals
@@ -38,6 +44,7 @@ npm i sveltacular
38
44
  - **Form** - Form container with validation
39
45
 
40
46
  ### Generic Components
47
+
41
48
  - **Card** - Card container
42
49
  - **Pill** - Badge/pill component
43
50
  - **Badge** - Notification badge
@@ -52,6 +59,7 @@ npm i sveltacular
52
59
  - **List** - Styled list component
53
60
 
54
61
  ### Navigation
62
+
55
63
  - **AppBar** - Application bar
56
64
  - **SideBar** - Side navigation
57
65
  - **Breadcrumbs** - Breadcrumb navigation
@@ -62,16 +70,19 @@ npm i sveltacular
62
70
  - **Drawer** - Slide-out drawer
63
71
 
64
72
  ### Modals
73
+
65
74
  - **Modal** - Generic modal dialog
66
75
  - **Alert** - Alert dialog
67
76
  - **Confirm** - Confirmation dialog
68
77
  - **Prompt** - Input prompt dialog
69
78
 
70
79
  ### Tables
80
+
71
81
  - **Table** - Table component with header/body/footer
72
82
  - **DataGrid** - Advanced data grid
73
83
 
74
84
  ### Typography
85
+
75
86
  - **Headline** - Heading component
76
87
  - **Subtitle** - Subtitle component
77
88
  - **Text** - Text component
@@ -79,6 +90,7 @@ npm i sveltacular
79
90
  - **CodeBlock** - Code block
80
91
 
81
92
  ### Layout
93
+
82
94
  - **FlexRow** / **FlexCol** - Flexbox layout
83
95
  - **Grid** - Grid layout
84
96
 
@@ -102,11 +114,19 @@ import { CheckBox, CheckBoxGroup } from 'sveltacular/forms/check-box';
102
114
 
103
115
  ## Theming
104
116
 
105
- Sveltacular uses CSS variables for theming. See [THEMING.md](./THEMING.md) for a complete list of available CSS variables.
117
+ Sveltacular uses CSS variables for theming. When you import `sveltacular/styles.css`, all default CSS variables are included. You can override any of these variables to customize the appearance of components.
118
+
119
+ See [THEMING.md](./THEMING.md) for a complete list of available CSS variables.
120
+
121
+ ### Customizing the Theme
106
122
 
107
- Example:
123
+ Override CSS variables in your own stylesheet (after importing the default styles):
108
124
 
109
125
  ```css
126
+ /* Import default styles first */
127
+ @import 'sveltacular/styles.css';
128
+
129
+ /* Then override variables as needed */
110
130
  :root {
111
131
  --button-primary-bg: #1e88e5;
112
132
  --form-input-border: #e0e0e0;
@@ -114,6 +134,27 @@ Example:
114
134
  }
115
135
  ```
116
136
 
137
+ Or in a Svelte component:
138
+
139
+ ```svelte
140
+ <script>
141
+ import 'sveltacular/styles.css';
142
+ import { Button } from 'sveltacular';
143
+ </script>
144
+
145
+ <style>
146
+ :global(:root) {
147
+ --button-primary-bg: #1e88e5;
148
+ --form-input-border: #e0e0e0;
149
+ --base-color-bg: #ffffff;
150
+ }
151
+ </style>
152
+ ```
153
+
154
+ ### Providing Your Own Theme
155
+
156
+ If you prefer not to use the default stylesheet, you can define all CSS variables yourself. See [THEMING.md](./THEMING.md) for the complete list of required variables.
157
+
117
158
  ## Form Validation
118
159
 
119
160
  Sveltacular includes a validation system:
@@ -136,6 +177,7 @@ if (!result.isValid) {
136
177
  ## Accessibility
137
178
 
138
179
  Sveltacular components include:
180
+
139
181
  - ARIA attributes for screen readers
140
182
  - Keyboard navigation support
141
183
  - Focus management utilities
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
+ import { untrack } from 'svelte';
2
3
  import type { DropdownOption, FormFieldSizeOptions } from '../../types/form.js';
3
4
  import FormField from '../form-field.svelte';
4
- import FormLabel from '../form-label.svelte';
5
5
  import CheckBox from './check-box.svelte';
6
6
  import { uniqueId } from '../../helpers/unique-id.js';
7
7
 
@@ -31,13 +31,20 @@
31
31
  // Sync itemsWithState when items or group changes (one-way: items/group -> itemsWithState)
32
32
  // Reassign the entire array to avoid reading itemsWithState in the effect
33
33
  $effect(() => {
34
- // Rebuild itemsWithState from items, using group to determine checked state
35
- // Reassign instead of mutate to avoid circular dependency
36
- const newItems = items.map((item) => ({
37
- ...item,
38
- isChecked: group.includes(item.value ?? '')
39
- }));
40
- itemsWithState = newItems;
34
+ // Track items and group as dependencies
35
+ const currentItems = items;
36
+ const currentGroup = group;
37
+
38
+ // Use untrack to prevent writing to itemsWithState from triggering this effect again
39
+ untrack(() => {
40
+ // Rebuild itemsWithState from items, using group to determine checked state
41
+ // Reassign instead of mutate to avoid circular dependency
42
+ const newItems = currentItems.map((item) => ({
43
+ ...item,
44
+ isChecked: currentGroup.includes(item.value ?? '')
45
+ }));
46
+ itemsWithState = newItems;
47
+ });
41
48
  });
42
49
 
43
50
  const handleCheckboxChange = (data: { isChecked: boolean; value: string }) => {
@@ -53,10 +60,7 @@
53
60
  };
54
61
  </script>
55
62
 
56
- <FormField {size}>
57
- {#if label}
58
- <FormLabel {id} {required} {label} />
59
- {/if}
63
+ <FormField {size} {label} {id} {required} {disabled}>
60
64
  <div>
61
65
  {#each itemsWithState as item}
62
66
  <CheckBox
@@ -19,9 +19,9 @@
19
19
  * />
20
20
  * ```
21
21
  */
22
+ import { untrack } from 'svelte';
22
23
  import type { DropdownOption, FormFieldSizeOptions } from '../../types/form.js';
23
24
  import FormField from '../form-field.svelte';
24
- import FormLabel from '../form-label.svelte';
25
25
  import { uniqueId } from '../../helpers/unique-id.js';
26
26
  import Menu from '../../generic/menu/menu.svelte';
27
27
  import AngleUpIcon from '../../icons/angle-up-icon.svelte';
@@ -40,7 +40,7 @@
40
40
  multiSelect = false,
41
41
  placeholder = '',
42
42
  label = undefined,
43
- helpText = undefined,
43
+ helperText = undefined,
44
44
  errorText = undefined,
45
45
  successText = undefined,
46
46
  maxSelections = undefined as number | undefined,
@@ -68,8 +68,8 @@
68
68
  placeholder?: string;
69
69
  /** Label text for the combobox */
70
70
  label?: string;
71
- /** Help text displayed below the input */
72
- helpText?: string;
71
+ /** Helper text displayed below the input */
72
+ helperText?: string;
73
73
  /** Error message to display */
74
74
  errorText?: string;
75
75
  /** Success message to display */
@@ -368,9 +368,28 @@
368
368
  }
369
369
  };
370
370
 
371
- // Initial filter
371
+ // Filter items when items or searchQuery changes
372
+ // Use untrack to prevent reading filteredItems/highlightIndex from triggering the effect
372
373
  $effect(() => {
373
- applyFilter();
374
+ // Track only the dependencies we care about
375
+ const query = searchQuery.trim().toLowerCase();
376
+ const currentItems = items;
377
+
378
+ // Use untrack to write to state without triggering this effect again
379
+ untrack(() => {
380
+ if (query && searchable) {
381
+ filteredItems = currentItems.filter((item) =>
382
+ item.name.toLowerCase().includes(query)
383
+ );
384
+ } else {
385
+ filteredItems = [...currentItems];
386
+ }
387
+
388
+ // Reset highlight if out of bounds
389
+ if (highlightIndex >= filteredItems.length) {
390
+ highlightIndex = Math.max(0, filteredItems.length - 1);
391
+ }
392
+ });
374
393
  });
375
394
 
376
395
  // Derived state for open/closed
@@ -379,16 +398,15 @@
379
398
  // Clear search query when menu closes in single-select mode
380
399
  $effect(() => {
381
400
  if (!isMenuOpen && !multiSelect) {
382
- searchQuery = '';
401
+ // Use untrack to prevent triggering other effects
402
+ untrack(() => {
403
+ searchQuery = '';
404
+ });
383
405
  }
384
406
  });
385
407
  </script>
386
408
 
387
- <FormField {size}>
388
- {#if label}
389
- <FormLabel {id} {required} {disabled} {label} />
390
- {/if}
391
-
409
+ <FormField {size} {label} {id} {required} {disabled} {helperText} {errorText} {successText}>
392
410
  <div class="combobox-wrapper {open ? 'open' : 'closed'} {disabled ? 'disabled' : 'enabled'}">
393
411
  <!-- Multi-select chip display -->
394
412
  {#if multiSelect && selectedItems.length > 0}
@@ -423,7 +441,7 @@
423
441
  aria-activedescendant={activeDescendant}
424
442
  aria-haspopup="listbox"
425
443
  aria-label={label}
426
- aria-describedby={helpText || errorText || successText ? `${id}-description` : undefined}
444
+ aria-describedby={helperText || errorText || successText ? `${id}-helper ${id}-error ${id}-success` : undefined}
427
445
  aria-invalid={errorText ? 'true' : undefined}
428
446
  onfocus={() => {
429
447
  if (!disabled) {
@@ -489,19 +507,6 @@
489
507
  </div>
490
508
  </div>
491
509
 
492
- <!-- Help/Error/Success text -->
493
- {#if helpText || errorText || successText}
494
- <div id="{id}-description" class="description">
495
- {#if errorText}
496
- <span class="error-text" role="alert">{errorText}</span>
497
- {:else if successText}
498
- <span class="success-text" role="status">{successText}</span>
499
- {:else if helpText}
500
- <span class="help-text">{helpText}</span>
501
- {/if}
502
- </div>
503
- {/if}
504
-
505
510
  <!-- Max selections indicator -->
506
511
  {#if multiSelect && maxSelections !== undefined}
507
512
  <div class="max-selections-indicator">
@@ -629,23 +634,6 @@ button:focus-visible {
629
634
  margin-top: 0.25rem;
630
635
  }
631
636
 
632
- .description {
633
- margin-top: 0.5rem;
634
- font-size: var(--font-sm);
635
- line-height: 1.4;
636
- }
637
- .description .help-text {
638
- color: var(--gray-600);
639
- }
640
- .description .error-text {
641
- color: var(--danger);
642
- font-weight: 500;
643
- }
644
- .description .success-text {
645
- color: var(--success);
646
- font-weight: 500;
647
- }
648
-
649
637
  .max-selections-indicator {
650
638
  margin-top: 0.5rem;
651
639
  font-size: var(--font-sm);
@@ -1,23 +1,3 @@
1
- /**
2
- * Combobox Component
3
- *
4
- * A searchable select component with typeahead, multi-select support, and virtual scrolling.
5
- * Follows the ARIA 1.2 Combobox pattern for full accessibility.
6
- *
7
- * @component
8
- * @example
9
- * ```svelte
10
- * <ComboBox
11
- * bind:value
12
- * items={[
13
- * { value: '1', name: 'Option 1' },
14
- * { value: '2', name: 'Option 2' }
15
- * ]}
16
- * label="Select an option"
17
- * searchable
18
- * />
19
- * ```
20
- */
21
1
  import type { DropdownOption, FormFieldSizeOptions } from '../../types/form.js';
22
2
  type $$ComponentProps = {
23
3
  /** Current selected value(s) - string for single-select, string[] for multi-select */
@@ -38,8 +18,8 @@ type $$ComponentProps = {
38
18
  placeholder?: string;
39
19
  /** Label text for the combobox */
40
20
  label?: string;
41
- /** Help text displayed below the input */
42
- helpText?: string;
21
+ /** Helper text displayed below the input */
22
+ helperText?: string;
43
23
  /** Error message to display */
44
24
  errorText?: string;
45
25
  /** Success message to display */
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
+ import { untrack } from 'svelte';
2
3
  import { addUnits, currentDateTime, isDateString, isDateOrDateTimeString, isDateTimeString } from '../../helpers/date.js';
3
4
  import { uniqueId } from '../../helpers/unique-id.js';
4
5
  import FormField from '../form-field.svelte';
5
- import FormLabel from '../form-label.svelte';
6
6
  import type { DateUnit, FormFieldSizeOptions } from '../../index.js';
7
7
  import Button from '../button/button.svelte';
8
8
 
@@ -68,17 +68,17 @@
68
68
 
69
69
  $effect(() => {
70
70
  if (!value) {
71
- if (nullable) enabled = false;
72
- else value = getDefaultValue();
71
+ // Use untrack to prevent writes to enabled/value from triggering this effect again
72
+ untrack(() => {
73
+ if (nullable) enabled = false;
74
+ else value = getDefaultValue();
75
+ });
73
76
  }
74
77
  });
75
78
  let disabled = $derived(!enabled);
76
79
  </script>
77
80
 
78
- <FormField {size}>
79
- {#if label}
80
- <FormLabel {id} {required} {label} />
81
- {/if}
81
+ <FormField {size} {label} {id} {required} {disabled}>
82
82
  <div class:nullable class:disabled>
83
83
  <span class="input">
84
84
  <input {...{ type }} {id} {placeholder} {disabled} bind:value {required} oninput={onInput} />
@@ -2,7 +2,6 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import { uniqueId } from '../../helpers/unique-id.js';
4
4
  import FormField from '../form-field.svelte';
5
- import FormLabel from '../form-label.svelte';
6
5
  import type { FormFieldSizeOptions } from '../../types/form.js';
7
6
 
8
7
  const id = uniqueId();
@@ -32,10 +31,7 @@
32
31
  } = $props();
33
32
  </script>
34
33
 
35
- <FormField {size}>
36
- {#if label}
37
- <FormLabel {id} {required} {label} />
38
- {/if}
34
+ <FormField {size} {label} {id} {required} {disabled}>
39
35
  <div>
40
36
  <input
41
37
  {id}
@@ -2,24 +2,82 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { ComponentSize } from '../types/size.js';
4
4
  import { getMaxWidth, getDisplayType } from '../types/size.js';
5
+ import FormLabel from './form-label.svelte';
5
6
 
6
7
  let {
7
8
  size = 'full',
9
+ label = undefined,
10
+ id = undefined,
11
+ required = false,
12
+ disabled = false,
13
+ helperText = undefined,
14
+ errorText = undefined,
15
+ successText = undefined,
8
16
  children
9
17
  }: {
10
18
  size?: ComponentSize;
19
+ label?: string | undefined;
20
+ id?: string | undefined;
21
+ required?: boolean;
22
+ disabled?: boolean;
23
+ helperText?: string | undefined;
24
+ errorText?: string | undefined;
25
+ successText?: string | undefined;
11
26
  children: Snippet;
12
27
  } = $props();
13
28
 
14
29
  let displayType = $derived(getDisplayType(size));
15
30
  let maxWidth = $derived(getMaxWidth(size));
31
+
32
+ let showHelperText = $derived(!!helperText && !errorText && !successText);
33
+ let showSuccessText = $derived(!!successText && !errorText);
34
+ let showErrorText = $derived(!!errorText);
16
35
  </script>
17
36
 
18
37
  <div style={`display: ${displayType}; width: 100%; min-width: 10rem; max-width: ${maxWidth}`}>
38
+ {#if label}
39
+ <FormLabel {id} {required} {disabled} {label} />
40
+ {/if}
19
41
  {@render children?.()}
42
+ {#if showHelperText}
43
+ <div class="helper-text" id="{id}-helper">{helperText}</div>
44
+ {/if}
45
+ {#if showSuccessText}
46
+ <div class="success-text" id="{id}-success" role="status" aria-live="polite">
47
+ {successText}
48
+ </div>
49
+ {/if}
50
+ {#if showErrorText}
51
+ <div class="error-text" id="{id}-error" role="alert" aria-live="assertive">
52
+ {errorText}
53
+ </div>
54
+ {/if}
20
55
  </div>
21
56
 
22
57
  <style>div {
23
58
  margin-bottom: var(--spacing-base);
24
59
  margin-right: var(--spacing-base);
60
+ }
61
+
62
+ .helper-text {
63
+ font-size: var(--font-sm);
64
+ line-height: 1.25rem;
65
+ padding: var(--spacing-xs);
66
+ color: var(--body-fg);
67
+ }
68
+
69
+ .success-text {
70
+ font-size: var(--font-sm);
71
+ line-height: 1.25rem;
72
+ padding: var(--spacing-xs);
73
+ color: var(--success, #28a745);
74
+ font-weight: 500;
75
+ }
76
+
77
+ .error-text {
78
+ font-size: var(--font-sm);
79
+ line-height: 1.25rem;
80
+ padding: var(--spacing-xs);
81
+ color: var(--danger, #dc3545);
82
+ font-weight: 500;
25
83
  }</style>
@@ -2,6 +2,13 @@ import type { Snippet } from 'svelte';
2
2
  import type { ComponentSize } from '../types/size.js';
3
3
  type $$ComponentProps = {
4
4
  size?: ComponentSize;
5
+ label?: string | undefined;
6
+ id?: string | undefined;
7
+ required?: boolean;
8
+ disabled?: boolean;
9
+ helperText?: string | undefined;
10
+ errorText?: string | undefined;
11
+ successText?: string | undefined;
5
12
  children: Snippet;
6
13
  };
7
14
  declare const FormField: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ children: Snippet;
6
+ }
7
+
8
+ let { children }: Props = $props();
9
+ </script>
10
+
11
+ <div class="form-row">
12
+ {@render children()}
13
+ </div>
14
+
15
+ <style>/* ============================================
16
+ BREAKPOINTS - Responsive Design
17
+ ============================================ */
18
+ .form-row {
19
+ display: flex;
20
+ flex-direction: row;
21
+ gap: var(--spacing-base);
22
+ align-items: flex-start;
23
+ }
24
+ @media (max-width: 479.98px) {
25
+ .form-row {
26
+ flex-direction: column;
27
+ align-items: stretch;
28
+ }
29
+ }</style>
@@ -0,0 +1,7 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ children: Snippet;
4
+ }
5
+ declare const FormRow: import("svelte").Component<Props, {}, "">;
6
+ type FormRow = ReturnType<typeof FormRow>;
7
+ export default FormRow;
@@ -33,3 +33,9 @@
33
33
  {@render children()}
34
34
  </form>
35
35
  </Section>
36
+
37
+ <style>form {
38
+ display: flex;
39
+ flex-direction: column;
40
+ gap: var(--spacing-base);
41
+ }</style>
@@ -2,7 +2,6 @@
2
2
  import LinkIcon from '../../icons/link-icon.svelte';
3
3
  import type { FormFieldSizeOptions } from '../../index.js';
4
4
  import FormField from '../form-field.svelte';
5
- import FormLabel from '../form-label.svelte';
6
5
 
7
6
  let {
8
7
  size = 'md' as FormFieldSizeOptions,
@@ -17,10 +16,7 @@
17
16
  } = $props();
18
17
  </script>
19
18
 
20
- <FormField {size}>
21
- {#if label}
22
- <FormLabel {label} />
23
- {/if}
19
+ <FormField {size} {label}>
24
20
  <div class="input">
25
21
  {#if href}
26
22
  <span class="icon">
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { DropdownOption, FormFieldSizeOptions, MenuOption } from '../../types/form.js';
3
3
  import FormField from '../form-field.svelte';
4
- import FormLabel from '../form-label.svelte';
5
4
  import { uniqueId } from '../../helpers/unique-id.js';
6
5
  import Menu from '../../generic/menu/menu.svelte';
7
6
  import AngleUpIcon from '../../icons/angle-up-icon.svelte';
@@ -151,10 +150,7 @@
151
150
  let open = $derived(isMenuOpen && !disabled);
152
151
  </script>
153
152
 
154
- <FormField {size}>
155
- {#if label}
156
- <FormLabel {id} {required} {disabled} {label} />
157
- {/if}
153
+ <FormField {size} {label} {id} {required} {disabled}>
158
154
  <div class="{open ? 'open' : 'closed'} {disabled ? 'disabled' : 'enabled'}">
159
155
  <input
160
156
  type="text"
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { uniqueId, type FormFieldSizeOptions } from '../../index.js';
3
3
  import FormField from '../form-field.svelte';
4
- import FormLabel from '../form-label.svelte';
5
4
  import { untrack } from 'svelte';
6
5
 
7
6
  const id = uniqueId();
@@ -58,8 +57,11 @@
58
57
  ]);
59
58
  $effect(() => {
60
59
  if (value !== null && value >= 0) {
61
- dollars = getDollarsFromValue();
62
- cents = getCentsFromValue();
60
+ // Use untrack to prevent writes to dollars/cents from triggering this effect again
61
+ untrack(() => {
62
+ dollars = getDollarsFromValue();
63
+ cents = getCentsFromValue();
64
+ });
63
65
  }
64
66
  });
65
67
 
@@ -207,10 +209,7 @@
207
209
 
208
210
  </script>
209
211
 
210
- <FormField {size}>
211
- {#if label}
212
- <FormLabel {id} {label} />
213
- {/if}
212
+ <FormField {size} {label} {id}>
214
213
  <div class="input {currency}" class:allowCents {id}>
215
214
  {#if prefix}
216
215
  <span class="prefix">{prefix}</span>
@@ -2,7 +2,6 @@
2
2
  import { roundToDecimals } from '../../helpers/round-to-decimals.js';
3
3
  import { uniqueId } from '../../helpers/unique-id.js';
4
4
  import FormField from '../form-field.svelte';
5
- import FormLabel from '../form-label.svelte';
6
5
  import type { FormFieldSizeOptions } from '../../types/form.js';
7
6
  const id = uniqueId();
8
7
 
@@ -64,10 +63,7 @@
64
63
  };
65
64
  </script>
66
65
 
67
- <FormField {size}>
68
- {#if label}
69
- <FormLabel {id} {label} />
70
- {/if}
66
+ <FormField {size} {label} {id}>
71
67
  <div class="input {type}">
72
68
  {#if prefix}
73
69
  <span class="prefix">{prefix}</span>
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
+ import { untrack } from 'svelte';
2
3
  import { uniqueId, type FormFieldSizeOptions } from '../../index.js';
3
4
  import FormField from '../form-field.svelte';
4
- import FormLabel from '../form-label.svelte';
5
5
 
6
6
  let {
7
7
  value = $bindable('' as string | null),
@@ -135,16 +135,18 @@
135
135
  setValue(value ?? '');
136
136
 
137
137
  $effect(() => {
138
- if (areaCode || localExt || lastFour) {
139
- publishChange();
138
+ // Only trigger when the phone number parts change
139
+ const hasValue = areaCode || localExt || lastFour;
140
+ if (hasValue) {
141
+ // Use untrack to prevent value changes from triggering this effect again
142
+ untrack(() => {
143
+ publishChange();
144
+ });
140
145
  }
141
146
  });
142
147
  </script>
143
148
 
144
- <FormField {size}>
145
- {#if label}
146
- <FormLabel id="{id}-areaCode" {label} />
147
- {/if}
149
+ <FormField {size} {label} id="{id}-areaCode">
148
150
  <div class="input">
149
151
  <span class="areaCode segment">
150
152
  <span>(</span>