@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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/bbui",
|
|
3
3
|
"description": "A UI solution used in the different Budibase projects.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.24.1",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"svelte": "src/index.js",
|
|
7
7
|
"module": "dist/bbui.es.js",
|
|
@@ -35,14 +35,15 @@
|
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
|
38
|
-
"@budibase/shared-core": "2.
|
|
39
|
-
"@budibase/string-templates": "2.
|
|
38
|
+
"@budibase/shared-core": "2.24.1",
|
|
39
|
+
"@budibase/string-templates": "2.24.1",
|
|
40
40
|
"@spectrum-css/accordion": "3.0.24",
|
|
41
41
|
"@spectrum-css/actionbutton": "1.0.1",
|
|
42
42
|
"@spectrum-css/actiongroup": "1.0.1",
|
|
43
43
|
"@spectrum-css/avatar": "3.0.2",
|
|
44
44
|
"@spectrum-css/button": "3.0.1",
|
|
45
45
|
"@spectrum-css/buttongroup": "3.0.2",
|
|
46
|
+
"@spectrum-css/calendar": "3.2.7",
|
|
46
47
|
"@spectrum-css/checkbox": "3.0.2",
|
|
47
48
|
"@spectrum-css/dialog": "3.0.1",
|
|
48
49
|
"@spectrum-css/divider": "1.0.3",
|
|
@@ -82,7 +83,6 @@
|
|
|
82
83
|
"dayjs": "^1.10.8",
|
|
83
84
|
"easymde": "^2.16.1",
|
|
84
85
|
"svelte-dnd-action": "^0.9.8",
|
|
85
|
-
"svelte-flatpickr": "3.2.3",
|
|
86
86
|
"svelte-portal": "^1.0.0"
|
|
87
87
|
},
|
|
88
88
|
"resolutions": {
|
|
@@ -103,5 +103,5 @@
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
},
|
|
106
|
-
"gitHead": "
|
|
106
|
+
"gitHead": "5cb6ac027c7f45e174e3545a7399d425822970d8"
|
|
107
107
|
}
|
|
@@ -38,7 +38,15 @@
|
|
|
38
38
|
<div use:getAnchor on:click={openMenu}>
|
|
39
39
|
<slot name="control" />
|
|
40
40
|
</div>
|
|
41
|
-
<Popover
|
|
41
|
+
<Popover
|
|
42
|
+
bind:this={dropdown}
|
|
43
|
+
{anchor}
|
|
44
|
+
{align}
|
|
45
|
+
{portalTarget}
|
|
46
|
+
resizable={false}
|
|
47
|
+
on:open
|
|
48
|
+
on:close
|
|
49
|
+
>
|
|
42
50
|
<Menu>
|
|
43
51
|
<slot />
|
|
44
52
|
</Menu>
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
const ignoredClasses = [
|
|
2
|
-
".flatpickr-calendar",
|
|
3
|
-
".spectrum-Popover",
|
|
4
2
|
".download-js-link",
|
|
3
|
+
".spectrum-Menu",
|
|
4
|
+
".date-time-popover",
|
|
5
|
+
]
|
|
6
|
+
const conditionallyIgnoredClasses = [
|
|
7
|
+
".spectrum-Underlay",
|
|
8
|
+
".drawer-wrapper",
|
|
9
|
+
".spectrum-Popover",
|
|
5
10
|
]
|
|
6
11
|
let clickHandlers = []
|
|
7
12
|
|
|
@@ -9,6 +14,9 @@ let clickHandlers = []
|
|
|
9
14
|
* Handle a body click event
|
|
10
15
|
*/
|
|
11
16
|
const handleClick = event => {
|
|
17
|
+
// Treat right clicks (context menu events) as normal clicks
|
|
18
|
+
const eventType = event.type === "contextmenu" ? "click" : event.type
|
|
19
|
+
|
|
12
20
|
// Ignore click if this is an ignored class
|
|
13
21
|
if (event.target.closest('[data-ignore-click-outside="true"]')) {
|
|
14
22
|
return
|
|
@@ -21,26 +29,23 @@ const handleClick = event => {
|
|
|
21
29
|
|
|
22
30
|
// Process handlers
|
|
23
31
|
clickHandlers.forEach(handler => {
|
|
24
|
-
|
|
32
|
+
// Check that we're the right kind of click event
|
|
33
|
+
if (handler.allowedType && eventType !== handler.allowedType) {
|
|
25
34
|
return
|
|
26
35
|
}
|
|
27
36
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
const clickInModal = event.target.closest(".spectrum-Underlay") != null
|
|
31
|
-
if (clickInModal && !sourceInModal) {
|
|
32
|
-
return
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Ignore clicks for drawers, unless the handler is registered from a drawer
|
|
36
|
-
const sourceInDrawer = handler.anchor.closest(".drawer-wrapper") != null
|
|
37
|
-
const clickInDrawer = event.target.closest(".drawer-wrapper") != null
|
|
38
|
-
if (clickInDrawer && !sourceInDrawer) {
|
|
37
|
+
// Check that the click isn't inside the target
|
|
38
|
+
if (handler.element.contains(event.target)) {
|
|
39
39
|
return
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// Ignore clicks for certain classes unless we're nested inside them
|
|
43
|
+
for (let className of conditionallyIgnoredClasses) {
|
|
44
|
+
const sourceInside = handler.anchor.closest(className) != null
|
|
45
|
+
const clickInside = event.target.closest(className) != null
|
|
46
|
+
if (clickInside && !sourceInside) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
handler.callback?.(event)
|
|
@@ -48,6 +53,7 @@ const handleClick = event => {
|
|
|
48
53
|
}
|
|
49
54
|
document.documentElement.addEventListener("click", handleClick, true)
|
|
50
55
|
document.documentElement.addEventListener("mousedown", handleClick, true)
|
|
56
|
+
document.documentElement.addEventListener("contextmenu", handleClick, true)
|
|
51
57
|
|
|
52
58
|
/**
|
|
53
59
|
* Adds or updates a click handler
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid alignment options are
|
|
3
|
+
* - left
|
|
4
|
+
* - right
|
|
5
|
+
* - left-outside
|
|
6
|
+
* - right-outside
|
|
7
|
+
**/
|
|
8
|
+
|
|
9
|
+
// Strategies are defined as [Popover]To[Anchor].
|
|
10
|
+
// They can apply for both horizontal and vertical alignment.
|
|
11
|
+
const Strategies = {
|
|
12
|
+
StartToStart: "StartToStart", // e.g. left alignment
|
|
13
|
+
EndToEnd: "EndToEnd", // e.g. right alignment
|
|
14
|
+
StartToEnd: "StartToEnd", // e.g. right-outside alignment
|
|
15
|
+
EndToStart: "EndToStart", // e.g. left-outside alignment
|
|
16
|
+
MidPoint: "MidPoint", // centers relative to midpoints
|
|
17
|
+
ScreenEdge: "ScreenEdge", // locks to screen edge
|
|
18
|
+
}
|
|
19
|
+
|
|
1
20
|
export default function positionDropdown(element, opts) {
|
|
2
21
|
let resizeObserver
|
|
3
22
|
let latestOpts = opts
|
|
@@ -19,6 +38,8 @@ export default function positionDropdown(element, opts) {
|
|
|
19
38
|
useAnchorWidth,
|
|
20
39
|
offset = 5,
|
|
21
40
|
customUpdate,
|
|
41
|
+
resizable,
|
|
42
|
+
wrap,
|
|
22
43
|
} = opts
|
|
23
44
|
if (!anchor) {
|
|
24
45
|
return
|
|
@@ -27,56 +48,159 @@ export default function positionDropdown(element, opts) {
|
|
|
27
48
|
// Compute bounds
|
|
28
49
|
const anchorBounds = anchor.getBoundingClientRect()
|
|
29
50
|
const elementBounds = element.getBoundingClientRect()
|
|
51
|
+
const winWidth = window.innerWidth
|
|
52
|
+
const winHeight = window.innerHeight
|
|
53
|
+
const screenOffset = 8
|
|
30
54
|
let styles = {
|
|
31
|
-
maxHeight
|
|
32
|
-
minWidth,
|
|
33
|
-
maxWidth,
|
|
55
|
+
maxHeight,
|
|
56
|
+
minWidth: useAnchorWidth ? anchorBounds.width : minWidth,
|
|
57
|
+
maxWidth: useAnchorWidth ? anchorBounds.width : maxWidth,
|
|
34
58
|
left: null,
|
|
35
59
|
top: null,
|
|
36
60
|
}
|
|
37
61
|
|
|
62
|
+
// Ignore all our logic for custom logic
|
|
38
63
|
if (typeof customUpdate === "function") {
|
|
39
64
|
styles = customUpdate(anchorBounds, elementBounds, {
|
|
40
65
|
...styles,
|
|
41
66
|
offset: opts.offset,
|
|
42
67
|
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Otherwise position ourselves as normal
|
|
71
|
+
else {
|
|
72
|
+
// Checks if we overflow off the screen. We only report that we overflow
|
|
73
|
+
// when the alternative dimension is larger than the one we are checking.
|
|
74
|
+
const doesXOverflow = () => {
|
|
75
|
+
const overflows = styles.left + elementBounds.width > winWidth
|
|
76
|
+
return overflows && anchorBounds.left > winWidth - anchorBounds.right
|
|
77
|
+
}
|
|
78
|
+
const doesYOverflow = () => {
|
|
79
|
+
const overflows = styles.top + elementBounds.height > winHeight
|
|
80
|
+
return overflows && anchorBounds.top > winHeight - anchorBounds.bottom
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Applies a dynamic max height constraint if appropriate
|
|
84
|
+
const applyMaxHeight = height => {
|
|
85
|
+
if (!styles.maxHeight && resizable) {
|
|
86
|
+
styles.maxHeight = height
|
|
51
87
|
}
|
|
52
|
-
} else if (
|
|
53
|
-
window.innerHeight - anchorBounds.bottom <
|
|
54
|
-
(maxHeight || 100)
|
|
55
|
-
) {
|
|
56
|
-
styles.top = anchorBounds.top - elementBounds.height - offset
|
|
57
|
-
styles.maxHeight = maxHeight || 240
|
|
58
|
-
} else {
|
|
59
|
-
styles.top = anchorBounds.bottom + offset
|
|
60
|
-
styles.maxHeight =
|
|
61
|
-
maxHeight || window.innerHeight - anchorBounds.bottom - 20
|
|
62
88
|
}
|
|
63
89
|
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
90
|
+
// Applies the X strategy to our styles
|
|
91
|
+
const applyXStrategy = strategy => {
|
|
92
|
+
switch (strategy) {
|
|
93
|
+
case Strategies.StartToStart:
|
|
94
|
+
default:
|
|
95
|
+
styles.left = anchorBounds.left
|
|
96
|
+
break
|
|
97
|
+
case Strategies.EndToEnd:
|
|
98
|
+
styles.left = anchorBounds.right - elementBounds.width
|
|
99
|
+
break
|
|
100
|
+
case Strategies.StartToEnd:
|
|
101
|
+
styles.left = anchorBounds.right + offset
|
|
102
|
+
break
|
|
103
|
+
case Strategies.EndToStart:
|
|
104
|
+
styles.left = anchorBounds.left - elementBounds.width - offset
|
|
105
|
+
break
|
|
106
|
+
case Strategies.MidPoint:
|
|
107
|
+
styles.left =
|
|
108
|
+
anchorBounds.left +
|
|
109
|
+
anchorBounds.width / 2 -
|
|
110
|
+
elementBounds.width / 2
|
|
111
|
+
break
|
|
112
|
+
case Strategies.ScreenEdge:
|
|
113
|
+
styles.left = winWidth - elementBounds.width - screenOffset
|
|
114
|
+
break
|
|
115
|
+
}
|
|
67
116
|
}
|
|
68
|
-
|
|
69
|
-
|
|
117
|
+
|
|
118
|
+
// Applies the Y strategy to our styles
|
|
119
|
+
const applyYStrategy = strategy => {
|
|
120
|
+
switch (strategy) {
|
|
121
|
+
case Strategies.StartToStart:
|
|
122
|
+
styles.top = anchorBounds.top
|
|
123
|
+
applyMaxHeight(winHeight - anchorBounds.top - screenOffset)
|
|
124
|
+
break
|
|
125
|
+
case Strategies.EndToEnd:
|
|
126
|
+
styles.top = anchorBounds.bottom - elementBounds.height
|
|
127
|
+
applyMaxHeight(anchorBounds.bottom - screenOffset)
|
|
128
|
+
break
|
|
129
|
+
case Strategies.StartToEnd:
|
|
130
|
+
default:
|
|
131
|
+
styles.top = anchorBounds.bottom + offset
|
|
132
|
+
applyMaxHeight(winHeight - anchorBounds.bottom - screenOffset)
|
|
133
|
+
break
|
|
134
|
+
case Strategies.EndToStart:
|
|
135
|
+
styles.top = anchorBounds.top - elementBounds.height - offset
|
|
136
|
+
applyMaxHeight(anchorBounds.top - screenOffset)
|
|
137
|
+
break
|
|
138
|
+
case Strategies.MidPoint:
|
|
139
|
+
styles.top =
|
|
140
|
+
anchorBounds.top +
|
|
141
|
+
anchorBounds.height / 2 -
|
|
142
|
+
elementBounds.height / 2
|
|
143
|
+
break
|
|
144
|
+
case Strategies.ScreenEdge:
|
|
145
|
+
styles.top = winHeight - elementBounds.height - screenOffset
|
|
146
|
+
applyMaxHeight(winHeight - 2 * screenOffset)
|
|
147
|
+
break
|
|
148
|
+
}
|
|
70
149
|
}
|
|
150
|
+
|
|
151
|
+
// Determine X strategy
|
|
71
152
|
if (align === "right") {
|
|
72
|
-
|
|
73
|
-
anchorBounds.left + anchorBounds.width - elementBounds.width
|
|
153
|
+
applyXStrategy(Strategies.EndToEnd)
|
|
74
154
|
} else if (align === "right-outside") {
|
|
75
|
-
|
|
155
|
+
applyXStrategy(Strategies.StartToEnd)
|
|
76
156
|
} else if (align === "left-outside") {
|
|
77
|
-
|
|
157
|
+
applyXStrategy(Strategies.EndToStart)
|
|
158
|
+
} else {
|
|
159
|
+
applyXStrategy(Strategies.StartToStart)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Determine Y strategy
|
|
163
|
+
if (align === "right-outside" || align === "left-outside") {
|
|
164
|
+
applyYStrategy(Strategies.MidPoint)
|
|
78
165
|
} else {
|
|
79
|
-
|
|
166
|
+
applyYStrategy(Strategies.StartToEnd)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Handle screen overflow
|
|
170
|
+
if (doesXOverflow()) {
|
|
171
|
+
// Swap left to right
|
|
172
|
+
if (align === "left") {
|
|
173
|
+
applyXStrategy(Strategies.EndToEnd)
|
|
174
|
+
}
|
|
175
|
+
// Swap right-outside to left-outside
|
|
176
|
+
else if (align === "right-outside") {
|
|
177
|
+
applyXStrategy(Strategies.EndToStart)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (doesYOverflow()) {
|
|
181
|
+
// If wrapping, lock to the bottom of the screen and also reposition to
|
|
182
|
+
// the side to not block the anchor
|
|
183
|
+
if (wrap) {
|
|
184
|
+
applyYStrategy(Strategies.MidPoint)
|
|
185
|
+
if (doesYOverflow()) {
|
|
186
|
+
applyYStrategy(Strategies.ScreenEdge)
|
|
187
|
+
}
|
|
188
|
+
applyXStrategy(Strategies.StartToEnd)
|
|
189
|
+
if (doesXOverflow()) {
|
|
190
|
+
applyXStrategy(Strategies.EndToStart)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Othewise invert as normal
|
|
194
|
+
else {
|
|
195
|
+
// If using an outside strategy then lock to the bottom of the screen
|
|
196
|
+
if (align === "left-outside" || align === "right-outside") {
|
|
197
|
+
applyYStrategy(Strategies.ScreenEdge)
|
|
198
|
+
}
|
|
199
|
+
// Otherwise flip above
|
|
200
|
+
else {
|
|
201
|
+
applyYStrategy(Strategies.EndToStart)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
80
204
|
}
|
|
81
205
|
}
|
|
82
206
|
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { cleanInput } from "./utils"
|
|
3
|
+
import Select from "../../Select.svelte"
|
|
4
|
+
import dayjs from "dayjs"
|
|
5
|
+
import NumberInput from "./NumberInput.svelte"
|
|
6
|
+
import { createEventDispatcher } from "svelte"
|
|
7
|
+
import isoWeek from "dayjs/plugin/isoWeek"
|
|
8
|
+
|
|
9
|
+
dayjs.extend(isoWeek)
|
|
10
|
+
|
|
11
|
+
export let value
|
|
12
|
+
|
|
13
|
+
const dispatch = createEventDispatcher()
|
|
14
|
+
const DaysOfWeek = [
|
|
15
|
+
"Monday",
|
|
16
|
+
"Tuesday",
|
|
17
|
+
"Wednesday",
|
|
18
|
+
"Thursday",
|
|
19
|
+
"Friday",
|
|
20
|
+
"Saturday",
|
|
21
|
+
"Sunday",
|
|
22
|
+
]
|
|
23
|
+
const MonthsOfYear = [
|
|
24
|
+
"January",
|
|
25
|
+
"February",
|
|
26
|
+
"March",
|
|
27
|
+
"April",
|
|
28
|
+
"May",
|
|
29
|
+
"June",
|
|
30
|
+
"July",
|
|
31
|
+
"August",
|
|
32
|
+
"September",
|
|
33
|
+
"October",
|
|
34
|
+
"November",
|
|
35
|
+
"December",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
const now = dayjs()
|
|
39
|
+
let calendarDate
|
|
40
|
+
|
|
41
|
+
$: calendarDate = dayjs(value || dayjs()).startOf("month")
|
|
42
|
+
$: mondays = getMondays(calendarDate)
|
|
43
|
+
|
|
44
|
+
const getMondays = monthStart => {
|
|
45
|
+
if (!monthStart?.isValid()) {
|
|
46
|
+
return []
|
|
47
|
+
}
|
|
48
|
+
let monthEnd = monthStart.endOf("month")
|
|
49
|
+
let calendarStart = monthStart.startOf("isoWeek")
|
|
50
|
+
const numWeeks = Math.ceil((monthEnd.diff(calendarStart, "day") + 1) / 7)
|
|
51
|
+
|
|
52
|
+
let mondays = []
|
|
53
|
+
for (let i = 0; i < numWeeks; i++) {
|
|
54
|
+
mondays.push(calendarStart.add(i, "weeks"))
|
|
55
|
+
}
|
|
56
|
+
return mondays
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const handleCalendarYearChange = e => {
|
|
60
|
+
calendarDate = calendarDate.year(parseInt(e.target.value))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const handleDateChange = date => {
|
|
64
|
+
const base = value || now
|
|
65
|
+
dispatch(
|
|
66
|
+
"change",
|
|
67
|
+
base.year(date.year()).month(date.month()).date(date.date())
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const setDate = date => {
|
|
72
|
+
calendarDate = date
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const cleanYear = cleanInput({ max: 9999, pad: 0, fallback: now.year() })
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<div class="spectrum-Calendar">
|
|
79
|
+
<div class="spectrum-Calendar-header">
|
|
80
|
+
<div
|
|
81
|
+
class="spectrum-Calendar-title"
|
|
82
|
+
aria-live="assertive"
|
|
83
|
+
aria-atomic="true"
|
|
84
|
+
>
|
|
85
|
+
<div class="month-selector">
|
|
86
|
+
<Select
|
|
87
|
+
autoWidth
|
|
88
|
+
placeholder={null}
|
|
89
|
+
options={MonthsOfYear.map((m, idx) => ({ label: m, value: idx }))}
|
|
90
|
+
value={calendarDate.month()}
|
|
91
|
+
on:change={e => (calendarDate = calendarDate.month(e.detail))}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
<NumberInput
|
|
95
|
+
value={calendarDate.year()}
|
|
96
|
+
min={0}
|
|
97
|
+
max={9999}
|
|
98
|
+
width={64}
|
|
99
|
+
on:change={handleCalendarYearChange}
|
|
100
|
+
on:input={cleanYear}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
<button
|
|
104
|
+
aria-label="Previous"
|
|
105
|
+
title="Previous"
|
|
106
|
+
class="spectrum-ActionButton spectrum-ActionButton--quiet spectrum-Calendar-prevMonth"
|
|
107
|
+
on:click={() => (calendarDate = calendarDate.subtract(1, "month"))}
|
|
108
|
+
>
|
|
109
|
+
<svg
|
|
110
|
+
class="spectrum-Icon spectrum-UIIcon-ChevronLeft100"
|
|
111
|
+
focusable="false"
|
|
112
|
+
aria-hidden="true"
|
|
113
|
+
>
|
|
114
|
+
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
|
115
|
+
</svg>
|
|
116
|
+
</button>
|
|
117
|
+
<button
|
|
118
|
+
aria-label="Next"
|
|
119
|
+
title="Next"
|
|
120
|
+
class="spectrum-ActionButton spectrum-ActionButton--quiet spectrum-Calendar-nextMonth"
|
|
121
|
+
on:click={() => (calendarDate = calendarDate.add(1, "month"))}
|
|
122
|
+
>
|
|
123
|
+
<svg
|
|
124
|
+
class="spectrum-Icon spectrum-UIIcon-ChevronRight100"
|
|
125
|
+
focusable="false"
|
|
126
|
+
aria-hidden="true"
|
|
127
|
+
>
|
|
128
|
+
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
|
129
|
+
</svg>
|
|
130
|
+
</button>
|
|
131
|
+
</div>
|
|
132
|
+
<div
|
|
133
|
+
class="spectrum-Calendar-body"
|
|
134
|
+
aria-readonly="true"
|
|
135
|
+
aria-disabled="false"
|
|
136
|
+
>
|
|
137
|
+
<table role="presentation" class="spectrum-Calendar-table">
|
|
138
|
+
<thead role="presentation">
|
|
139
|
+
<tr>
|
|
140
|
+
{#each DaysOfWeek as day}
|
|
141
|
+
<th scope="col" class="spectrum-Calendar-tableCell">
|
|
142
|
+
<abbr class="spectrum-Calendar-dayOfWeek" title={day}>
|
|
143
|
+
{day[0]}
|
|
144
|
+
</abbr>
|
|
145
|
+
</th>
|
|
146
|
+
{/each}
|
|
147
|
+
</tr>
|
|
148
|
+
</thead>
|
|
149
|
+
<tbody role="presentation">
|
|
150
|
+
{#each mondays as monday}
|
|
151
|
+
<tr>
|
|
152
|
+
{#each [0, 1, 2, 3, 4, 5, 6] as dayOffset}
|
|
153
|
+
{@const date = monday.add(dayOffset, "days")}
|
|
154
|
+
{@const outsideMonth = date.month() !== calendarDate.month()}
|
|
155
|
+
<td
|
|
156
|
+
class="spectrum-Calendar-tableCell"
|
|
157
|
+
aria-disabled="true"
|
|
158
|
+
aria-selected="false"
|
|
159
|
+
aria-invalid="false"
|
|
160
|
+
title={date.format("dddd, MMMM D, YYYY")}
|
|
161
|
+
on:click={() => handleDateChange(date)}
|
|
162
|
+
>
|
|
163
|
+
<span
|
|
164
|
+
role="presentation"
|
|
165
|
+
class="spectrum-Calendar-date"
|
|
166
|
+
class:is-outsideMonth={outsideMonth}
|
|
167
|
+
class:is-today={!outsideMonth && date.isSame(now, "day")}
|
|
168
|
+
class:is-selected={date.isSame(value, "day")}
|
|
169
|
+
>
|
|
170
|
+
{date.date()}
|
|
171
|
+
</span>
|
|
172
|
+
</td>
|
|
173
|
+
{/each}
|
|
174
|
+
</tr>
|
|
175
|
+
{/each}
|
|
176
|
+
</tbody>
|
|
177
|
+
</table>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<style>
|
|
182
|
+
/* Calendar overrides */
|
|
183
|
+
.spectrum-Calendar {
|
|
184
|
+
width: auto;
|
|
185
|
+
}
|
|
186
|
+
.spectrum-Calendar-header {
|
|
187
|
+
width: auto;
|
|
188
|
+
}
|
|
189
|
+
.spectrum-Calendar-title {
|
|
190
|
+
display: flex;
|
|
191
|
+
justify-content: flex-start;
|
|
192
|
+
align-items: stretch;
|
|
193
|
+
flex: 1 1 auto;
|
|
194
|
+
}
|
|
195
|
+
.spectrum-Calendar-header button {
|
|
196
|
+
border-radius: 4px;
|
|
197
|
+
}
|
|
198
|
+
.spectrum-Calendar-date.is-outsideMonth {
|
|
199
|
+
visibility: visible;
|
|
200
|
+
color: var(--spectrum-global-color-gray-400);
|
|
201
|
+
}
|
|
202
|
+
.spectrum-Calendar-date.is-today,
|
|
203
|
+
.spectrum-Calendar-date.is-today::before {
|
|
204
|
+
border-color: var(--spectrum-global-color-gray-400);
|
|
205
|
+
}
|
|
206
|
+
.spectrum-Calendar-date.is-today.is-selected,
|
|
207
|
+
.spectrum-Calendar-date.is-today.is-selected::before {
|
|
208
|
+
border-color: var(
|
|
209
|
+
--primaryColorHover,
|
|
210
|
+
var(--spectrum-global-color-blue-700)
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
.spectrum-Calendar-date.is-selected:not(.is-range-selection) {
|
|
214
|
+
background: var(--primaryColor, var(--spectrum-global-color-blue-400));
|
|
215
|
+
}
|
|
216
|
+
.spectrum-Calendar tr {
|
|
217
|
+
box-sizing: content-box;
|
|
218
|
+
height: 40px;
|
|
219
|
+
}
|
|
220
|
+
.spectrum-Calendar-tableCell {
|
|
221
|
+
box-sizing: content-box;
|
|
222
|
+
}
|
|
223
|
+
.spectrum-Calendar-nextMonth,
|
|
224
|
+
.spectrum-Calendar-prevMonth {
|
|
225
|
+
order: 1;
|
|
226
|
+
padding: 4px;
|
|
227
|
+
}
|
|
228
|
+
.spectrum-Calendar-date {
|
|
229
|
+
color: var(--spectrum-alias-text-color);
|
|
230
|
+
}
|
|
231
|
+
.spectrum-Calendar-date.is-selected {
|
|
232
|
+
color: white;
|
|
233
|
+
}
|
|
234
|
+
.spectrum-Calendar-dayOfWeek {
|
|
235
|
+
color: var(--spectrum-global-color-gray-600);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Style select */
|
|
239
|
+
.month-selector :global(.spectrum-Picker) {
|
|
240
|
+
background: none;
|
|
241
|
+
border: none;
|
|
242
|
+
padding: 4px 6px;
|
|
243
|
+
}
|
|
244
|
+
.month-selector :global(.spectrum-Picker:hover),
|
|
245
|
+
.month-selector :global(.spectrum-Picker.is-open) {
|
|
246
|
+
background: var(--spectrum-global-color-gray-200);
|
|
247
|
+
}
|
|
248
|
+
.month-selector :global(.spectrum-Picker-label) {
|
|
249
|
+
font-size: 18px;
|
|
250
|
+
font-weight: bold;
|
|
251
|
+
}
|
|
252
|
+
</style>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import Icon from "../../../Icon/Icon.svelte"
|
|
3
|
+
import { getDateDisplayValue } from "../../../helpers"
|
|
4
|
+
|
|
5
|
+
export let anchor
|
|
6
|
+
export let disabled
|
|
7
|
+
export let readonly
|
|
8
|
+
export let error
|
|
9
|
+
export let focused
|
|
10
|
+
export let placeholder
|
|
11
|
+
export let id
|
|
12
|
+
export let value
|
|
13
|
+
export let icon
|
|
14
|
+
export let enableTime
|
|
15
|
+
export let timeOnly
|
|
16
|
+
|
|
17
|
+
$: displayValue = getDateDisplayValue(value, { enableTime, timeOnly })
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
21
|
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
22
|
+
<div
|
|
23
|
+
bind:this={anchor}
|
|
24
|
+
class:is-disabled={disabled || readonly}
|
|
25
|
+
class:is-invalid={!!error}
|
|
26
|
+
class:is-focused={focused}
|
|
27
|
+
class="spectrum-InputGroup spectrum-Datepicker"
|
|
28
|
+
aria-readonly={readonly}
|
|
29
|
+
aria-required="false"
|
|
30
|
+
aria-haspopup="true"
|
|
31
|
+
on:click
|
|
32
|
+
>
|
|
33
|
+
<div
|
|
34
|
+
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
|
35
|
+
class:is-disabled={disabled}
|
|
36
|
+
class:is-invalid={!!error}
|
|
37
|
+
>
|
|
38
|
+
{#if !!error}
|
|
39
|
+
<svg
|
|
40
|
+
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
|
41
|
+
focusable="false"
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
>
|
|
44
|
+
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
45
|
+
</svg>
|
|
46
|
+
{/if}
|
|
47
|
+
<input
|
|
48
|
+
{disabled}
|
|
49
|
+
{readonly}
|
|
50
|
+
data-input
|
|
51
|
+
type="text"
|
|
52
|
+
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
|
53
|
+
class:is-disabled={disabled}
|
|
54
|
+
{placeholder}
|
|
55
|
+
{id}
|
|
56
|
+
value={displayValue}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
{#if !disabled && !readonly}
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
|
|
63
|
+
tabindex="-1"
|
|
64
|
+
class:is-invalid={!!error}
|
|
65
|
+
>
|
|
66
|
+
<Icon name={icon} />
|
|
67
|
+
</button>
|
|
68
|
+
{/if}
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<style>
|
|
72
|
+
/* Date label overrides */
|
|
73
|
+
.spectrum-Textfield-input {
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
}
|
|
76
|
+
.spectrum-Textfield:not(.is-disabled):hover {
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
}
|
|
79
|
+
.spectrum-Datepicker {
|
|
80
|
+
width: 100%;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
}
|
|
83
|
+
.spectrum-Datepicker .spectrum-Textfield {
|
|
84
|
+
width: 100%;
|
|
85
|
+
}
|
|
86
|
+
.is-disabled {
|
|
87
|
+
pointer-events: none !important;
|
|
88
|
+
}
|
|
89
|
+
input:read-only {
|
|
90
|
+
border-right-width: 1px;
|
|
91
|
+
border-top-right-radius: var(--spectrum-textfield-border-radius);
|
|
92
|
+
border-bottom-right-radius: var(--spectrum-textfield-border-radius);
|
|
93
|
+
}
|
|
94
|
+
</style>
|