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