@budibase/bbui 3.42.0 → 3.43.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.
- package/dist/bbui.mjs +765 -41106
- package/package.json +3 -3
- package/src/Drawer/Drawer.svelte +2 -2
- package/src/Form/Core/DatePicker/DatePickerPopoverContents.svelte +5 -3
- package/src/Form/Core/DatePicker/utils.spec.ts +53 -0
- package/src/Form/Core/DatePicker/utils.ts +53 -55
- package/src/Form/Dropzone.svelte +2 -0
- package/src/Modal/Modal.svelte +2 -2
- package/src/Switcher/Switcher.svelte +20 -5
- package/src/index.ts +1 -1
- package/src/utils/ids.test.ts +13 -0
- package/src/utils/ids.ts +5 -0
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": "v3.
|
|
4
|
+
"version": "v3.43.0",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"module": "dist/bbui.mjs",
|
|
7
7
|
"exports": {
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
"@spectrum-css/underlay": "2.0.9",
|
|
76
76
|
"@spectrum-css/vars": "3.0.1",
|
|
77
77
|
"atrament": "^4.3.0",
|
|
78
|
-
"date-fns": "^4.1.0",
|
|
79
78
|
"dayjs": "^1.10.8",
|
|
80
79
|
"easymde": "^2.16.1",
|
|
80
|
+
"nanoid": "6.0.1",
|
|
81
81
|
"sanitize-html": "^2.13.0",
|
|
82
82
|
"svelte-portal": "^2.2.1"
|
|
83
83
|
},
|
|
@@ -108,5 +108,5 @@
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
},
|
|
111
|
-
"gitHead": "
|
|
111
|
+
"gitHead": "221c199a898fffd9c8803d5327cc3089cc02b7f3"
|
|
112
112
|
}
|
package/src/Drawer/Drawer.svelte
CHANGED
|
@@ -66,7 +66,6 @@
|
|
|
66
66
|
</script>
|
|
67
67
|
|
|
68
68
|
<script lang="ts">
|
|
69
|
-
import { generate } from "shortid"
|
|
70
69
|
import { createEventDispatcher, onDestroy, setContext } from "svelte"
|
|
71
70
|
import type { TransitionConfig } from "svelte/transition"
|
|
72
71
|
import Portal from "svelte-portal"
|
|
@@ -79,6 +78,7 @@
|
|
|
79
78
|
overlayStack,
|
|
80
79
|
BASE_Z_INDEX,
|
|
81
80
|
} from "../Modal/overlayStack"
|
|
81
|
+
import { generateId } from "../utils/ids"
|
|
82
82
|
|
|
83
83
|
interface DrawerEvents {
|
|
84
84
|
drawerShow: string
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
const spacing = 11
|
|
94
94
|
|
|
95
95
|
let visible: boolean = false
|
|
96
|
-
let drawerId: string =
|
|
96
|
+
let drawerId: string = generateId()
|
|
97
97
|
let depth: number = 0
|
|
98
98
|
let stackIndex: number = -1
|
|
99
99
|
let computedZIndex: number = BASE_Z_INDEX
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { stringifyDate } from "../../../helpers"
|
|
7
7
|
import Calendar from "./Calendar.svelte"
|
|
8
8
|
import TimePicker from "./TimePicker.svelte"
|
|
9
|
-
import type
|
|
9
|
+
import { getLocaleStartDayOfWeek, type Weekday } from "./utils"
|
|
10
10
|
|
|
11
11
|
export let useKeyboardShortcuts = true
|
|
12
12
|
export let ignoreTimezones = false
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
export let timeOnly = false
|
|
15
15
|
export let setTimeTo: string | undefined = undefined
|
|
16
16
|
export let value: Dayjs | null | undefined = null
|
|
17
|
-
|
|
17
|
+
const browserStartDayOfWeek = getLocaleStartDayOfWeek()
|
|
18
|
+
export let startDayOfWeek: Weekday | undefined = undefined
|
|
18
19
|
export let calendarLabels = resolveTranslationGroup("calendar")
|
|
19
20
|
|
|
20
21
|
const dispatch = createEventDispatcher<{ change: string | null }>()
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
|
|
23
24
|
$: showCalendar = !timeOnly
|
|
24
25
|
$: showTime = enableTime || timeOnly
|
|
26
|
+
$: resolvedStartDayOfWeek = startDayOfWeek ?? browserStartDayOfWeek
|
|
25
27
|
|
|
26
28
|
const setToNow = () => {
|
|
27
29
|
const now = dayjs().second(0).millisecond(0)
|
|
@@ -65,7 +67,7 @@
|
|
|
65
67
|
{#if showCalendar}
|
|
66
68
|
<Calendar
|
|
67
69
|
{value}
|
|
68
|
-
{
|
|
70
|
+
startDayOfWeek={resolvedStartDayOfWeek}
|
|
69
71
|
{calendarLabels}
|
|
70
72
|
on:change={e => handleChange(e.detail)}
|
|
71
73
|
bind:this={calendar}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest"
|
|
2
|
+
import { getLocaleStartDayOfWeek } from "./utils"
|
|
3
|
+
|
|
4
|
+
interface LocaleWithWeekInfo {
|
|
5
|
+
readonly weekInfo?: { firstDay?: number }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
describe("getLocaleStartDayOfWeek", () => {
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
vi.restoreAllMocks()
|
|
11
|
+
vi.unstubAllGlobals()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it("uses the locale's first day of the week", () => {
|
|
15
|
+
expect(getLocaleStartDayOfWeek(["en-US"])).toBe("Sunday")
|
|
16
|
+
expect(getLocaleStartDayOfWeek(["en-GB"])).toBe("Monday")
|
|
17
|
+
expect(getLocaleStartDayOfWeek(["ar-AF"])).toBe("Saturday")
|
|
18
|
+
expect(getLocaleStartDayOfWeek(["dv-MV"])).toBe("Friday")
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it("normalizes locale separators", () => {
|
|
22
|
+
expect(getLocaleStartDayOfWeek(["en_US"])).toBe("Sunday")
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it("uses the browser's preferred locales by default", () => {
|
|
26
|
+
vi.stubGlobal("navigator", {
|
|
27
|
+
language: "en-US",
|
|
28
|
+
languages: ["en-US"],
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
expect(getLocaleStartDayOfWeek()).toBe("Sunday")
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("uses the next valid locale", () => {
|
|
35
|
+
expect(getLocaleStartDayOfWeek(["invalid--locale", "en-US"])).toBe("Sunday")
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it("uses region data when locale week information is unavailable", () => {
|
|
39
|
+
const localePrototype: Intl.Locale & LocaleWithWeekInfo =
|
|
40
|
+
Intl.Locale.prototype
|
|
41
|
+
vi.spyOn(localePrototype, "weekInfo", "get").mockReturnValue(undefined)
|
|
42
|
+
|
|
43
|
+
expect(getLocaleStartDayOfWeek(["en-US"])).toBe("Sunday")
|
|
44
|
+
expect(getLocaleStartDayOfWeek(["ar-AF"])).toBe("Saturday")
|
|
45
|
+
expect(getLocaleStartDayOfWeek(["dv-MV"])).toBe("Friday")
|
|
46
|
+
expect(getLocaleStartDayOfWeek(["en-GB"])).toBe("Monday")
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("falls back to Monday without supported locale metadata", () => {
|
|
50
|
+
expect(getLocaleStartDayOfWeek([])).toBe("Monday")
|
|
51
|
+
expect(getLocaleStartDayOfWeek(["invalid--locale"])).toBe("Monday")
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import type { Locale } from "date-fns"
|
|
2
|
-
import * as dateFnsLocales from "date-fns/locale"
|
|
3
|
-
|
|
4
1
|
interface Input {
|
|
5
2
|
max: number
|
|
6
3
|
pad: number
|
|
7
4
|
fallback: string
|
|
8
5
|
}
|
|
9
6
|
|
|
7
|
+
interface LocaleWeekInfo {
|
|
8
|
+
firstDay?: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface LocaleWithWeekInfo {
|
|
12
|
+
readonly weekInfo?: LocaleWeekInfo
|
|
13
|
+
getWeekInfo?: () => LocaleWeekInfo
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
export type Weekday =
|
|
11
17
|
| "Sunday"
|
|
12
18
|
| "Monday"
|
|
@@ -27,6 +33,19 @@ const WEEKDAY_BY_INDEX: Weekday[] = [
|
|
|
27
33
|
"Saturday",
|
|
28
34
|
]
|
|
29
35
|
|
|
36
|
+
// Fallback data for browsers without Intl.Locale week information support.
|
|
37
|
+
// Regions not listed use CLDR's global Monday default.
|
|
38
|
+
// Source: unicode-org/cldr-json/cldr-json/cldr-core/supplemental/weekData.json
|
|
39
|
+
const SUNDAY_START_REGIONS = new Set(
|
|
40
|
+
"AG AS BD BR BS BT BW BZ CA CO DM DO ET GT GU HK HN ID IL IN IS JM JP KE KH KR LA MH MM MO MT MX MZ NI NP PA PE PH PK PR PT SA SG SV TH TT TW UM US VE VI WS YE ZA ZW".split(
|
|
41
|
+
" "
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
const SATURDAY_START_REGIONS = new Set(
|
|
45
|
+
"AF BH DJ DZ EG IQ IR JO KW LY OM QA SD SY".split(" ")
|
|
46
|
+
)
|
|
47
|
+
const FRIDAY_START_REGIONS = new Set(["MV"])
|
|
48
|
+
|
|
30
49
|
const normalizeLocaleCode = (code?: string | null) => {
|
|
31
50
|
if (!code) {
|
|
32
51
|
return null
|
|
@@ -34,52 +53,6 @@ const normalizeLocaleCode = (code?: string | null) => {
|
|
|
34
53
|
return code.toLowerCase().replace(/_/g, "-")
|
|
35
54
|
}
|
|
36
55
|
|
|
37
|
-
const normalizeLocaleKey = (key?: string) => {
|
|
38
|
-
if (!key) {
|
|
39
|
-
return null
|
|
40
|
-
}
|
|
41
|
-
return key
|
|
42
|
-
.replace(/_/g, "-")
|
|
43
|
-
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
|
44
|
-
.toLowerCase()
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const localeLookup = (() => {
|
|
48
|
-
const lookup = new Map<string, Locale>()
|
|
49
|
-
|
|
50
|
-
const register = (key: string, locale: Locale | undefined) => {
|
|
51
|
-
if (!locale) {
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
const codeCandidates = new Set<string>()
|
|
55
|
-
const normalizedKey = normalizeLocaleKey(key)
|
|
56
|
-
if (normalizedKey) {
|
|
57
|
-
codeCandidates.add(normalizedKey)
|
|
58
|
-
}
|
|
59
|
-
const normalizedLocaleCode = normalizeLocaleCode(locale.code)
|
|
60
|
-
if (normalizedLocaleCode) {
|
|
61
|
-
codeCandidates.add(normalizedLocaleCode)
|
|
62
|
-
}
|
|
63
|
-
for (const candidate of codeCandidates) {
|
|
64
|
-
if (!lookup.has(candidate)) {
|
|
65
|
-
lookup.set(candidate, locale)
|
|
66
|
-
}
|
|
67
|
-
const base = candidate.split("-")[0]
|
|
68
|
-
if (base && !lookup.has(base)) {
|
|
69
|
-
lookup.set(base, locale)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
for (const [key, locale] of Object.entries(dateFnsLocales)) {
|
|
75
|
-
if (typeof locale === "object" && locale) {
|
|
76
|
-
register(key, locale as Locale)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return lookup
|
|
81
|
-
})()
|
|
82
|
-
|
|
83
56
|
const getNavigatorLocales = (): readonly string[] => {
|
|
84
57
|
if (typeof navigator === "undefined") {
|
|
85
58
|
return []
|
|
@@ -90,6 +63,34 @@ const getNavigatorLocales = (): readonly string[] => {
|
|
|
90
63
|
return navigator.language ? [navigator.language] : []
|
|
91
64
|
}
|
|
92
65
|
|
|
66
|
+
const getFallbackFirstDay = (locale: Intl.Locale): number => {
|
|
67
|
+
const region = locale.region ?? locale.maximize().region
|
|
68
|
+
if (region && SUNDAY_START_REGIONS.has(region)) {
|
|
69
|
+
return 7
|
|
70
|
+
}
|
|
71
|
+
if (region && SATURDAY_START_REGIONS.has(region)) {
|
|
72
|
+
return 6
|
|
73
|
+
}
|
|
74
|
+
if (region && FRIDAY_START_REGIONS.has(region)) {
|
|
75
|
+
return 5
|
|
76
|
+
}
|
|
77
|
+
return 1
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const getLocaleFirstDay = (code: string): number | undefined => {
|
|
81
|
+
if (typeof Intl.Locale !== "function") {
|
|
82
|
+
return undefined
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const locale = new Intl.Locale(code) as Intl.Locale & LocaleWithWeekInfo
|
|
87
|
+
const weekInfo = locale.weekInfo ?? locale.getWeekInfo?.()
|
|
88
|
+
return weekInfo?.firstDay ?? getFallbackFirstDay(locale)
|
|
89
|
+
} catch {
|
|
90
|
+
return undefined
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
93
94
|
export const getLocaleStartDayOfWeek = (
|
|
94
95
|
locales: readonly string[] = getNavigatorLocales()
|
|
95
96
|
): Weekday => {
|
|
@@ -98,12 +99,9 @@ export const getLocaleStartDayOfWeek = (
|
|
|
98
99
|
if (!normalized) {
|
|
99
100
|
continue
|
|
100
101
|
}
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (typeof weekStartsOn === "number") {
|
|
105
|
-
const index = ((weekStartsOn % 7) + 7) % 7
|
|
106
|
-
return WEEKDAY_BY_INDEX[index]
|
|
102
|
+
const firstDay = getLocaleFirstDay(normalized)
|
|
103
|
+
if (typeof firstDay === "number" && firstDay >= 1 && firstDay <= 7) {
|
|
104
|
+
return WEEKDAY_BY_INDEX[firstDay % 7]
|
|
107
105
|
}
|
|
108
106
|
}
|
|
109
107
|
return DEFAULT_WEEKDAY
|
package/src/Form/Dropzone.svelte
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
undefined
|
|
23
23
|
export let gallery: boolean = true
|
|
24
24
|
export let fileTags: string[] = []
|
|
25
|
+
export let extensions: string = "*"
|
|
25
26
|
export let maximum: number | undefined = undefined
|
|
26
27
|
export let compact: boolean = false
|
|
27
28
|
export let helpText: string | undefined = undefined
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
{handleTooManyFiles}
|
|
45
46
|
{gallery}
|
|
46
47
|
{fileTags}
|
|
48
|
+
{extensions}
|
|
47
49
|
{maximum}
|
|
48
50
|
{compact}
|
|
49
51
|
on:change={onChange}
|
package/src/Modal/Modal.svelte
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
import Portal from "svelte-portal"
|
|
22
22
|
import Context from "../context"
|
|
23
23
|
import { ModalCancelFrom } from "../constants"
|
|
24
|
-
import { generate } from "shortid"
|
|
25
24
|
import {
|
|
26
25
|
BASE_Z_INDEX,
|
|
27
26
|
overlayStack,
|
|
@@ -29,6 +28,7 @@
|
|
|
29
28
|
removeOverlay,
|
|
30
29
|
isActiveOverlay,
|
|
31
30
|
} from "./overlayStack"
|
|
31
|
+
import { generateId } from "../utils/ids"
|
|
32
32
|
|
|
33
33
|
export let fixed: boolean = false
|
|
34
34
|
export let inline: boolean = false
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
// Ensure any popovers inside this modal are rendered inside this modal
|
|
46
46
|
// Unique ids are required to ensure nested modals are parented correctly
|
|
47
|
-
const uniqueId =
|
|
47
|
+
const uniqueId = generateId()
|
|
48
48
|
const modalId = uniqueId
|
|
49
49
|
setContext(Context.PopoverRoot, `.spectrum-Modal-${uniqueId}`)
|
|
50
50
|
|
|
@@ -3,16 +3,19 @@
|
|
|
3
3
|
import AbsTooltip from "../Tooltip/AbsTooltip.svelte"
|
|
4
4
|
import ActionButton from "../ActionButton/ActionButton.svelte"
|
|
5
5
|
|
|
6
|
-
export let leftIcon: string
|
|
6
|
+
export let leftIcon: string | undefined = undefined
|
|
7
7
|
export let leftNotificationTooltip: string | undefined = undefined
|
|
8
8
|
export let leftNotificationCount: number | undefined = undefined
|
|
9
9
|
export let leftText: string
|
|
10
|
-
export let rightIcon: string
|
|
10
|
+
export let rightIcon: string | undefined = undefined
|
|
11
11
|
export let rightNotificationTooltip: string | undefined = undefined
|
|
12
12
|
export let rightNotificationCount: number | undefined = undefined
|
|
13
13
|
export let rightText: string
|
|
14
14
|
export let selected: "left" | "right" = "left"
|
|
15
15
|
export let disabled = false
|
|
16
|
+
export let leftDisabled = false
|
|
17
|
+
export let rightDisabled = false
|
|
18
|
+
export let size: "S" | "M" | "L" = "M"
|
|
16
19
|
|
|
17
20
|
const dispatch = createEventDispatcher<{
|
|
18
21
|
left: void
|
|
@@ -21,7 +24,7 @@
|
|
|
21
24
|
</script>
|
|
22
25
|
|
|
23
26
|
<div class="view-mode-toggle" class:disabled>
|
|
24
|
-
<div class="group">
|
|
27
|
+
<div class="group size-{size}">
|
|
25
28
|
<div class="wrapper">
|
|
26
29
|
{#if leftNotificationTooltip && leftNotificationCount}
|
|
27
30
|
<AbsTooltip text={leftNotificationTooltip}>
|
|
@@ -39,7 +42,8 @@
|
|
|
39
42
|
<ActionButton
|
|
40
43
|
icon={leftIcon}
|
|
41
44
|
quiet
|
|
42
|
-
{disabled}
|
|
45
|
+
disabled={disabled || leftDisabled}
|
|
46
|
+
{size}
|
|
43
47
|
selected={selected === "left"}
|
|
44
48
|
on:click={() => {
|
|
45
49
|
selected = "left"
|
|
@@ -67,7 +71,8 @@
|
|
|
67
71
|
<ActionButton
|
|
68
72
|
icon={rightIcon}
|
|
69
73
|
quiet
|
|
70
|
-
{disabled}
|
|
74
|
+
disabled={disabled || rightDisabled}
|
|
75
|
+
{size}
|
|
71
76
|
selected={selected === "right"}
|
|
72
77
|
on:click={() => {
|
|
73
78
|
selected = "right"
|
|
@@ -101,6 +106,16 @@
|
|
|
101
106
|
.left :global(*) {
|
|
102
107
|
border-radius: 10px 0 0 10px;
|
|
103
108
|
}
|
|
109
|
+
/* A radius sized for M crops the label on a shorter button */
|
|
110
|
+
.group.size-S {
|
|
111
|
+
border-radius: 8px;
|
|
112
|
+
}
|
|
113
|
+
.size-S .right :global(*) {
|
|
114
|
+
border-radius: 0 6px 6px 0;
|
|
115
|
+
}
|
|
116
|
+
.size-S .left :global(*) {
|
|
117
|
+
border-radius: 6px 0 0 6px;
|
|
118
|
+
}
|
|
104
119
|
.wrapper {
|
|
105
120
|
position: relative;
|
|
106
121
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import "./bbui.css"
|
|
|
2
2
|
|
|
3
3
|
// Constants
|
|
4
4
|
export * from "./constants"
|
|
5
|
+
export { generateId } from "./utils/ids"
|
|
5
6
|
|
|
6
7
|
// Form components
|
|
7
8
|
export { default as Checkbox } from "./Form/Checkbox.svelte"
|
|
@@ -122,5 +123,4 @@ export { createNotificationStore, notifications } from "./Stores/notifications"
|
|
|
122
123
|
|
|
123
124
|
// Helpers
|
|
124
125
|
export * as Helpers from "./helpers"
|
|
125
|
-
|
|
126
126
|
export type * from "./types"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { generateId } from "./ids"
|
|
3
|
+
|
|
4
|
+
const ID_PATTERN = /^[0-9a-zA-Z_-]+$/
|
|
5
|
+
|
|
6
|
+
describe("generateId", () => {
|
|
7
|
+
it("uses the configured length and character set", () => {
|
|
8
|
+
const ids = Array.from({ length: 100 }, generateId)
|
|
9
|
+
|
|
10
|
+
expect(ids.every(id => id.length === 9)).toBe(true)
|
|
11
|
+
expect(ids.every(id => ID_PATTERN.test(id))).toBe(true)
|
|
12
|
+
})
|
|
13
|
+
})
|