@budibase/bbui 2.23.12 → 2.24.1
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/dist/bbui.es.js +2 -2
- package/dist/bbui.es.js.map +1 -1
- package/package.json +5 -5
- package/src/ActionMenu/ActionMenu.svelte +9 -1
- package/src/Actions/click_outside.js +22 -16
- package/src/Actions/position_dropdown.js +155 -31
- package/src/Form/Core/DatePicker/Calendar.svelte +252 -0
- package/src/Form/Core/DatePicker/DateInput.svelte +94 -0
- package/src/Form/Core/DatePicker/DatePicker.svelte +83 -0
- package/src/Form/Core/DatePicker/DatePickerPopoverContents.svelte +102 -0
- package/src/Form/Core/DatePicker/NumberInput.svelte +54 -0
- package/src/Form/Core/DatePicker/TimePicker.svelte +59 -0
- package/src/Form/Core/DatePicker/utils.js +14 -0
- package/src/Form/Core/DateRangePicker.svelte +69 -0
- package/src/Form/Core/Picker.svelte +1 -0
- package/src/Form/Core/index.js +3 -1
- package/src/Form/DatePicker.svelte +3 -12
- package/src/Form/DateRangePicker.svelte +34 -0
- package/src/Layout/Page.svelte +5 -5
- package/src/Popover/Popover.svelte +6 -3
- package/src/helpers.js +108 -0
- package/src/index.js +25 -24
- package/src/Form/Core/DatePicker.svelte +0 -268
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import "@spectrum-css/calendar/dist/index-vars.css"
|
|
3
|
+
import "@spectrum-css/inputgroup/dist/index-vars.css"
|
|
4
|
+
import "@spectrum-css/textfield/dist/index-vars.css"
|
|
5
|
+
import Popover from "../../../Popover/Popover.svelte"
|
|
6
|
+
import { onMount } from "svelte"
|
|
7
|
+
import DateInput from "./DateInput.svelte"
|
|
8
|
+
import { parseDate } from "../../../helpers"
|
|
9
|
+
import DatePickerPopoverContents from "./DatePickerPopoverContents.svelte"
|
|
10
|
+
|
|
11
|
+
export let id = null
|
|
12
|
+
export let disabled = false
|
|
13
|
+
export let readonly = false
|
|
14
|
+
export let error = null
|
|
15
|
+
export let enableTime = true
|
|
16
|
+
export let value = null
|
|
17
|
+
export let placeholder = null
|
|
18
|
+
export let timeOnly = false
|
|
19
|
+
export let ignoreTimezones = false
|
|
20
|
+
export let useKeyboardShortcuts = true
|
|
21
|
+
export let appendTo = null
|
|
22
|
+
export let api = null
|
|
23
|
+
export let align = "left"
|
|
24
|
+
|
|
25
|
+
let isOpen = false
|
|
26
|
+
let anchor
|
|
27
|
+
let popover
|
|
28
|
+
|
|
29
|
+
$: parsedValue = parseDate(value, { timeOnly, enableTime })
|
|
30
|
+
|
|
31
|
+
const onOpen = () => {
|
|
32
|
+
isOpen = true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const onClose = () => {
|
|
36
|
+
isOpen = false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
onMount(() => {
|
|
40
|
+
api = {
|
|
41
|
+
open: () => popover?.show(),
|
|
42
|
+
close: () => popover?.hide(),
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<DateInput
|
|
48
|
+
bind:anchor
|
|
49
|
+
{disabled}
|
|
50
|
+
{readonly}
|
|
51
|
+
{error}
|
|
52
|
+
{placeholder}
|
|
53
|
+
{id}
|
|
54
|
+
{enableTime}
|
|
55
|
+
{timeOnly}
|
|
56
|
+
focused={isOpen}
|
|
57
|
+
value={parsedValue}
|
|
58
|
+
on:click={popover?.show}
|
|
59
|
+
icon={timeOnly ? "Clock" : "Calendar"}
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
<Popover
|
|
63
|
+
bind:this={popover}
|
|
64
|
+
on:open
|
|
65
|
+
on:close
|
|
66
|
+
on:open={onOpen}
|
|
67
|
+
on:close={onClose}
|
|
68
|
+
portalTarget={appendTo}
|
|
69
|
+
{anchor}
|
|
70
|
+
{align}
|
|
71
|
+
resizable={false}
|
|
72
|
+
>
|
|
73
|
+
{#if isOpen}
|
|
74
|
+
<DatePickerPopoverContents
|
|
75
|
+
{useKeyboardShortcuts}
|
|
76
|
+
{ignoreTimezones}
|
|
77
|
+
{enableTime}
|
|
78
|
+
{timeOnly}
|
|
79
|
+
value={parsedValue}
|
|
80
|
+
on:change
|
|
81
|
+
/>
|
|
82
|
+
{/if}
|
|
83
|
+
</Popover>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import dayjs from "dayjs"
|
|
3
|
+
import TimePicker from "./TimePicker.svelte"
|
|
4
|
+
import Calendar from "./Calendar.svelte"
|
|
5
|
+
import ActionButton from "../../../ActionButton/ActionButton.svelte"
|
|
6
|
+
import { createEventDispatcher, onMount } from "svelte"
|
|
7
|
+
import { stringifyDate } from "../../../helpers"
|
|
8
|
+
|
|
9
|
+
export let useKeyboardShortcuts = true
|
|
10
|
+
export let ignoreTimezones
|
|
11
|
+
export let enableTime
|
|
12
|
+
export let timeOnly
|
|
13
|
+
export let value
|
|
14
|
+
|
|
15
|
+
const dispatch = createEventDispatcher()
|
|
16
|
+
let calendar
|
|
17
|
+
|
|
18
|
+
$: showCalendar = !timeOnly
|
|
19
|
+
$: showTime = enableTime || timeOnly
|
|
20
|
+
|
|
21
|
+
const setToNow = () => {
|
|
22
|
+
const now = dayjs()
|
|
23
|
+
calendar?.setDate(now)
|
|
24
|
+
handleChange(now)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const handleChange = date => {
|
|
28
|
+
dispatch(
|
|
29
|
+
"change",
|
|
30
|
+
stringifyDate(date, { enableTime, timeOnly, ignoreTimezones })
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const clearDateOnBackspace = event => {
|
|
35
|
+
// Ignore if we're typing a value
|
|
36
|
+
if (document.activeElement?.tagName.toLowerCase() === "input") {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
|
|
40
|
+
dispatch("change", null)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onMount(() => {
|
|
45
|
+
if (useKeyboardShortcuts) {
|
|
46
|
+
document.addEventListener("keyup", clearDateOnBackspace)
|
|
47
|
+
}
|
|
48
|
+
return () => {
|
|
49
|
+
document.removeEventListener("keyup", clearDateOnBackspace)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<div class="date-time-popover">
|
|
55
|
+
{#if showCalendar}
|
|
56
|
+
<Calendar
|
|
57
|
+
{value}
|
|
58
|
+
on:change={e => handleChange(e.detail)}
|
|
59
|
+
bind:this={calendar}
|
|
60
|
+
/>
|
|
61
|
+
{/if}
|
|
62
|
+
<div class="footer" class:spaced={showCalendar}>
|
|
63
|
+
{#if showTime}
|
|
64
|
+
<TimePicker {value} on:change={e => handleChange(e.detail)} />
|
|
65
|
+
{/if}
|
|
66
|
+
<div class="actions">
|
|
67
|
+
<ActionButton
|
|
68
|
+
disabled={!value}
|
|
69
|
+
size="S"
|
|
70
|
+
on:click={() => dispatch("change", null)}
|
|
71
|
+
>
|
|
72
|
+
Clear
|
|
73
|
+
</ActionButton>
|
|
74
|
+
<ActionButton size="S" on:click={setToNow}>
|
|
75
|
+
{showTime ? "Now" : "Today"}
|
|
76
|
+
</ActionButton>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<style>
|
|
82
|
+
.date-time-popover {
|
|
83
|
+
padding: 8px;
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
}
|
|
86
|
+
.footer {
|
|
87
|
+
display: flex;
|
|
88
|
+
justify-content: space-between;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: 60px;
|
|
91
|
+
}
|
|
92
|
+
.footer.spaced {
|
|
93
|
+
padding-top: 14px;
|
|
94
|
+
}
|
|
95
|
+
.actions {
|
|
96
|
+
padding: 4px 0;
|
|
97
|
+
flex: 1 1 auto;
|
|
98
|
+
display: flex;
|
|
99
|
+
justify-content: flex-end;
|
|
100
|
+
gap: 6px;
|
|
101
|
+
}
|
|
102
|
+
</style>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export let value
|
|
3
|
+
export let min
|
|
4
|
+
export let max
|
|
5
|
+
export let hideArrows = false
|
|
6
|
+
export let width
|
|
7
|
+
|
|
8
|
+
$: style = width ? `width:${width}px;` : ""
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<input
|
|
12
|
+
class:hide-arrows={hideArrows}
|
|
13
|
+
type="number"
|
|
14
|
+
{style}
|
|
15
|
+
{value}
|
|
16
|
+
{min}
|
|
17
|
+
{max}
|
|
18
|
+
onclick="this.select()"
|
|
19
|
+
on:change
|
|
20
|
+
on:input
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<style>
|
|
24
|
+
input {
|
|
25
|
+
background: none;
|
|
26
|
+
border: none;
|
|
27
|
+
outline: none;
|
|
28
|
+
color: var(--spectrum-alias-text-color);
|
|
29
|
+
padding: 4px 6px 5px 6px;
|
|
30
|
+
border-radius: 4px;
|
|
31
|
+
transition: background 130ms ease-out;
|
|
32
|
+
font-size: 18px;
|
|
33
|
+
font-weight: bold;
|
|
34
|
+
font-family: var(--font-sans);
|
|
35
|
+
-webkit-font-smoothing: antialiased;
|
|
36
|
+
box-sizing: content-box !important;
|
|
37
|
+
}
|
|
38
|
+
input:focus,
|
|
39
|
+
input:hover {
|
|
40
|
+
--space: 30px;
|
|
41
|
+
background: var(--spectrum-global-color-gray-200);
|
|
42
|
+
z-index: 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Hide built-in arrows */
|
|
46
|
+
input.hide-arrows::-webkit-outer-spin-button,
|
|
47
|
+
input.hide-arrows::-webkit-inner-spin-button {
|
|
48
|
+
-webkit-appearance: none;
|
|
49
|
+
margin: 0;
|
|
50
|
+
}
|
|
51
|
+
input.hide-arrows {
|
|
52
|
+
-moz-appearance: textfield;
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { cleanInput } from "./utils"
|
|
3
|
+
import dayjs from "dayjs"
|
|
4
|
+
import NumberInput from "./NumberInput.svelte"
|
|
5
|
+
import { createEventDispatcher } from "svelte"
|
|
6
|
+
|
|
7
|
+
export let value
|
|
8
|
+
|
|
9
|
+
const dispatch = createEventDispatcher()
|
|
10
|
+
|
|
11
|
+
$: displayValue = value || dayjs()
|
|
12
|
+
|
|
13
|
+
const handleHourChange = e => {
|
|
14
|
+
dispatch("change", displayValue.hour(parseInt(e.target.value)))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const handleMinuteChange = e => {
|
|
18
|
+
dispatch("change", displayValue.minute(parseInt(e.target.value)))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const cleanHour = cleanInput({ max: 23, pad: 2, fallback: "00" })
|
|
22
|
+
const cleanMinute = cleanInput({ max: 59, pad: 2, fallback: "00" })
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<div class="time-picker">
|
|
26
|
+
<NumberInput
|
|
27
|
+
hideArrows
|
|
28
|
+
value={displayValue.hour().toString().padStart(2, "0")}
|
|
29
|
+
min={0}
|
|
30
|
+
max={23}
|
|
31
|
+
width={20}
|
|
32
|
+
on:input={cleanHour}
|
|
33
|
+
on:change={handleHourChange}
|
|
34
|
+
/>
|
|
35
|
+
<span>:</span>
|
|
36
|
+
<NumberInput
|
|
37
|
+
hideArrows
|
|
38
|
+
value={displayValue.minute().toString().padStart(2, "0")}
|
|
39
|
+
min={0}
|
|
40
|
+
max={59}
|
|
41
|
+
width={20}
|
|
42
|
+
on:input={cleanMinute}
|
|
43
|
+
on:change={handleMinuteChange}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.time-picker {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-direction: row;
|
|
51
|
+
align-items: center;
|
|
52
|
+
}
|
|
53
|
+
.time-picker span {
|
|
54
|
+
font-weight: bold;
|
|
55
|
+
font-size: 18px;
|
|
56
|
+
z-index: 0;
|
|
57
|
+
margin-bottom: 1px;
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const cleanInput = ({ max, pad, fallback }) => {
|
|
2
|
+
return e => {
|
|
3
|
+
if (e.target.value) {
|
|
4
|
+
const value = parseInt(e.target.value)
|
|
5
|
+
if (isNaN(value)) {
|
|
6
|
+
e.target.value = fallback
|
|
7
|
+
} else {
|
|
8
|
+
e.target.value = Math.min(max, value).toString().padStart(pad, "0")
|
|
9
|
+
}
|
|
10
|
+
} else {
|
|
11
|
+
e.target.value = fallback
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import CoreDatePicker from "./DatePicker/DatePicker.svelte"
|
|
3
|
+
import Icon from "../../Icon/Icon.svelte"
|
|
4
|
+
|
|
5
|
+
export let value = null
|
|
6
|
+
export let disabled = false
|
|
7
|
+
export let readonly = false
|
|
8
|
+
export let error = null
|
|
9
|
+
export let appendTo = undefined
|
|
10
|
+
export let ignoreTimezones = false
|
|
11
|
+
|
|
12
|
+
let fromDate
|
|
13
|
+
let toDate
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div class="date-range">
|
|
17
|
+
<CoreDatePicker
|
|
18
|
+
value={fromDate}
|
|
19
|
+
on:change={e => (fromDate = e.detail)}
|
|
20
|
+
enableTime={false}
|
|
21
|
+
/>
|
|
22
|
+
<div class="arrow">
|
|
23
|
+
<Icon name="ChevronRight" />
|
|
24
|
+
</div>
|
|
25
|
+
<CoreDatePicker
|
|
26
|
+
value={toDate}
|
|
27
|
+
on:change={e => (toDate = e.detail)}
|
|
28
|
+
enableTime={false}
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<style>
|
|
33
|
+
.date-range {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: row;
|
|
36
|
+
border: 1px solid var(--spectrum-alias-border-color);
|
|
37
|
+
border-radius: 4px;
|
|
38
|
+
}
|
|
39
|
+
.date-range :global(.spectrum-InputGroup),
|
|
40
|
+
.date-range :global(.spectrum-Textfield),
|
|
41
|
+
.date-range :global(input) {
|
|
42
|
+
min-width: 0 !important;
|
|
43
|
+
width: 150px !important;
|
|
44
|
+
}
|
|
45
|
+
.date-range :global(input) {
|
|
46
|
+
border: none;
|
|
47
|
+
text-align: center;
|
|
48
|
+
}
|
|
49
|
+
.date-range :global(button) {
|
|
50
|
+
display: none;
|
|
51
|
+
}
|
|
52
|
+
.date-range :global(> :first-child input),
|
|
53
|
+
.date-range :global(> :first-child) {
|
|
54
|
+
border-top-right-radius: 0;
|
|
55
|
+
border-bottom-right-radius: 0;
|
|
56
|
+
}
|
|
57
|
+
.date-range :global(> :last-child input),
|
|
58
|
+
.date-range :global(> :last-child) {
|
|
59
|
+
border-top-left-radius: 0;
|
|
60
|
+
border-bottom-left-radius: 0;
|
|
61
|
+
}
|
|
62
|
+
.arrow {
|
|
63
|
+
position: absolute;
|
|
64
|
+
top: 50%;
|
|
65
|
+
left: 50%;
|
|
66
|
+
transform: translateX(-50%) translateY(-50%);
|
|
67
|
+
z-index: 1;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
package/src/Form/Core/index.js
CHANGED
|
@@ -8,7 +8,9 @@ export { default as CoreTextArea } from "./TextArea.svelte"
|
|
|
8
8
|
export { default as CoreCombobox } from "./Combobox.svelte"
|
|
9
9
|
export { default as CoreSwitch } from "./Switch.svelte"
|
|
10
10
|
export { default as CoreSearch } from "./Search.svelte"
|
|
11
|
-
export { default as CoreDatePicker } from "./DatePicker.svelte"
|
|
11
|
+
export { default as CoreDatePicker } from "./DatePicker/DatePicker.svelte"
|
|
12
|
+
export { default as CoreDatePickerPopoverContents } from "./DatePicker/DatePickerPopoverContents.svelte"
|
|
13
|
+
export { default as CoreDateRangePicker } from "./DateRangePicker.svelte"
|
|
12
14
|
export { default as CoreDropzone } from "./Dropzone.svelte"
|
|
13
15
|
export { default as CoreStepper } from "./Stepper.svelte"
|
|
14
16
|
export { default as CoreRichTextField } from "./RichTextField.svelte"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import Field from "./Field.svelte"
|
|
3
|
-
import DatePicker from "./Core/DatePicker.svelte"
|
|
3
|
+
import DatePicker from "./Core/DatePicker/DatePicker.svelte"
|
|
4
4
|
import { createEventDispatcher } from "svelte"
|
|
5
5
|
|
|
6
6
|
export let value = null
|
|
@@ -11,22 +11,15 @@
|
|
|
11
11
|
export let error = null
|
|
12
12
|
export let enableTime = true
|
|
13
13
|
export let timeOnly = false
|
|
14
|
-
export let time24hr = false
|
|
15
14
|
export let placeholder = null
|
|
16
15
|
export let appendTo = undefined
|
|
17
16
|
export let ignoreTimezones = false
|
|
18
|
-
export let range = false
|
|
19
17
|
export let helpText = null
|
|
18
|
+
|
|
20
19
|
const dispatch = createEventDispatcher()
|
|
21
20
|
|
|
22
21
|
const onChange = e => {
|
|
23
|
-
|
|
24
|
-
// Flatpickr cant take two dates and work out what to display, needs to be provided a string.
|
|
25
|
-
// Like - "Date1 to Date2". Hence passing in that specifically from the array
|
|
26
|
-
value = e?.detail[1]
|
|
27
|
-
} else {
|
|
28
|
-
value = e.detail
|
|
29
|
-
}
|
|
22
|
+
value = e.detail
|
|
30
23
|
dispatch("change", e.detail)
|
|
31
24
|
}
|
|
32
25
|
</script>
|
|
@@ -40,10 +33,8 @@
|
|
|
40
33
|
{placeholder}
|
|
41
34
|
{enableTime}
|
|
42
35
|
{timeOnly}
|
|
43
|
-
{time24hr}
|
|
44
36
|
{appendTo}
|
|
45
37
|
{ignoreTimezones}
|
|
46
|
-
{range}
|
|
47
38
|
on:change={onChange}
|
|
48
39
|
/>
|
|
49
40
|
</Field>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Field from "./Field.svelte"
|
|
3
|
+
import DateRangePicker from "./Core/DateRangePicker.svelte"
|
|
4
|
+
import { createEventDispatcher } from "svelte"
|
|
5
|
+
|
|
6
|
+
export let value = null
|
|
7
|
+
export let label = null
|
|
8
|
+
export let labelPosition = "above"
|
|
9
|
+
export let disabled = false
|
|
10
|
+
export let readonly = false
|
|
11
|
+
export let error = null
|
|
12
|
+
export let helpText = null
|
|
13
|
+
export let appendTo = undefined
|
|
14
|
+
export let ignoreTimezones = false
|
|
15
|
+
|
|
16
|
+
const dispatch = createEventDispatcher()
|
|
17
|
+
|
|
18
|
+
const onChange = e => {
|
|
19
|
+
value = e.detail
|
|
20
|
+
dispatch("change", e.detail)
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<Field {helpText} {label} {labelPosition} {error}>
|
|
25
|
+
<DateRangePicker
|
|
26
|
+
{error}
|
|
27
|
+
{disabled}
|
|
28
|
+
{readonly}
|
|
29
|
+
{value}
|
|
30
|
+
{appendTo}
|
|
31
|
+
{ignoreTimezones}
|
|
32
|
+
on:change={onChange}
|
|
33
|
+
/>
|
|
34
|
+
</Field>
|
package/src/Layout/Page.svelte
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
export let narrower = false
|
|
8
8
|
export let noPadding = false
|
|
9
9
|
|
|
10
|
-
let
|
|
10
|
+
let sidePanelVisible = false
|
|
11
11
|
|
|
12
12
|
setContext("side-panel", {
|
|
13
|
-
open: () => (
|
|
14
|
-
close: () => (
|
|
13
|
+
open: () => (sidePanelVisible = true),
|
|
14
|
+
close: () => (sidePanelVisible = false),
|
|
15
15
|
})
|
|
16
16
|
</script>
|
|
17
17
|
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
<div
|
|
26
26
|
id="side-panel"
|
|
27
|
-
class:visible={
|
|
27
|
+
class:visible={sidePanelVisible}
|
|
28
28
|
use:clickOutside={() => {
|
|
29
|
-
|
|
29
|
+
sidePanelVisible = false
|
|
30
30
|
}}
|
|
31
31
|
>
|
|
32
32
|
<slot name="side-panel" />
|
|
@@ -18,13 +18,15 @@
|
|
|
18
18
|
export let open = false
|
|
19
19
|
export let useAnchorWidth = false
|
|
20
20
|
export let dismissible = true
|
|
21
|
-
export let offset =
|
|
21
|
+
export let offset = 4
|
|
22
22
|
export let customHeight
|
|
23
23
|
export let animate = true
|
|
24
24
|
export let customZindex
|
|
25
25
|
export let handlePostionUpdate
|
|
26
26
|
export let showPopover = true
|
|
27
27
|
export let clickOutsideOverride = false
|
|
28
|
+
export let resizable = true
|
|
29
|
+
export let wrap = false
|
|
28
30
|
|
|
29
31
|
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
|
|
30
32
|
|
|
@@ -91,6 +93,8 @@
|
|
|
91
93
|
useAnchorWidth,
|
|
92
94
|
offset,
|
|
93
95
|
customUpdate: handlePostionUpdate,
|
|
96
|
+
resizable,
|
|
97
|
+
wrap,
|
|
94
98
|
}}
|
|
95
99
|
use:clickOutside={{
|
|
96
100
|
callback: dismissible ? handleOutsideClick : () => {},
|
|
@@ -116,12 +120,11 @@
|
|
|
116
120
|
min-width: var(--spectrum-global-dimension-size-2000);
|
|
117
121
|
border-color: var(--spectrum-global-color-gray-300);
|
|
118
122
|
overflow: auto;
|
|
119
|
-
transition: opacity 260ms ease-out
|
|
123
|
+
transition: opacity 260ms ease-out;
|
|
120
124
|
}
|
|
121
125
|
.hidden {
|
|
122
126
|
opacity: 0;
|
|
123
127
|
pointer-events: none;
|
|
124
|
-
transform: translateY(-20px);
|
|
125
128
|
}
|
|
126
129
|
.customZindex {
|
|
127
130
|
z-index: var(--customZindex) !important;
|
package/src/helpers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { helpers } from "@budibase/shared-core"
|
|
2
|
+
import dayjs from "dayjs"
|
|
2
3
|
|
|
3
4
|
export const deepGet = helpers.deepGet
|
|
4
5
|
|
|
@@ -115,3 +116,110 @@ export const copyToClipboard = value => {
|
|
|
115
116
|
}
|
|
116
117
|
})
|
|
117
118
|
}
|
|
119
|
+
|
|
120
|
+
// Parsed a date value. This is usually an ISO string, but can be a
|
|
121
|
+
// bunch of different formats and shapes depending on schema flags.
|
|
122
|
+
export const parseDate = (value, { enableTime = true }) => {
|
|
123
|
+
// If empty then invalid
|
|
124
|
+
if (!value) {
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Certain string values need transformed
|
|
129
|
+
if (typeof value === "string") {
|
|
130
|
+
// Check for time only values
|
|
131
|
+
if (!isNaN(new Date(`0-${value}`))) {
|
|
132
|
+
value = `0-${value}`
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// If date only, check for cases where we received a UTC string
|
|
136
|
+
else if (!enableTime && value.endsWith("Z")) {
|
|
137
|
+
value = value.split("Z")[0]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Parse value and check for validity
|
|
142
|
+
const parsedDate = dayjs(value)
|
|
143
|
+
if (!parsedDate.isValid()) {
|
|
144
|
+
return null
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// By rounding to the nearest second we avoid locking up in an endless
|
|
148
|
+
// loop in the builder, caused by potentially enriching {{ now }} to every
|
|
149
|
+
// millisecond.
|
|
150
|
+
return dayjs(Math.floor(parsedDate.valueOf() / 1000) * 1000)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Stringifies a dayjs object to create an ISO string that respects the various
|
|
154
|
+
// schema flags
|
|
155
|
+
export const stringifyDate = (
|
|
156
|
+
value,
|
|
157
|
+
{ enableTime = true, timeOnly = false, ignoreTimezones = false } = {}
|
|
158
|
+
) => {
|
|
159
|
+
if (!value) {
|
|
160
|
+
return null
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Time only fields always ignore timezones, otherwise they make no sense.
|
|
164
|
+
// For non-timezone-aware fields, create an ISO 8601 timestamp of the exact
|
|
165
|
+
// time picked, without timezone
|
|
166
|
+
const offsetForTimezone = (enableTime && ignoreTimezones) || timeOnly
|
|
167
|
+
if (offsetForTimezone) {
|
|
168
|
+
// Ensure we use the correct offset for the date
|
|
169
|
+
const referenceDate = timeOnly ? new Date() : value.toDate()
|
|
170
|
+
const offset = referenceDate.getTimezoneOffset() * 60000
|
|
171
|
+
return new Date(value.valueOf() - offset).toISOString().slice(0, -1)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// For date-only fields, construct a manual timestamp string without a time
|
|
175
|
+
// or time zone
|
|
176
|
+
else if (!enableTime) {
|
|
177
|
+
const year = value.year()
|
|
178
|
+
const month = `${value.month() + 1}`.padStart(2, "0")
|
|
179
|
+
const day = `${value.date()}`.padStart(2, "0")
|
|
180
|
+
return `${year}-${month}-${day}T00:00:00.000`
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Otherwise use a normal ISO string with time and timezone
|
|
184
|
+
else {
|
|
185
|
+
return value.toISOString()
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Determine the dayjs-compatible format of the browser's default locale
|
|
190
|
+
const getPatternForPart = part => {
|
|
191
|
+
switch (part.type) {
|
|
192
|
+
case "day":
|
|
193
|
+
return "D".repeat(part.value.length)
|
|
194
|
+
case "month":
|
|
195
|
+
return "M".repeat(part.value.length)
|
|
196
|
+
case "year":
|
|
197
|
+
return "Y".repeat(part.value.length)
|
|
198
|
+
case "literal":
|
|
199
|
+
return part.value
|
|
200
|
+
default:
|
|
201
|
+
console.log("Unsupported date part", part)
|
|
202
|
+
return ""
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const localeDateFormat = new Intl.DateTimeFormat()
|
|
206
|
+
.formatToParts(new Date("2021-01-01"))
|
|
207
|
+
.map(getPatternForPart)
|
|
208
|
+
.join("")
|
|
209
|
+
|
|
210
|
+
// Formats a dayjs date according to schema flags
|
|
211
|
+
export const getDateDisplayValue = (
|
|
212
|
+
value,
|
|
213
|
+
{ enableTime = true, timeOnly = false } = {}
|
|
214
|
+
) => {
|
|
215
|
+
if (!value?.isValid()) {
|
|
216
|
+
return ""
|
|
217
|
+
}
|
|
218
|
+
if (timeOnly) {
|
|
219
|
+
return value.format("HH:mm")
|
|
220
|
+
} else if (!enableTime) {
|
|
221
|
+
return value.format(localeDateFormat)
|
|
222
|
+
} else {
|
|
223
|
+
return value.format(`${localeDateFormat} HH:mm`)
|
|
224
|
+
}
|
|
225
|
+
}
|