@stubber/form-fields 2.2.6 → 3.0.0

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 (126) hide show
  1. package/README.md +63 -25
  2. package/dist/form-fields/Autocomplete.svelte +261 -0
  3. package/dist/form-fields/Autocomplete.svelte.d.ts +43 -0
  4. package/dist/form-fields/CollapsibleSection.svelte +127 -0
  5. package/dist/form-fields/CollapsibleSection.svelte.d.ts +26 -0
  6. package/dist/form-fields/FieldLabel.svelte +18 -3
  7. package/dist/form-fields/FieldLabel.svelte.d.ts +1 -0
  8. package/dist/form-fields/FieldMessage.svelte +20 -9
  9. package/dist/form-fields/Form.svelte +19 -2
  10. package/dist/form-fields/Form.svelte.d.ts +2 -0
  11. package/dist/form-fields/UploadStatus.svelte +76 -0
  12. package/dist/form-fields/UploadStatus.svelte.d.ts +18 -0
  13. package/dist/form-fields/fields/_field-builder-reorder.svelte +35 -0
  14. package/dist/form-fields/fields/_field-builder-reorder.svelte.d.ts +22 -0
  15. package/dist/form-fields/{sub → fields}/array-builder-field.svelte +104 -38
  16. package/dist/form-fields/fields/checkbox-autocomplete.svelte +153 -0
  17. package/dist/form-fields/fields/checkbox-field.svelte +69 -0
  18. package/dist/form-fields/{sub → fields}/contact-selector-field.svelte +9 -6
  19. package/dist/form-fields/{sub → fields}/currency-field.svelte +108 -70
  20. package/dist/form-fields/fields/data-indication-field.svelte +39 -0
  21. package/dist/form-fields/fields/date-field.svelte +31 -0
  22. package/dist/form-fields/fields/date-time-field.svelte +31 -0
  23. package/dist/form-fields/{sub → fields}/email-field.svelte +25 -11
  24. package/dist/form-fields/fields/field-builder-field.svelte +568 -0
  25. package/dist/form-fields/fields/file-field.svelte +284 -0
  26. package/dist/form-fields/{sub → fields}/file-field.svelte.d.ts +2 -0
  27. package/dist/form-fields/fields/grid-field.svelte +152 -0
  28. package/dist/form-fields/{sub → fields}/heading-field.svelte +7 -4
  29. package/dist/form-fields/{sub → fields}/html-field.svelte +6 -4
  30. package/dist/form-fields/{sub → fields}/map-field.svelte +121 -78
  31. package/dist/form-fields/{sub → fields}/multi-checkbox-field.svelte +35 -20
  32. package/dist/form-fields/fields/multistep-field.svelte +135 -0
  33. package/dist/form-fields/fields/note-field.svelte +35 -0
  34. package/dist/form-fields/{sub → fields}/number-field.svelte +29 -14
  35. package/dist/form-fields/fields/object-builder-field.svelte +176 -0
  36. package/dist/form-fields/fields/qr-code-scanner-field.svelte +203 -0
  37. package/dist/form-fields/fields/radio-field.svelte +82 -0
  38. package/dist/form-fields/{sub → fields}/radio-field.svelte.d.ts +1 -1
  39. package/dist/form-fields/fields/screenrecorder-field.svelte +255 -0
  40. package/dist/form-fields/{sub → fields}/screenshot-field.svelte +101 -40
  41. package/dist/form-fields/{sub → fields}/scroll-and-read-display-field.svelte +43 -27
  42. package/dist/form-fields/fields/section-field.svelte +73 -0
  43. package/dist/form-fields/fields/select-field.svelte +104 -0
  44. package/dist/form-fields/{sub → fields}/signature-field.svelte +78 -22
  45. package/dist/form-fields/fields/slider-field.svelte +53 -0
  46. package/dist/form-fields/{sub → fields}/smart-text-field.svelte +62 -82
  47. package/dist/form-fields/fields/telephone-field.svelte +108 -0
  48. package/dist/form-fields/{sub → fields}/telephone-field.svelte.d.ts +1 -1
  49. package/dist/form-fields/{sub → fields}/text-field.svelte +28 -11
  50. package/dist/form-fields/{sub → fields}/voicenote-field.svelte +117 -46
  51. package/dist/form-fields/file_creation_options.d.ts +1 -1
  52. package/dist/form-fields/form-field.svelte +8 -1
  53. package/dist/form-fields/icons/ChevronDownIcon.svelte +21 -0
  54. package/dist/form-fields/icons/ChevronDownIcon.svelte.d.ts +20 -0
  55. package/dist/form-fields/icons/QrCodeScannerIcon.svelte +25 -0
  56. package/dist/form-fields/icons/QrCodeScannerIcon.svelte.d.ts +19 -0
  57. package/dist/form-fields/icons/ScreenRecordingIcon.svelte +21 -0
  58. package/dist/form-fields/icons/ScreenRecordingIcon.svelte.d.ts +19 -0
  59. package/dist/form-fields/icons/ScreenshotIcon.svelte +22 -0
  60. package/dist/form-fields/icons/ScreenshotIcon.svelte.d.ts +19 -0
  61. package/dist/form-fields/index.d.ts +5 -1
  62. package/dist/form-fields/index.js +3 -1
  63. package/dist/form-fields/interfaces.d.ts +63 -63
  64. package/dist/form-fields/interfaces.js +42 -39
  65. package/dist/form-fields/intl-tel-input.css +1 -0
  66. package/dist/form-fields/styles.d.ts +17 -0
  67. package/dist/form-fields/styles.js +4 -0
  68. package/package.json +2 -4
  69. package/dist/form-fields/sub/checkbox-autocomplete.svelte +0 -56
  70. package/dist/form-fields/sub/checkbox-field.svelte +0 -42
  71. package/dist/form-fields/sub/data-indication-field.svelte +0 -19
  72. package/dist/form-fields/sub/date-field.svelte +0 -31
  73. package/dist/form-fields/sub/date-time-field.svelte +0 -31
  74. package/dist/form-fields/sub/field-builder-field.svelte +0 -657
  75. package/dist/form-fields/sub/file-field.svelte +0 -159
  76. package/dist/form-fields/sub/grid-field.svelte +0 -69
  77. package/dist/form-fields/sub/multistep-field.svelte +0 -70
  78. package/dist/form-fields/sub/note-field.svelte +0 -18
  79. package/dist/form-fields/sub/object-builder-field.svelte +0 -134
  80. package/dist/form-fields/sub/qr-code-scanner-field.svelte +0 -84
  81. package/dist/form-fields/sub/radio-field.svelte +0 -69
  82. package/dist/form-fields/sub/screenrecorder-field.svelte +0 -178
  83. package/dist/form-fields/sub/section-field.svelte +0 -50
  84. package/dist/form-fields/sub/select-field.svelte +0 -150
  85. package/dist/form-fields/sub/slider-field.svelte +0 -28
  86. package/dist/form-fields/sub/telephone-field.svelte +0 -64
  87. /package/dist/form-fields/{sub → fields}/array-builder-field.svelte.d.ts +0 -0
  88. /package/dist/form-fields/{sub → fields}/checkbox-autocomplete.svelte.d.ts +0 -0
  89. /package/dist/form-fields/{sub → fields}/checkbox-field.svelte.d.ts +0 -0
  90. /package/dist/form-fields/{sub → fields}/code-field.svelte +0 -0
  91. /package/dist/form-fields/{sub → fields}/code-field.svelte.d.ts +0 -0
  92. /package/dist/form-fields/{sub → fields}/contact-selector-field.svelte.d.ts +0 -0
  93. /package/dist/form-fields/{sub → fields}/currency-field.svelte.d.ts +0 -0
  94. /package/dist/form-fields/{sub → fields}/data-indication-field.svelte.d.ts +0 -0
  95. /package/dist/form-fields/{sub → fields}/date-field.svelte.d.ts +0 -0
  96. /package/dist/form-fields/{sub → fields}/date-time-field.svelte.d.ts +0 -0
  97. /package/dist/form-fields/{sub → fields}/email-field.svelte.d.ts +0 -0
  98. /package/dist/form-fields/{sub → fields}/field-builder-field.svelte.d.ts +0 -0
  99. /package/dist/form-fields/{sub → fields}/grid-field.svelte.d.ts +0 -0
  100. /package/dist/form-fields/{sub → fields}/heading-field.svelte.d.ts +0 -0
  101. /package/dist/form-fields/{sub → fields}/hidden-field.svelte +0 -0
  102. /package/dist/form-fields/{sub → fields}/hidden-field.svelte.d.ts +0 -0
  103. /package/dist/form-fields/{sub → fields}/hidden-location-field.svelte +0 -0
  104. /package/dist/form-fields/{sub → fields}/hidden-location-field.svelte.d.ts +0 -0
  105. /package/dist/form-fields/{sub → fields}/html-field.svelte.d.ts +0 -0
  106. /package/dist/form-fields/{sub → fields}/jsoneditor-field.svelte +0 -0
  107. /package/dist/form-fields/{sub → fields}/jsoneditor-field.svelte.d.ts +0 -0
  108. /package/dist/form-fields/{sub → fields}/map-field.svelte.d.ts +0 -0
  109. /package/dist/form-fields/{sub → fields}/multi-checkbox-field.svelte.d.ts +0 -0
  110. /package/dist/form-fields/{sub → fields}/multistep-field.svelte.d.ts +0 -0
  111. /package/dist/form-fields/{sub → fields}/note-field.svelte.d.ts +0 -0
  112. /package/dist/form-fields/{sub → fields}/number-field.svelte.d.ts +0 -0
  113. /package/dist/form-fields/{sub → fields}/object-builder-field.svelte.d.ts +0 -0
  114. /package/dist/form-fields/{sub → fields}/qr-code-scanner-field.svelte.d.ts +0 -0
  115. /package/dist/form-fields/{sub → fields}/screenrecorder-field.svelte.d.ts +0 -0
  116. /package/dist/form-fields/{sub → fields}/screenshot-field.svelte.d.ts +0 -0
  117. /package/dist/form-fields/{sub → fields}/scroll-and-read-display-field.svelte.d.ts +0 -0
  118. /package/dist/form-fields/{sub → fields}/section-field.svelte.d.ts +0 -0
  119. /package/dist/form-fields/{sub → fields}/select-field.svelte.d.ts +0 -0
  120. /package/dist/form-fields/{sub → fields}/selectresource-field.svelte +0 -0
  121. /package/dist/form-fields/{sub → fields}/selectresource-field.svelte.d.ts +0 -0
  122. /package/dist/form-fields/{sub → fields}/signature-field.svelte.d.ts +0 -0
  123. /package/dist/form-fields/{sub → fields}/slider-field.svelte.d.ts +0 -0
  124. /package/dist/form-fields/{sub → fields}/smart-text-field.svelte.d.ts +0 -0
  125. /package/dist/form-fields/{sub → fields}/text-field.svelte.d.ts +0 -0
  126. /package/dist/form-fields/{sub → fields}/voicenote-field.svelte.d.ts +0 -0
