@budibase/bbui 1.1.32 → 1.1.33-alpha.2

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.
@@ -0,0 +1,436 @@
1
+ <script>
2
+ import "@spectrum-css/inputgroup/dist/index-vars.css"
3
+ import "@spectrum-css/popover/dist/index-vars.css"
4
+ import "@spectrum-css/menu/dist/index-vars.css"
5
+ import { fly } from "svelte/transition"
6
+ import { createEventDispatcher } from "svelte"
7
+ import clickOutside from "../../Actions/click_outside"
8
+ import Icon from "../../Icon/Icon.svelte"
9
+ import StatusLight from "../../StatusLight/StatusLight.svelte"
10
+ import Detail from "../../Typography/Detail.svelte"
11
+ import Search from "./Search.svelte"
12
+
13
+ export let primaryLabel = ""
14
+ export let primaryValue = null
15
+ export let id = null
16
+ export let placeholder = "Choose an option or type"
17
+ export let disabled = false
18
+ export let readonly = false
19
+ export let updateOnChange = true
20
+ export let error = null
21
+ export let secondaryOptions = []
22
+ export let primaryOptions = []
23
+ export let secondaryFieldText = ""
24
+ export let secondaryFieldIcon = ""
25
+ export let secondaryFieldColour = ""
26
+ export let getPrimaryOptionValue = option => option
27
+ export let getPrimaryOptionColour = () => null
28
+ export let getPrimaryOptionIcon = () => null
29
+ export let getSecondaryOptionLabel = option => option
30
+ export let getSecondaryOptionValue = option => option
31
+ export let getSecondaryOptionColour = () => null
32
+ export let onSelectOption = () => {}
33
+ export let autoWidth = false
34
+ export let autocomplete = false
35
+ export let isOptionSelected = () => false
36
+ export let isPlaceholder = false
37
+ export let placeholderOption = null
38
+
39
+ const dispatch = createEventDispatcher()
40
+ let primaryOpen = false
41
+ let secondaryOpen = false
42
+ let focus = false
43
+ let searchTerm = null
44
+
45
+ $: groupTitles = Object.keys(primaryOptions)
46
+ let iconData
47
+
48
+ const updateSearch = e => {
49
+ dispatch("search", e.detail)
50
+ }
51
+
52
+ const updateValue = newValue => {
53
+ if (readonly) {
54
+ return
55
+ }
56
+ dispatch("change", newValue)
57
+ }
58
+
59
+ const onClickSecondary = () => {
60
+ dispatch("click")
61
+ if (readonly) {
62
+ return
63
+ }
64
+ secondaryOpen = true
65
+ }
66
+
67
+ const onPickPrimary = newValue => {
68
+ dispatch("pickprimary", newValue)
69
+ primaryOpen = false
70
+ }
71
+
72
+ const onClearPrimary = () => {
73
+ dispatch("pickprimary", null)
74
+ primaryOpen = false
75
+ }
76
+
77
+ const onPickSecondary = newValue => {
78
+ dispatch("picksecondary", newValue)
79
+ secondaryOpen = false
80
+ }
81
+
82
+ const onBlur = event => {
83
+ if (readonly) {
84
+ return
85
+ }
86
+ focus = false
87
+ updateValue(event.target.value)
88
+ }
89
+
90
+ const onInput = event => {
91
+ if (readonly || !updateOnChange) {
92
+ return
93
+ }
94
+ updateValue(event.target.value)
95
+ }
96
+
97
+ const updateValueOnEnter = event => {
98
+ if (readonly) {
99
+ return
100
+ }
101
+ if (event.key === "Enter") {
102
+ updateValue(event.target.value)
103
+ }
104
+ }
105
+ </script>
106
+
107
+ <div
108
+ class="spectrum-InputGroup"
109
+ class:is-invalid={!!error}
110
+ class:is-disabled={disabled}
111
+ >
112
+ <div
113
+ class="spectrum-Textfield spectrum-InputGroup-textfield"
114
+ class:is-invalid={!!error}
115
+ class:is-disabled={disabled}
116
+ class:is-focused={focus}
117
+ class:is-full-width={!secondaryOptions.length}
118
+ >
119
+ {#if iconData}
120
+ <svg
121
+ width="16px"
122
+ height="16px"
123
+ class="spectrum-Icon iconPadding"
124
+ style="color: {iconData?.color}"
125
+ focusable="false"
126
+ >
127
+ <use xlink:href="#spectrum-icon-18-{iconData?.icon}" />
128
+ </svg>
129
+ {/if}
130
+ <input
131
+ {id}
132
+ on:click={() => (primaryOpen = true)}
133
+ on:blur
134
+ on:focus
135
+ on:input
136
+ on:keyup
137
+ on:blur={onBlur}
138
+ on:input={onInput}
139
+ on:keyup={updateValueOnEnter}
140
+ value={primaryLabel || ""}
141
+ placeholder={placeholder || ""}
142
+ {disabled}
143
+ {readonly}
144
+ class="spectrum-Textfield-input spectrum-InputGroup-input"
145
+ class:labelPadding={iconData}
146
+ />
147
+ {#if primaryValue}
148
+ <button
149
+ on:click={() => onClearPrimary()}
150
+ type="reset"
151
+ class="spectrum-ClearButton spectrum-Search-clearButton"
152
+ >
153
+ <svg
154
+ class="spectrum-Icon spectrum-UIIcon-Cross75"
155
+ focusable="false"
156
+ aria-hidden="true"
157
+ >
158
+ <use xlink:href="#spectrum-css-icon-Cross75" />
159
+ </svg>
160
+ </button>
161
+ {/if}
162
+ </div>
163
+ {#if primaryOpen}
164
+ <div
165
+ use:clickOutside={() => (primaryOpen = false)}
166
+ transition:fly|local={{ y: -20, duration: 200 }}
167
+ class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
168
+ class:auto-width={autoWidth}
169
+ class:is-full-width={!secondaryOptions.length}
170
+ >
171
+ {#if autocomplete}
172
+ <Search
173
+ value={searchTerm}
174
+ on:change={event => updateSearch(event)}
175
+ {disabled}
176
+ placeholder="Search"
177
+ />
178
+ {/if}
179
+
180
+ <ul class="spectrum-Menu" role="listbox">
181
+ {#if placeholderOption}
182
+ <li
183
+ class="spectrum-Menu-item placeholder"
184
+ class:is-selected={isPlaceholder}
185
+ role="option"
186
+ aria-selected="true"
187
+ tabindex="0"
188
+ on:click={() => onSelectOption(null)}
189
+ >
190
+ <span class="spectrum-Menu-itemLabel">{placeholderOption}</span>
191
+ <svg
192
+ class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
193
+ focusable="false"
194
+ aria-hidden="true"
195
+ >
196
+ <use xlink:href="#spectrum-css-icon-Checkmark100" />
197
+ </svg>
198
+ </li>
199
+ {/if}
200
+ {#each groupTitles as title}
201
+ <div class="spectrum-Menu-item">
202
+ <Detail>{title}</Detail>
203
+ </div>
204
+ {#if primaryOptions}
205
+ {#each primaryOptions[title].data as option, idx}
206
+ <li
207
+ class="spectrum-Menu-item"
208
+ class:is-selected={isOptionSelected(
209
+ getPrimaryOptionValue(option, idx)
210
+ )}
211
+ role="option"
212
+ aria-selected="true"
213
+ tabindex="0"
214
+ on:click={() =>
215
+ onPickPrimary({
216
+ value: primaryOptions[title].getValue(option),
217
+ label: primaryOptions[title].getLabel(option),
218
+ })}
219
+ >
220
+ {#if primaryOptions[title].getIcon(option)}
221
+ <div
222
+ style="background: {primaryOptions[title].getColour(
223
+ option
224
+ )};"
225
+ class="circle"
226
+ >
227
+ <div>
228
+ <Icon
229
+ size="S"
230
+ name={primaryOptions[title].getIcon(option)}
231
+ />
232
+ </div>
233
+ </div>
234
+ {:else if getPrimaryOptionColour(option, idx)}
235
+ <span class="option-left">
236
+ <StatusLight
237
+ square
238
+ color={getPrimaryOptionColour(option, idx)}
239
+ />
240
+ </span>
241
+ {/if}
242
+ <span class="spectrum-Menu-itemLabel">
243
+ <span
244
+ class:spacing-group={primaryOptions[title].getIcon(option)}
245
+ >
246
+ {primaryOptions[title].getLabel(option)}
247
+ <span />
248
+ </span>
249
+ <svg
250
+ class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
251
+ focusable="false"
252
+ aria-hidden="true"
253
+ >
254
+ <use xlink:href="#spectrum-css-icon-Checkmark100" />
255
+ </svg>
256
+ {#if getPrimaryOptionIcon(option, idx) && getPrimaryOptionColour(option, idx)}
257
+ <span class="option-right">
258
+ <StatusLight
259
+ square
260
+ color={getPrimaryOptionColour(option, idx)}
261
+ />
262
+ </span>
263
+ {/if}
264
+ </span>
265
+ </li>
266
+ {/each}
267
+ {/if}
268
+ {/each}
269
+ </ul>
270
+ </div>
271
+ {/if}
272
+ {#if secondaryOptions.length}
273
+ <div style="width: 30%">
274
+ <button
275
+ {id}
276
+ class="spectrum-Picker spectrum-Picker--sizeM override-borders"
277
+ {disabled}
278
+ class:is-open={secondaryOpen}
279
+ aria-haspopup="listbox"
280
+ on:mousedown={onClickSecondary}
281
+ >
282
+ {#if secondaryFieldIcon}
283
+ <span class="option-left">
284
+ <Icon name={secondaryFieldIcon} />
285
+ </span>
286
+ {:else if secondaryFieldColour}
287
+ <span class="option-left">
288
+ <StatusLight square color={secondaryFieldColour} />
289
+ </span>
290
+ {/if}
291
+
292
+ <span class:auto-width={autoWidth} class="spectrum-Picker-label">
293
+ {secondaryFieldText}
294
+ </span>
295
+ <svg
296
+ class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
297
+ focusable="false"
298
+ aria-hidden="true"
299
+ >
300
+ <use xlink:href="#spectrum-css-icon-Chevron100" />
301
+ </svg>
302
+ </button>
303
+ {#if secondaryOpen}
304
+ <div
305
+ use:clickOutside={() => (secondaryOpen = false)}
306
+ transition:fly|local={{ y: -20, duration: 200 }}
307
+ class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
308
+ style="width: 30%"
309
+ >
310
+ <ul class="spectrum-Menu" role="listbox">
311
+ {#each secondaryOptions as option, idx}
312
+ <li
313
+ class="spectrum-Menu-item"
314
+ class:is-selected={isOptionSelected(
315
+ getSecondaryOptionValue(option, idx)
316
+ )}
317
+ role="option"
318
+ aria-selected="true"
319
+ tabindex="0"
320
+ on:click={() =>
321
+ onPickSecondary(getSecondaryOptionValue(option, idx))}
322
+ >
323
+ {#if getSecondaryOptionColour(option, idx)}
324
+ <span class="option-left">
325
+ <StatusLight
326
+ square
327
+ color={getSecondaryOptionColour(option, idx)}
328
+ />
329
+ </span>
330
+ {/if}
331
+
332
+ <span class="spectrum-Menu-itemLabel">
333
+ {getSecondaryOptionLabel(option, idx)}
334
+ </span>
335
+ <svg
336
+ class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
337
+ focusable="false"
338
+ aria-hidden="true"
339
+ >
340
+ <use xlink:href="#spectrum-css-icon-Checkmark100" />
341
+ </svg>
342
+ </li>
343
+ {/each}
344
+ </ul>
345
+ </div>
346
+ {/if}
347
+ </div>
348
+ {/if}
349
+ </div>
350
+
351
+ <style>
352
+ .spacing-group {
353
+ margin-left: var(--spacing-m);
354
+ }
355
+ .spectrum-InputGroup {
356
+ min-width: 0;
357
+ width: 100%;
358
+ }
359
+ .spectrum-InputGroup :global(.spectrum-Search-input) {
360
+ border: none;
361
+ border-bottom: 1px solid var(--spectrum-global-color-gray-300);
362
+ border-bottom-left-radius: 0;
363
+ border-bottom-right-radius: 0;
364
+ }
365
+
366
+ .override-borders {
367
+ border-top-left-radius: 0px;
368
+ border-bottom-left-radius: 0px;
369
+ }
370
+
371
+ .spectrum-Popover {
372
+ max-height: 240px;
373
+ z-index: 999;
374
+ top: 100%;
375
+ }
376
+
377
+ .option-left {
378
+ padding-right: 8px;
379
+ }
380
+ .option-right {
381
+ padding-left: 8px;
382
+ }
383
+
384
+ .circle {
385
+ border-radius: 50%;
386
+ height: 28px;
387
+ color: white;
388
+ font-weight: bold;
389
+ line-height: 48px;
390
+ font-size: 1.2em;
391
+ width: 28px;
392
+ position: relative;
393
+ }
394
+
395
+ .circle > div {
396
+ position: absolute;
397
+ text-decoration: none;
398
+ top: 50%;
399
+ left: 50%;
400
+ transform: translateX(-50%) translateY(-50%);
401
+ }
402
+
403
+ .iconPadding {
404
+ position: absolute;
405
+ top: 50%;
406
+ left: 10px;
407
+ transform: translateY(-50%);
408
+ color: silver;
409
+ margin-right: 10px;
410
+ }
411
+
412
+ .labelPadding {
413
+ padding-left: calc(1em + 10px + 8px);
414
+ }
415
+
416
+ .spectrum-Textfield.spectrum-InputGroup-textfield {
417
+ width: 70%;
418
+ }
419
+ .spectrum-Textfield.spectrum-InputGroup-textfield.is-full-width {
420
+ width: 100%;
421
+ }
422
+ .spectrum-Textfield.spectrum-InputGroup-textfield.is-full-width input {
423
+ border-right-width: thin;
424
+ }
425
+
426
+ .spectrum-Popover.spectrum-Popover--bottom.spectrum-Picker-popover.is-open {
427
+ width: 70%;
428
+ }
429
+ .spectrum-Popover.spectrum-Popover--bottom.spectrum-Picker-popover.is-open.is-full-width {
430
+ width: 100%;
431
+ }
432
+
433
+ .spectrum-Search-clearButton {
434
+ position: absolute;
435
+ }
436
+ </style>
@@ -17,7 +17,6 @@
17
17
  export let autoWidth = false
18
18
  export let autocomplete = false
19
19
  export let sort = false
20
-
21
20
  const dispatch = createEventDispatcher()
22
21
  let open = false
23
22
  $: fieldText = getFieldText(value, options, placeholder)
@@ -0,0 +1,55 @@
1
+ <script>
2
+ import Field from "./Field.svelte"
3
+ import InputDropdown from "./Core/InputDropdown.svelte"
4
+ import { createEventDispatcher } from "svelte"
5
+
6
+ export let inputValue = null
7
+ export let dropdownValue = null
8
+ export let inputType = "text"
9
+ export let label = null
10
+ export let labelPosition = "above"
11
+ export let placeholder = null
12
+ export let disabled = false
13
+ export let readonly = false
14
+ export let error = null
15
+ export let updateOnChange = true
16
+ export let quiet = false
17
+ export let dataCy
18
+ export let autofocus
19
+ export let options = []
20
+
21
+ const dispatch = createEventDispatcher()
22
+
23
+ const onPick = e => {
24
+ dropdownValue = e.detail
25
+ dispatch("pick", e.detail)
26
+ }
27
+ const onChange = e => {
28
+ inputValue = e.detail
29
+ dispatch("change", e.detail)
30
+ }
31
+ </script>
32
+
33
+ <Field {label} {labelPosition} {error}>
34
+ <InputDropdown
35
+ {dataCy}
36
+ {updateOnChange}
37
+ {error}
38
+ {disabled}
39
+ {readonly}
40
+ {inputValue}
41
+ {dropdownValue}
42
+ {placeholder}
43
+ {inputType}
44
+ {quiet}
45
+ {autofocus}
46
+ {options}
47
+ on:change={onChange}
48
+ on:pick={onPick}
49
+ on:click
50
+ on:input
51
+ on:blur
52
+ on:focus
53
+ on:keyup
54
+ />
55
+ </Field>
@@ -14,7 +14,7 @@
14
14
  export let getOptionLabel = option => option
15
15
  export let getOptionValue = option => option
16
16
  export let sort = false
17
-
17
+ export let autoWidth = false
18
18
  const dispatch = createEventDispatcher()
19
19
  const onChange = e => {
20
20
  value = e.detail
@@ -33,6 +33,7 @@
33
33
  {sort}
34
34
  {getOptionLabel}
35
35
  {getOptionValue}
36
+ {autoWidth}
36
37
  on:change={onChange}
37
38
  on:click
38
39
  />
@@ -0,0 +1,132 @@
1
+ <script>
2
+ import Field from "./Field.svelte"
3
+ import PickerDropdown from "./Core/PickerDropdown.svelte"
4
+ import { createEventDispatcher } from "svelte"
5
+
6
+ export let primaryValue = null
7
+ export let secondaryValue = null
8
+ export let inputType = "text"
9
+ export let label = null
10
+ export let labelPosition = "above"
11
+ export let secondaryPlaceholder = null
12
+ export let autocomplete
13
+ export let placeholder = null
14
+ export let disabled = false
15
+ export let readonly = false
16
+ export let error = null
17
+ export let updateOnChange = true
18
+ export let getSecondaryOptionLabel = option =>
19
+ extractProperty(option, "label")
20
+ export let getSecondaryOptionValue = option =>
21
+ extractProperty(option, "value")
22
+ export let getSecondaryOptionColour = () => {}
23
+ export let getSecondaryOptionIcon = () => {}
24
+ export let quiet = false
25
+ export let dataCy
26
+ export let autofocus
27
+ export let primaryOptions = []
28
+ export let secondaryOptions = []
29
+ export let searchTerm
30
+
31
+ let primaryLabel
32
+ let secondaryLabel
33
+ const dispatch = createEventDispatcher()
34
+
35
+ $: secondaryFieldText = getSecondaryFieldText(
36
+ secondaryValue,
37
+ secondaryOptions,
38
+ secondaryPlaceholder
39
+ )
40
+ $: secondaryFieldIcon = getSecondaryFieldAttribute(
41
+ getSecondaryOptionIcon,
42
+ secondaryValue,
43
+ secondaryOptions
44
+ )
45
+ $: secondaryFieldColour = getSecondaryFieldAttribute(
46
+ getSecondaryOptionColour,
47
+ secondaryValue,
48
+ secondaryOptions
49
+ )
50
+
51
+ const getSecondaryFieldAttribute = (getAttribute, value, options) => {
52
+ // Wait for options to load if there is a value but no options
53
+
54
+ if (!options?.length) {
55
+ return ""
56
+ }
57
+
58
+ const index = options.findIndex(
59
+ (option, idx) => getSecondaryOptionValue(option, idx) === value
60
+ )
61
+
62
+ return index !== -1 ? getAttribute(options[index], index) : null
63
+ }
64
+
65
+ const getSecondaryFieldText = (value, options, placeholder) => {
66
+ // Always use placeholder if no value
67
+ if (value == null || value === "") {
68
+ return placeholder || "Choose an option"
69
+ }
70
+
71
+ return getSecondaryFieldAttribute(getSecondaryOptionLabel, value, options)
72
+ }
73
+
74
+ const onPickPrimary = e => {
75
+ primaryLabel = e?.detail?.label || null
76
+ primaryValue = e?.detail?.value || null
77
+ dispatch("pickprimary", e?.detail?.value || {})
78
+ }
79
+
80
+ const onPickSecondary = e => {
81
+ secondaryValue = e.detail
82
+ dispatch("picksecondary", e.detail)
83
+ }
84
+
85
+ const extractProperty = (value, property) => {
86
+ if (value && typeof value === "object") {
87
+ return value[property]
88
+ }
89
+ return value
90
+ }
91
+
92
+ const updateSearchTerm = e => {
93
+ searchTerm = e.detail
94
+ }
95
+ </script>
96
+
97
+ <Field {label} {labelPosition} {error}>
98
+ <PickerDropdown
99
+ {searchTerm}
100
+ {autocomplete}
101
+ {dataCy}
102
+ {updateOnChange}
103
+ {error}
104
+ {disabled}
105
+ {readonly}
106
+ {placeholder}
107
+ {inputType}
108
+ {quiet}
109
+ {autofocus}
110
+ {primaryOptions}
111
+ {secondaryOptions}
112
+ {getSecondaryOptionLabel}
113
+ {getSecondaryOptionValue}
114
+ {getSecondaryOptionIcon}
115
+ {getSecondaryOptionColour}
116
+ {secondaryFieldText}
117
+ {secondaryFieldIcon}
118
+ {secondaryFieldColour}
119
+ {primaryValue}
120
+ {secondaryValue}
121
+ {primaryLabel}
122
+ {secondaryLabel}
123
+ on:pickprimary={onPickPrimary}
124
+ on:picksecondary={onPickSecondary}
125
+ on:search={updateSearchTerm}
126
+ on:click
127
+ on:input
128
+ on:blur
129
+ on:focus
130
+ on:keyup
131
+ />
132
+ </Field>