package/README.md CHANGED
@@ -1,38 +1,76 @@
1
1
  # stubber-form-fields-pkg
2
2
 
3
- ## How to use
3
+ This package provides a set of Svelte components for building accessible, composable form fields from json definitions.
4
4
 
5
- ```
6
- git clone git@github.com:stubber/stubber-public-form-fields-pkg.git form-fields-pkg
7
- cd form-fields-pkg
8
- cp .env_staging .env
9
- pnpm install
10
- pnpm run dev
11
- ```
5
+ ## Consumer styling contract
12
6
 
13
- # Publishing
7
+ The package owns the structural CSS that fields need to function, including layout, spacing,
8
+ sizing, alignment, overlays, and disclosure movement. Consumers own the visual theme and provide
9
+ classes for semantic roles through the `Form` component's `styles` prop.
14
10
 
15
- We use [semantic-release](https://github.com/semantic-release/semantic-release) to automate the versioning and package publishing process. To publish a new version of the package, follow these steps:
11
+ `FormFieldStyles` is a flat set of class hooks. A class string is added directly to every element
12
+ with that role, alongside the package's internal structural class. All properties are optional;
13
+ the package does not supply or merge a default visual theme, and an omitted role receives no
14
+ consumer class. Pass the styles once at the outer form boundary; nested forms inherit them.
16
15
 
17
- Simply make a commit with a message that follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification, and push it to the master branch. The commit message should include a type (feat, fix, chore, etc.) and an optional scope.
16
+ ```svelte
17
+ <script lang="ts">
18
+ import { Form, type FormFieldStyles } from "@stubber/form-fields";
18
19
 
19
- For example:
20
+ const styles: FormFieldStyles = {
21
+ label: "text-sm font-medium",
22
+ message: "text-xs text-slate-500 data-[variant=error]:text-red-600",
23
+ input: "w-full rounded border px-3 py-2",
24
+ choice: "size-4 accent-blue-600",
25
+ range: "w-full accent-blue-600",
26
+ group: "rounded border p-3 data-[invalid]:border-red-600",
27
+ dropzone: "rounded border-2 border-dashed p-4 data-[dragging]:border-blue-600",
28
+ uploadItem: "rounded border bg-slate-50 p-2",
29
+ popover: "rounded border bg-white p-1 shadow-md",
30
+ option:
31
+ "px-2 py-1 data-[highlighted]:bg-blue-600 data-[highlighted]:text-white aria-[selected=true]:font-semibold",
32
+ button:
33
+ "rounded px-2 py-1 data-[variant=primary]:bg-blue-600 data-[variant=primary]:text-white data-[variant=destructive]:bg-red-600 data-[variant=destructive]:text-white data-[size=icon]:size-8 data-[size=icon]:p-0",
34
+ };
35
+ </script>
20
36
 
21
- ```bash
22
- # will create a new major release.
23
- breaking: change button color to red
24
- feat!: add new button component
37
+ <Form {styles} {form} {initial_form} />
38
+ ```
25
39
 
26
- # will create a new minor release.
27
- feat: add new button component
40
+ The supported roles are:
28
41
 
29
- # will create a new patch release.
30
- fix: fix button color
31
- ```
42
+ | Role | Applied to |
43
+ | ------------ | ------------------------------------------------------------------------------------------------------ |
44
+ | `label` | Field labels and section/group legends |
45
+ | `input` | Text-like controls, including text areas, number/date inputs, autocomplete inputs, and editor surfaces |
46
+ | `choice` | Checkboxes, radio buttons, and other binary or multiple-choice controls |
47
+ | `range` | Range/slider inputs |
48
+ | `message` | Help and validation messages |
49
+ | `group` | Section and grouped-field containers |
50
+ | `dropzone` | File upload dropzones |
51
+ | `uploadItem` | Uploaded, recorded, or captured file items |
52
+ | `popover` | Option lists, popovers, and dialogs |
53
+ | `option` | Individual options in a choice list |
54
+ | `button` | Form actions, including add, remove, navigation, capture, and recording controls |
55
+
56
+ ### State selectors
32
57
 
33
- See default ruleset [here](https://github.com/semantic-release/commit-analyzer/blob/master/lib/default-release-rules.js)
58
+ Use native selectors where the state has an HTML or ARIA equivalent: `:disabled`,
59
+ `[aria-invalid="true"]`, and `[aria-selected="true"]`. The package exposes the remaining visual
60
+ states as attributes:
34
61
 
35
- # Quick Testing
62
+ | Role | Attribute | Values/presence |
63
+ | ---------- | ------------------ | ------------------------------------------ |
64
+ | `message` | `data-variant` | `help`, `error` |
65
+ | `group` | `data-invalid` | Present when invalid |
66
+ | `dropzone` | `data-dragging` | Present while dragging over the target |
67
+ | `dropzone` | `data-disabled` | Present when the upload limit is reached |
68
+ | `dropzone` | `data-invalid` | Present when invalid |
69
+ | `option` | `data-highlighted` | Present on the keyboard-highlighted option |
70
+ | `button` | `data-variant` | `primary`, `destructive` |
71
+ | `button` | `data-size` | `icon` |
36
72
 
37
- A preview of all fields and some edge cases are available to test on the sveltekit app.
38
- On staging, it's hosted at https://form-fields-pkg.staging.stubber.zone
73
+ Button variant and size are independent, so a button may be both
74
+ `data-variant="destructive"` and `data-size="icon"`. Treat the role names and state attributes above
75
+ as the public styling API; internal package class names and DOM structure are implementation
76
+ details and should not be targeted by consumer styles.
@@ -0,0 +1,261 @@
1
+ <script context="module"></script>
2
+
3
+ <script>import { debounce, isEqual } from "lodash-es";
4
+ import { createEventDispatcher, getContext, onDestroy, tick } from "svelte";
5
+ import ChevronDownIcon from "./icons/ChevronDownIcon.svelte";
6
+ import { STYLES_CONTEXT, classNames } from "./styles";
7
+ export let id;
8
+ export let options = [];
9
+ export let value = void 0;
10
+ export let placeholder = "Select...";
11
+ export let emptyMessage = "No results found.";
12
+ export let loadingMessage = "Loading\u2026";
13
+ export let ariaLabel = void 0;
14
+ export let ariaDescribedby = void 0;
15
+ export let invalid = false;
16
+ export let inputClass = void 0;
17
+ export let variant = "default";
18
+ export let loadOptions = void 0;
19
+ export let selected = (option, value2) => isEqual(option.value, value2);
20
+ const styles = getContext(STYLES_CONTEXT);
21
+ const dispatch = createEventDispatcher();
22
+ let open = false;
23
+ let loading = false;
24
+ let query = "";
25
+ let highlightedIndex = 0;
26
+ let inputElement;
27
+ let listElement;
28
+ let availableOptions = options;
29
+ let requestId = 0;
30
+ $: if (!loadOptions) availableOptions = options;
31
+ $: selectedLabel = availableOptions.find((option) => selected(option, value))?.label ?? placeholder;
32
+ $: if (!open) query = selectedLabel;
33
+ $: visibleOptions = loadOptions ? availableOptions : availableOptions.filter(
34
+ (option) => (option.searchText ?? option.label).toLowerCase().includes(query.trim().toLowerCase())
35
+ );
36
+ $: if (highlightedIndex >= visibleOptions.length) {
37
+ highlightedIndex = Math.max(visibleOptions.length - 1, 0);
38
+ }
39
+ $: if (open && listElement && visibleOptions[highlightedIndex]) {
40
+ const index = highlightedIndex;
41
+ void tick().then(() => {
42
+ if (open && index === highlightedIndex) {
43
+ listElement.children[index]?.scrollIntoView({
44
+ block: "nearest",
45
+ inline: "nearest"
46
+ });
47
+ }
48
+ });
49
+ }
50
+ const search = async (searchTerm) => {
51
+ if (!loadOptions) return;
52
+ const currentRequest = ++requestId;
53
+ loading = true;
54
+ try {
55
+ const results = await loadOptions(searchTerm);
56
+ if (currentRequest !== requestId) return;
57
+ availableOptions = results;
58
+ highlightedIndex = 0;
59
+ } finally {
60
+ if (currentRequest === requestId) loading = false;
61
+ }
62
+ };
63
+ const debouncedSearch = debounce(search, 300);
64
+ onDestroy(() => debouncedSearch.cancel());
65
+ function openList() {
66
+ if (open) return;
67
+ open = true;
68
+ query = "";
69
+ highlightedIndex = Math.max(
70
+ availableOptions.findIndex((option) => selected(option, value)),
71
+ 0
72
+ );
73
+ if (loadOptions) void search("");
74
+ }
75
+ function closeList() {
76
+ open = false;
77
+ query = selectedLabel;
78
+ }
79
+ function choose(option) {
80
+ dispatch("select", option);
81
+ inputElement.focus();
82
+ closeList();
83
+ }
84
+ function handleInput(event) {
85
+ query = event.currentTarget.value;
86
+ open = true;
87
+ highlightedIndex = 0;
88
+ if (loadOptions) debouncedSearch(query);
89
+ }
90
+ function handleKeydown(event) {
91
+ if (event.key === "ArrowDown") {
92
+ event.preventDefault();
93
+ openList();
94
+ highlightedIndex = Math.min(highlightedIndex + 1, visibleOptions.length - 1);
95
+ } else if (event.key === "ArrowUp") {
96
+ event.preventDefault();
97
+ openList();
98
+ highlightedIndex = Math.max(highlightedIndex - 1, 0);
99
+ } else if (event.key === "Enter" && open && visibleOptions[highlightedIndex]) {
100
+ event.preventDefault();
101
+ choose(visibleOptions[highlightedIndex]);
102
+ } else if (event.key === "Escape" && open) {
103
+ event.preventDefault();
104
+ closeList();
105
+ }
106
+ }
107
+ function handleFocusout(event) {
108
+ const nextTarget = event.relatedTarget;
109
+ const currentTarget = event.currentTarget;
110
+ if (!(nextTarget instanceof Node) || !currentTarget.contains(nextTarget)) closeList();
111
+ }
112
+ function optionKey(option, index) {
113
+ return option.key ?? `${option.label}-${index}`;
114
+ }
115
+ </script>
116
+
117
+ <div class="autocomplete" data-variant={variant} on:focusout={handleFocusout}>
118
+ <div class="autocomplete-input-wrapper" aria-invalid={invalid ? true : undefined}>
119
+ <input
120
+ bind:this={inputElement}
121
+ {id}
122
+ class={classNames("autocomplete-input", inputClass, variant === "default" && $styles.input)}
123
+ data-variant={variant === "embedded" ? "embedded" : undefined}
124
+ type="text"
125
+ role="combobox"
126
+ autocomplete="off"
127
+ value={query}
128
+ {placeholder}
129
+ aria-label={ariaLabel}
130
+ aria-autocomplete="list"
131
+ aria-expanded={open}
132
+ aria-controls="autocomplete-listbox-{id}"
133
+ aria-activedescendant={open && visibleOptions[highlightedIndex]
134
+ ? `autocomplete-option-${id}-${highlightedIndex}`
135
+ : undefined}
136
+ aria-invalid={invalid ? true : undefined}
137
+ aria-describedby={ariaDescribedby}
138
+ on:focus={openList}
139
+ on:click={openList}
140
+ on:input={handleInput}
141
+ on:keydown={handleKeydown}
142
+ />
143
+ <span class:open class="autocomplete-indicator" aria-hidden="true">
144
+ <ChevronDownIcon />
145
+ </span>
146
+ </div>
147
+
148
+ {#if open}
149
+ <ul
150
+ bind:this={listElement}
151
+ id="autocomplete-listbox-{id}"
152
+ class={classNames("autocomplete-options", $styles.popover)}
153
+ role="listbox"
154
+ >
155
+ {#if loading}
156
+ <li class="autocomplete-status" role="status">{loadingMessage}</li>
157
+ {:else if visibleOptions.length === 0}
158
+ <li class="autocomplete-status" role="status">{emptyMessage}</li>
159
+ {:else}
160
+ {#each visibleOptions as option, index (optionKey(option, index))}
161
+ <li role="presentation">
162
+ <button
163
+ id="autocomplete-option-{id}-{index}"
164
+ type="button"
165
+ role="option"
166
+ tabindex="-1"
167
+ aria-selected={selected(option, value)}
168
+ data-highlighted={index === highlightedIndex ? true : undefined}
169
+ class={classNames("autocomplete-option", $styles.option)}
170
+ on:mousedown={(event) => event.preventDefault()}
171
+ on:mouseenter={() => (highlightedIndex = index)}
172
+ on:click={() => choose(option)}
173
+ >
174
+ <slot name="option" {option}>{option.label}</slot>
175
+ </button>
176
+ </li>
177
+ {/each}
178
+ {/if}
179
+ </ul>
180
+ {/if}
181
+ </div>
182
+
183
+ <style>
184
+ .autocomplete {
185
+ position: relative;
186
+ box-sizing: border-box;
187
+ width: 100%;
188
+ }
189
+
190
+ .autocomplete-input-wrapper {
191
+ position: relative;
192
+ display: flex;
193
+ align-items: center;
194
+ }
195
+
196
+ .autocomplete-input {
197
+ box-sizing: border-box;
198
+ width: 100%;
199
+ padding-inline-end: 2rem;
200
+ }
201
+
202
+ .autocomplete-indicator {
203
+ position: absolute;
204
+ inset-inline-end: 0.5rem;
205
+ display: flex;
206
+ pointer-events: none;
207
+ }
208
+
209
+ .autocomplete-options {
210
+ position: absolute;
211
+ z-index: 20;
212
+ top: 100%;
213
+ box-sizing: border-box;
214
+ width: 100%;
215
+ max-height: 15rem;
216
+ overflow-y: auto;
217
+ }
218
+
219
+ :global(:where(.autocomplete-option)) {
220
+ -webkit-appearance: none;
221
+ -moz-appearance: none;
222
+ appearance: none;
223
+ box-sizing: border-box;
224
+ display: block;
225
+ width: 100%;
226
+ padding: 0;
227
+ border: 0;
228
+ background: transparent;
229
+ color: inherit;
230
+ font: inherit;
231
+ text-align: start;
232
+ cursor: pointer;
233
+ }
234
+
235
+ .autocomplete[data-variant="embedded"] {
236
+ width: 5.5rem;
237
+ }
238
+
239
+ .autocomplete[data-variant="embedded"] .autocomplete-input {
240
+ padding-inline-end: 0.5rem;
241
+ background: transparent;
242
+ color: inherit;
243
+ }
244
+
245
+ .autocomplete[data-variant="embedded"] .autocomplete-input:focus {
246
+ outline: none;
247
+ }
248
+
249
+ .autocomplete[data-variant="embedded"] .autocomplete-indicator {
250
+ inset-block: 0;
251
+ inset-inline-end: 0;
252
+ align-items: center;
253
+ }
254
+
255
+ .autocomplete[data-variant="embedded"] .autocomplete-options {
256
+ right: 0;
257
+ left: auto;
258
+ width: min(18rem, calc(100vw - 2rem));
259
+ max-height: 16rem;
260
+ }
261
+ </style>
@@ -0,0 +1,43 @@
1
+ import { SvelteComponent } from "svelte";
2
+ export interface AutocompleteOption<T = any> {
3
+ label: string;
4
+ value: T;
5
+ key?: string;
6
+ searchText?: string;
7
+ [key: string]: any;
8
+ }
9
+ declare const __propDef: {
10
+ props: {
11
+ id: string;
12
+ options?: AutocompleteOption[];
13
+ value?: any;
14
+ placeholder?: string;
15
+ emptyMessage?: string;
16
+ loadingMessage?: string;
17
+ ariaLabel?: string | undefined;
18
+ ariaDescribedby?: string | undefined;
19
+ invalid?: boolean;
20
+ inputClass?: string | undefined;
21
+ variant?: "default" | "embedded";
22
+ loadOptions?: ((searchTerm: string) => Promise<AutocompleteOption[]>) | undefined;
23
+ selected?: (option: AutocompleteOption, value: any) => boolean;
24
+ };
25
+ events: {
26
+ select: CustomEvent<AutocompleteOption<any>>;
27
+ } & {
28
+ [evt: string]: CustomEvent<any>;
29
+ };
30
+ slots: {
31
+ option: {
32
+ option: AutocompleteOption<any>;
33
+ };
34
+ };
35
+ exports?: {} | undefined;
36
+ bindings?: string | undefined;
37
+ };
38
+ export type AutocompleteProps = typeof __propDef.props;
39
+ export type AutocompleteEvents = typeof __propDef.events;
40
+ export type AutocompleteSlots = typeof __propDef.slots;
41
+ export default class Autocomplete extends SvelteComponent<AutocompleteProps, AutocompleteEvents, AutocompleteSlots> {
42
+ }
43
+ export {};
@@ -0,0 +1,127 @@
1
+ <script>import ChevronDownIcon from "./icons/ChevronDownIcon.svelte";
2
+ export let title;
3
+ export let open = false;
4
+ export let contentId = void 0;
5
+ export let labelledBy = void 0;
6
+ export let onToggle = void 0;
7
+ function toggle() {
8
+ open = !open;
9
+ onToggle?.(open);
10
+ }
11
+ </script>
12
+
13
+ <div class="collapsible-section">
14
+ <div class="collapsible-section-header">
15
+ <button
16
+ type="button"
17
+ class="collapsible-section-toggle"
18
+ aria-expanded={open}
19
+ aria-controls={contentId}
20
+ aria-label={open ? `Collapse ${title}` : `Expand ${title}`}
21
+ on:click={toggle}
22
+ >
23
+ <span class="collapsible-section-heading">
24
+ <slot name="heading">{title}</slot>
25
+ </span>
26
+ <span
27
+ class="collapsible-section-indicator"
28
+ class:collapsible-section-indicator-open={open}
29
+ aria-hidden="true"
30
+ >
31
+ <ChevronDownIcon />
32
+ </span>
33
+ </button>
34
+ <slot name="actions" />
35
+ </div>
36
+
37
+ {#if open}
38
+ <div id={contentId} class="collapsible-section-content" aria-labelledby={labelledBy}>
39
+ <slot />
40
+ </div>
41
+ {/if}
42
+ </div>
43
+
44
+ <style>
45
+ .collapsible-section,
46
+ .collapsible-section-header {
47
+ box-sizing: border-box;
48
+ width: 100%;
49
+ min-width: 0;
50
+ }
51
+
52
+ .collapsible-section-header {
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 0.25rem;
56
+ }
57
+
58
+ .collapsible-section-toggle {
59
+ -webkit-appearance: none;
60
+ -moz-appearance: none;
61
+ appearance: none;
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 0.75rem;
65
+ flex: 1;
66
+ box-sizing: border-box;
67
+ width: 100%;
68
+ min-width: 0;
69
+ padding: 0.5rem;
70
+ border: 0;
71
+ border-radius: 0.375rem;
72
+ background: transparent;
73
+ color: inherit;
74
+ font: inherit;
75
+ font-weight: 600;
76
+ text-align: start;
77
+ cursor: pointer;
78
+ }
79
+
80
+ .collapsible-section-toggle:hover {
81
+ background: rgba(127, 127, 127, 0.08);
82
+ }
83
+
84
+ .collapsible-section-toggle:focus {
85
+ outline: none;
86
+ }
87
+
88
+ .collapsible-section-toggle:focus-visible {
89
+ outline: 2px solid Highlight;
90
+ outline-offset: 2px;
91
+ }
92
+
93
+ .collapsible-section-heading {
94
+ flex: 1;
95
+ min-width: 0;
96
+ }
97
+
98
+ .collapsible-section-indicator {
99
+ display: inline-flex;
100
+ flex: none;
101
+ transform: rotate(-90deg);
102
+ transition: transform 150ms ease;
103
+ }
104
+
105
+ .collapsible-section-indicator-open {
106
+ transform: rotate(0deg);
107
+ }
108
+
109
+ .collapsible-section-content {
110
+ display: flex;
111
+ flex-direction: column;
112
+ gap: 0.5rem;
113
+ box-sizing: border-box;
114
+ width: auto;
115
+ min-width: 0;
116
+ margin-top: 0.5rem;
117
+ margin-inline-start: 0.5rem;
118
+ padding-inline-start: 0.75rem;
119
+ /* border-inline-start: 2px solid rgba(127, 127, 127, 0.25); */
120
+ }
121
+
122
+ @media (prefers-reduced-motion: reduce) {
123
+ .collapsible-section-indicator {
124
+ transition: none;
125
+ }
126
+ }
127
+ </style>
@@ -0,0 +1,26 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ title: string;
5
+ open?: boolean;
6
+ contentId?: string | undefined;
7
+ labelledBy?: string | undefined;
8
+ onToggle?: ((open: boolean) => void) | undefined;
9
+ };
10
+ events: {
11
+ [evt: string]: CustomEvent<any>;
12
+ };
13
+ slots: {
14
+ heading: {};
15
+ actions: {};
16
+ default: {};
17
+ };
18
+ exports?: {} | undefined;
19
+ bindings?: string | undefined;
20
+ };
21
+ export type CollapsibleSectionProps = typeof __propDef.props;
22
+ export type CollapsibleSectionEvents = typeof __propDef.events;
23
+ export type CollapsibleSectionSlots = typeof __propDef.slots;
24
+ export default class CollapsibleSection extends SvelteComponent<CollapsibleSectionProps, CollapsibleSectionEvents, CollapsibleSectionSlots> {
25
+ }
26
+ export {};
@@ -1,12 +1,27 @@
1
- <script>import { Label } from "@stubber/ui/label";
1
+ <script>import { getContext } from "svelte";
2
2
  import FieldExprToggle from "./FieldExprToggle.svelte";
3
+ import { STYLES_CONTEXT, classNames } from "./styles";
3
4
  export let fieldStore;
5
+ export let with_margin = true;
6
+ const styles = getContext(STYLES_CONTEXT);
4
7
  $: hide_label = $fieldStore.hide_label;
5
8
  </script>
6
9
 
7
- <div class="mb-1 flex flex-row items-center justify-between">
10
+ <div class="field-label-wrapper" class:field-label-wrapper-spacing={with_margin}>
8
11
  {#if !hide_label}
9
- <Label for={$fieldStore.id}>{$fieldStore.label}</Label>
12
+ <label class={$styles.label} for={$fieldStore.id}>{$fieldStore.label}</label>
10
13
  {/if}
11
14
  <FieldExprToggle {fieldStore} />
12
15
  </div>
16
+
17
+ <style>
18
+ .field-label-wrapper {
19
+ display: flex;
20
+ justify-content: space-between;
21
+ align-items: center;
22
+ }
23
+
24
+ .field-label-wrapper-spacing {
25
+ margin-bottom: 0.25rem;
26
+ }
27
+ </style>
@@ -4,6 +4,7 @@ import type { IBuiltField } from "./interfaces";
4
4
  declare const __propDef: {
5
5
  props: {
6
6
  fieldStore: Writable<IBuiltField>;
7
+ with_margin?: boolean;
7
8
  };
8
9
  events: {
9
10
  [evt: string]: CustomEvent<any>;
@@ -1,15 +1,26 @@
1
- <script>import { Label } from "@stubber/ui/label";
1
+ <script>import { getContext } from "svelte";
2
+ import { STYLES_CONTEXT, classNames } from "./styles";
2
3
  export let fieldStore;
4
+ const styles = getContext(STYLES_CONTEXT);
3
5
  $: validation_result = $fieldStore.validation_result;
4
- $: type = validation_result?.type;
5
6
  </script>
6
7
 
7
- <Label
8
+ <p
8
9
  id="message-{$fieldStore.id}"
9
- for={$fieldStore.id}
10
- class="block truncate text-xs {!$fieldStore.validation_result ? 'invisible' : ''} {type == 'error'
11
- ? 'text-red-500'
12
- : 'text-muted-foreground'}"
10
+ aria-hidden={!validation_result}
11
+ class={classNames("field-message", $styles.message)}
12
+ data-variant={validation_result?.type === "error" ? "error" : "help"}
13
13
  >
14
- {$fieldStore.validation_result?.message}
15
- </Label>
14
+ {validation_result?.message ?? "\u00a0"}
15
+ </p>
16
+
17
+ <style>
18
+ .field-message {
19
+ margin: 0;
20
+ min-height: 1em;
21
+ }
22
+
23
+ .field-message[aria-hidden="true"] {
24
+ visibility: hidden;
25
+ }
26
+ </style>
@@ -1,6 +1,7 @@
1
- <script>import { setContext } from "svelte";
1
+ <script>import { getContext, setContext } from "svelte";
2
2
  import { get as getStoreValue, writable } from "svelte/store";
3
3
  import FormField from "./form-field.svelte";
4
+ import { STYLES_CONTEXT } from "./styles";
4
5
  import { build_fields } from "./utils";
5
6
  import { validate_field } from "./validations/validate_field";
6
7
  export let initial_form;
@@ -9,7 +10,14 @@ export let attachments = writable([]);
9
10
  export let dependencies = void 0;
10
11
  export let form_valid = false;
11
12
  export let dynamic_field_type = false;
13
+ export let styles = void 0;
12
14
  setContext("dynamic_field_type", dynamic_field_type);
15
+ const parentStyles = getContext(STYLES_CONTEXT);
16
+ const stylesContext = writable(
17
+ styles ?? (parentStyles ? getStoreValue(parentStyles) : {})
18
+ );
19
+ setContext(STYLES_CONTEXT, stylesContext);
20
+ $: stylesContext.set(styles ?? (parentStyles ? getStoreValue(parentStyles) : {}));
13
21
  let fields = [];
14
22
  let form_errors = {};
15
23
  const rebuild_fields = (initial_form2, form2) => {
@@ -42,8 +50,17 @@ const validate_form = async (_) => {
42
50
  };
43
51
  </script>
44
52
 
45
- <div class="flex flex-col gap-y-2">
53
+ <div class="form">
46
54
  {#each fields as fieldStore (getStoreValue(fieldStore).id)}
47
55
  <FormField {fieldStore} />
48
56
  {/each}
49
57
  </div>
58
+
59
+ <style>
60
+ .form {
61
+ display: flex;
62
+ flex-direction: column;
63
+ gap: 0.5rem;
64
+ width: 100%;
65
+ }
66
+ </style>