@graphenedata/cli 0.0.12 → 0.0.14
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/cli/cli.js +8591 -1214
- package/dist/docs/base.md +98 -0
- package/dist/docs/cli.md +22 -0
- package/dist/docs/graphene.md +10 -10
- package/dist/ui/component-utilities/echarts.js +2 -3
- package/dist/ui/component-utilities/formatting.js +3 -11
- package/dist/ui/component-utilities/getSeriesConfig.js +2 -1
- package/dist/ui/components/Area.svelte +188 -151
- package/dist/ui/components/AreaChart.svelte +43 -79
- package/dist/ui/components/Bar.svelte +273 -255
- package/dist/ui/components/BarChart.svelte +58 -112
- package/dist/ui/components/BigValue.svelte +13 -7
- package/dist/ui/components/Chart.svelte +280 -317
- package/dist/ui/components/Column.svelte +102 -113
- package/dist/ui/components/DateRange.svelte +37 -27
- package/dist/ui/components/Dropdown.svelte +77 -57
- package/dist/ui/components/DropdownOption.svelte +10 -7
- package/dist/ui/components/ECharts.svelte +23 -16
- package/dist/ui/components/ErrorChart.svelte +85 -21
- package/dist/ui/components/GrapheneQuery.svelte +7 -3
- package/dist/ui/components/InlineDelta.svelte +53 -34
- package/dist/ui/components/Line.svelte +192 -178
- package/dist/ui/components/LineChart.svelte +53 -96
- package/dist/ui/components/PieChart.svelte +26 -15
- package/dist/ui/components/QueryLoad.svelte +15 -10
- package/dist/ui/components/SortIcon.svelte +5 -1
- package/dist/ui/components/Table.svelte +15 -9
- package/dist/ui/components/TableCell.svelte +30 -17
- package/dist/ui/components/TableGroupRow.svelte +26 -19
- package/dist/ui/components/TableGroupToggle.svelte +9 -6
- package/dist/ui/components/TableHeader.svelte +37 -27
- package/dist/ui/components/TableRow.svelte +30 -20
- package/dist/ui/components/TableSubtotalRow.svelte +16 -9
- package/dist/ui/components/TableTotalRow.svelte +18 -11
- package/dist/ui/components/TextInput.svelte +23 -20
- package/dist/ui/components/_Table.svelte +303 -260
- package/dist/ui/internal/LocalApp.svelte +40 -0
- package/dist/ui/internal/NavSidebar.svelte +27 -30
- package/dist/ui/internal/PageError.svelte +23 -0
- package/dist/ui/internal/checkSocket.ts +48 -0
- package/dist/ui/internal/queryEngine.ts +9 -2
- package/dist/ui/internal/telemetry.ts +1 -0
- package/dist/ui/web.js +5 -55
- package/package.json +9 -10
- package/cli.ts +0 -156
- package/dist/ui/internal/NavSidebarHMR.svelte +0 -8
|
@@ -1,116 +1,72 @@
|
|
|
1
|
-
<script
|
|
1
|
+
<script lang="ts" module>
|
|
2
2
|
export const evidenceInclude = true
|
|
3
3
|
</script>
|
|
4
4
|
|
|
5
5
|
<script lang="ts">
|
|
6
|
-
import {getContext, onDestroy} from 'svelte'
|
|
6
|
+
import {getContext, onDestroy, onMount, untrack} from 'svelte'
|
|
7
|
+
import {type Writable, get} from 'svelte/store'
|
|
7
8
|
import {propKey, strictBuild} from '../component-utilities/chartContext.js'
|
|
8
9
|
import {getThemeStores} from '../component-utilities/themeStores'
|
|
9
10
|
import {toBoolean} from '../component-utilities/convert'
|
|
10
11
|
import {parseCommaList} from '../component-utilities/inputUtils.ts'
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export let fmt: string | undefined = undefined
|
|
25
|
-
export let totalAgg: string | undefined = undefined
|
|
26
|
-
export let totalFmt: string | undefined = undefined
|
|
27
|
-
export let weightCol: string | undefined = undefined
|
|
28
|
-
export let subtotalFmt: string | undefined = undefined
|
|
29
|
-
export let colorMax: string | undefined = undefined
|
|
30
|
-
export let colorMin: string | undefined = undefined
|
|
31
|
-
export let colorMid: string | undefined = undefined
|
|
32
|
-
export let colorBreakpoints: string[] | undefined = undefined
|
|
33
|
-
export let colorScale: any = 'default'
|
|
34
|
-
export let scaleColumn: string | undefined = undefined
|
|
35
|
-
export let downIsGood: boolean | string | undefined = undefined
|
|
36
|
-
export let showValue: boolean | string | undefined = undefined
|
|
37
|
-
export let deltaSymbol: boolean | string | undefined = undefined
|
|
38
|
-
export let neutralMin: number | string | undefined = 0
|
|
39
|
-
export let neutralMax: number | string | undefined = 0
|
|
40
|
-
export let chip: boolean | string | undefined = undefined
|
|
41
|
-
export let sparkWidth: number | string | undefined = undefined
|
|
42
|
-
export let sparkHeight: number | string | undefined = undefined
|
|
43
|
-
export let sparkColor: string | undefined = undefined
|
|
44
|
-
export let sparkX: string | undefined = undefined
|
|
45
|
-
export let sparkY: string | undefined = undefined
|
|
46
|
-
export let sparkYScale: boolean | string | undefined = undefined
|
|
47
|
-
export let barColor: string | undefined = '#a5cdee'
|
|
48
|
-
export let negativeBarColor: string | undefined = '#fca5a5'
|
|
49
|
-
export let backgroundColor: string | undefined = 'transparent'
|
|
50
|
-
export let hideLabels: boolean | string | undefined = undefined
|
|
51
|
-
export let colGroup: string | undefined = undefined
|
|
52
|
-
export let fmtColumn: string | undefined = undefined
|
|
53
|
-
export let redNegatives: boolean | string | undefined = undefined
|
|
13
|
+
interface Props {
|
|
14
|
+
id: string, description?: string, contentType?: string, title?: string, align?: string
|
|
15
|
+
wrap?: boolean | string, wrapTitle?: boolean | string, height?: string, width?: string, alt?: string
|
|
16
|
+
openInNewTab?: boolean | string, linkLabel?: string, fmt?: string, totalAgg?: string, totalFmt?: string
|
|
17
|
+
weightCol?: string, subtotalFmt?: string, colorMax?: string, colorMin?: string, colorMid?: string
|
|
18
|
+
colorBreakpoints?: string[], colorScale?: any, scaleColumn?: string, downIsGood?: boolean | string
|
|
19
|
+
showValue?: boolean | string, deltaSymbol?: boolean | string, neutralMin?: number | string
|
|
20
|
+
neutralMax?: number | string, chip?: boolean | string, sparkWidth?: number | string
|
|
21
|
+
sparkHeight?: number | string, sparkColor?: string, sparkX?: string, sparkY?: string
|
|
22
|
+
sparkYScale?: boolean | string, barColor?: string, negativeBarColor?: string, backgroundColor?: string
|
|
23
|
+
hideLabels?: boolean | string, colGroup?: string, fmtColumn?: string, redNegatives?: boolean | string
|
|
24
|
+
}
|
|
54
25
|
|
|
55
|
-
|
|
26
|
+
let {
|
|
27
|
+
id, description = undefined, contentType = undefined, title = undefined, align = undefined,
|
|
28
|
+
wrap = undefined, wrapTitle = undefined, height = undefined, width = undefined, alt = undefined,
|
|
29
|
+
openInNewTab = undefined, linkLabel = undefined, fmt = undefined, totalAgg = undefined,
|
|
30
|
+
totalFmt = undefined, weightCol = undefined, subtotalFmt = undefined, colorMax = undefined,
|
|
31
|
+
colorMin = undefined, colorMid = undefined, colorBreakpoints = undefined, colorScale = 'default',
|
|
32
|
+
scaleColumn = undefined, downIsGood = undefined, showValue = undefined, deltaSymbol = undefined,
|
|
33
|
+
neutralMin = 0, neutralMax = 0, chip = undefined, sparkWidth = undefined, sparkHeight = undefined,
|
|
34
|
+
sparkColor = undefined, sparkX = undefined, sparkY = undefined, sparkYScale = undefined,
|
|
35
|
+
barColor = '#a5cdee', negativeBarColor = '#fca5a5', backgroundColor = 'transparent',
|
|
36
|
+
hideLabels = undefined, colGroup = undefined, fmtColumn = undefined, redNegatives = undefined,
|
|
37
|
+
}: Props = $props()
|
|
56
38
|
|
|
57
|
-
|
|
58
|
-
let negativeBarColorStore = resolveColor(negativeBarColor)
|
|
59
|
-
let backgroundColorStore = resolveColor(backgroundColor)
|
|
60
|
-
let colorScaleStore = resolveColorPalette(colorScale)
|
|
39
|
+
const {resolveColor, resolveColorPalette} = getThemeStores()
|
|
61
40
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
41
|
+
// Get stores reactively - use $derived to track prop changes
|
|
42
|
+
let barColorStore = $derived(resolveColor(barColor))
|
|
43
|
+
let negativeBarColorStore = $derived(resolveColor(negativeBarColor))
|
|
44
|
+
let backgroundColorStore = $derived(resolveColor(backgroundColor))
|
|
45
|
+
let colorScaleStore = $derived(resolveColorPalette(colorScale))
|
|
66
46
|
|
|
67
|
-
const
|
|
68
|
-
$: colorBreakpoints = parseCommaList(colorBreakpoints)
|
|
47
|
+
const chartProps = getContext<Writable<any>>(propKey)
|
|
69
48
|
|
|
70
49
|
const identifier = Symbol('GrapheneColumn')
|
|
71
50
|
|
|
72
|
-
wrap = toBoolean(wrap) ?? false
|
|
73
|
-
wrapTitle = toBoolean(wrapTitle) ?? false
|
|
74
|
-
openInNewTab = toBoolean(openInNewTab) ?? false
|
|
75
|
-
downIsGood = toBoolean(downIsGood) ?? false
|
|
76
|
-
showValue = toBoolean(showValue) ?? true
|
|
77
|
-
deltaSymbol = toBoolean(deltaSymbol) ?? true
|
|
78
|
-
chip = toBoolean(chip) ?? false
|
|
79
|
-
sparkYScale = toBoolean(sparkYScale) ?? false
|
|
80
|
-
hideLabels = toBoolean(hideLabels) ?? false
|
|
81
|
-
redNegatives = toBoolean(redNegatives) ?? false
|
|
82
|
-
|
|
83
51
|
const coerceNumber = (value: number | string | undefined): number | undefined => {
|
|
84
52
|
if (value === undefined || value === null || value === '') return undefined
|
|
85
53
|
let parsed = Number(value)
|
|
86
54
|
return Number.isNaN(parsed) ? undefined : parsed
|
|
87
55
|
}
|
|
88
56
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
let data = $props.data?.[0]
|
|
92
|
-
if (!data || !Object.keys(data).includes(id)) {
|
|
93
|
-
let error = `Error in table: ${id} does not exist in the dataset`
|
|
94
|
-
if (strictBuild) throw new Error(error)
|
|
95
|
-
console.warn(error)
|
|
96
|
-
}
|
|
97
|
-
} catch (error) {
|
|
98
|
-
if (strictBuild) throw error
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const options = () => ({
|
|
57
|
+
// Build the column options object - as a function so it can be called synchronously
|
|
58
|
+
const getColumnOptions = () => ({
|
|
103
59
|
identifier,
|
|
104
60
|
id,
|
|
105
61
|
title,
|
|
106
62
|
align,
|
|
107
|
-
wrap,
|
|
108
|
-
wrapTitle,
|
|
63
|
+
wrap: toBoolean(wrap) ?? false,
|
|
64
|
+
wrapTitle: toBoolean(wrapTitle) ?? false,
|
|
109
65
|
contentType,
|
|
110
66
|
height,
|
|
111
67
|
width,
|
|
112
68
|
alt,
|
|
113
|
-
openInNewTab,
|
|
69
|
+
openInNewTab: toBoolean(openInNewTab) ?? false,
|
|
114
70
|
linkLabel,
|
|
115
71
|
fmt,
|
|
116
72
|
fmtColumn,
|
|
@@ -118,57 +74,90 @@
|
|
|
118
74
|
totalFmt,
|
|
119
75
|
subtotalFmt,
|
|
120
76
|
weightCol,
|
|
121
|
-
downIsGood,
|
|
122
|
-
deltaSymbol,
|
|
123
|
-
chip,
|
|
77
|
+
downIsGood: toBoolean(downIsGood) ?? false,
|
|
78
|
+
deltaSymbol: toBoolean(deltaSymbol) ?? true,
|
|
79
|
+
chip: toBoolean(chip) ?? false,
|
|
124
80
|
neutralMin: coerceNumber(neutralMin) ?? 0,
|
|
125
81
|
neutralMax: coerceNumber(neutralMax) ?? 0,
|
|
126
|
-
showValue,
|
|
82
|
+
showValue: toBoolean(showValue) ?? true,
|
|
127
83
|
colorMax,
|
|
128
84
|
colorMin,
|
|
129
85
|
colorMid,
|
|
130
|
-
colorScale:
|
|
131
|
-
colorBreakpoints,
|
|
86
|
+
colorScale: get(colorScaleStore),
|
|
87
|
+
colorBreakpoints: parseCommaList(colorBreakpoints),
|
|
132
88
|
scaleColumn,
|
|
133
89
|
colGroup,
|
|
134
90
|
description,
|
|
135
|
-
redNegatives,
|
|
91
|
+
redNegatives: toBoolean(redNegatives) ?? false,
|
|
136
92
|
sparkWidth,
|
|
137
93
|
sparkHeight,
|
|
138
94
|
sparkColor,
|
|
139
95
|
sparkX,
|
|
140
96
|
sparkY,
|
|
141
|
-
sparkYScale,
|
|
142
|
-
barColor:
|
|
143
|
-
negativeBarColor:
|
|
144
|
-
backgroundColor:
|
|
145
|
-
hideLabels,
|
|
97
|
+
sparkYScale: toBoolean(sparkYScale) ?? false,
|
|
98
|
+
barColor: get(barColorStore),
|
|
99
|
+
negativeBarColor: get(negativeBarColorStore),
|
|
100
|
+
backgroundColor: get(backgroundColorStore),
|
|
101
|
+
hideLabels: toBoolean(hideLabels) ?? false,
|
|
146
102
|
})
|
|
147
103
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
let
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
next.columns = [
|
|
158
|
-
...next.columns.slice(0, existing),
|
|
159
|
-
option,
|
|
160
|
-
...next.columns.slice(existing + 1),
|
|
161
|
-
]
|
|
104
|
+
// Register column on mount
|
|
105
|
+
onMount(() => {
|
|
106
|
+
// Check column name once on mount (not reactively)
|
|
107
|
+
try {
|
|
108
|
+
let data = get(chartProps).data?.[0]
|
|
109
|
+
if (data && !Object.keys(data).includes(id)) {
|
|
110
|
+
let error = `Error in table: ${id} does not exist in the dataset`
|
|
111
|
+
if (strictBuild) throw new Error(error)
|
|
112
|
+
console.warn(error)
|
|
162
113
|
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (strictBuild) throw error
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Initial registration
|
|
119
|
+
chartProps.update((state: any) => {
|
|
120
|
+
let next = {...state, columns: [...state.columns, getColumnOptions()]}
|
|
163
121
|
return next
|
|
164
122
|
})
|
|
165
|
-
}
|
|
123
|
+
})
|
|
166
124
|
|
|
167
|
-
|
|
125
|
+
// Update column options when props change
|
|
126
|
+
// Track all the props that affect columnOptions
|
|
127
|
+
$effect(() => {
|
|
128
|
+
// Read all props that could change
|
|
129
|
+
void [id, title, align, wrap, wrapTitle, contentType, height, width, alt, openInNewTab,
|
|
130
|
+
linkLabel, fmt, fmtColumn, totalAgg, totalFmt, subtotalFmt, weightCol, downIsGood,
|
|
131
|
+
deltaSymbol, chip, neutralMin, neutralMax, showValue, colorMax, colorMin, colorMid,
|
|
132
|
+
colorScale, colorBreakpoints, scaleColumn, colGroup, description, redNegatives,
|
|
133
|
+
sparkWidth, sparkHeight, sparkColor, sparkX, sparkY, sparkYScale, barColor,
|
|
134
|
+
negativeBarColor, backgroundColor, hideLabels]
|
|
135
|
+
// Also track store values
|
|
136
|
+
void [$colorScaleStore, $barColorStore, $negativeBarColorStore, $backgroundColorStore]
|
|
137
|
+
|
|
138
|
+
// Use untrack to prevent this update from creating a dependency loop
|
|
139
|
+
untrack(() => {
|
|
140
|
+
chartProps.update((state: any) => {
|
|
141
|
+
let next = {...state}
|
|
142
|
+
let existing = next.columns.findIndex((column: any) => column.identifier === identifier)
|
|
143
|
+
let option = getColumnOptions()
|
|
144
|
+
if (existing !== -1) {
|
|
145
|
+
next.columns = [
|
|
146
|
+
...next.columns.slice(0, existing),
|
|
147
|
+
option,
|
|
148
|
+
...next.columns.slice(existing + 1),
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
return next
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
})
|
|
168
155
|
|
|
169
156
|
onDestroy(() => {
|
|
170
|
-
|
|
171
|
-
|
|
157
|
+
untrack(() => {
|
|
158
|
+
chartProps.update((state: any) => {
|
|
159
|
+
return {...state, columns: state.columns.filter((column: any) => column.identifier !== identifier)}
|
|
160
|
+
})
|
|
172
161
|
})
|
|
173
162
|
})
|
|
174
163
|
</script>
|
|
@@ -2,17 +2,25 @@
|
|
|
2
2
|
import {onMount} from 'svelte'
|
|
3
3
|
import {toBoolean} from '../component-utilities/inputUtils'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
interface Props {
|
|
6
|
+
name: string
|
|
7
|
+
label?: string
|
|
8
|
+
title?: string
|
|
9
|
+
description?: string
|
|
10
|
+
start?: string | Date
|
|
11
|
+
end?: string | Date
|
|
12
|
+
defaultValue?: string
|
|
13
|
+
presetRanges?: string | string[]
|
|
14
|
+
data?: string
|
|
15
|
+
dates?: string
|
|
16
|
+
hideDuringPrint?: boolean | string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
name, label = undefined, title = undefined, description = undefined, start = undefined,
|
|
21
|
+
end = undefined, defaultValue = undefined, presetRanges = undefined, data = undefined,
|
|
22
|
+
dates = undefined, hideDuringPrint = true,
|
|
23
|
+
}: Props = $props()
|
|
16
24
|
|
|
17
25
|
const DEFAULT_PRESETS = ['Last 7 Days', 'Last 30 Days', 'Last 90 Days', 'Last 365 Days', 'Last Month', 'Last Year', 'Month to Date', 'Month to Today', 'Year to Date', 'Year to Today', 'All Time']
|
|
18
26
|
|
|
@@ -20,21 +28,21 @@
|
|
|
20
28
|
let queryKey = ''
|
|
21
29
|
let queryHandler: ((res: {rows?: any[]; error?: any}) => void) | null = null
|
|
22
30
|
|
|
23
|
-
let domainStart: string | null = null
|
|
24
|
-
let domainEnd: string | null = null
|
|
31
|
+
let domainStart: string | null = $state(null)
|
|
32
|
+
let domainEnd: string | null = $state(null)
|
|
25
33
|
|
|
26
|
-
let currentStart: string | null = null
|
|
27
|
-
let currentEnd: string | null = null
|
|
28
|
-
let currentPreset: string = ''
|
|
34
|
+
let currentStart: string | null = $state(null)
|
|
35
|
+
let currentEnd: string | null = $state(null)
|
|
36
|
+
let currentPreset: string = $state('')
|
|
29
37
|
let touched = false
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
let hidePrint = $derived(toBoolean(hideDuringPrint))
|
|
40
|
+
let presetList = $derived((() => {
|
|
33
41
|
if (Array.isArray(presetRanges)) return presetRanges
|
|
34
42
|
if (presetRanges) return [presetRanges]
|
|
35
43
|
return DEFAULT_PRESETS
|
|
36
|
-
})()
|
|
37
|
-
|
|
44
|
+
})())
|
|
45
|
+
let displayLabel = $derived(title || label)
|
|
38
46
|
|
|
39
47
|
onMount(() => {
|
|
40
48
|
mounted = true
|
|
@@ -55,7 +63,9 @@
|
|
|
55
63
|
}
|
|
56
64
|
})
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
$effect(() => {
|
|
67
|
+
refreshQuery()
|
|
68
|
+
})
|
|
59
69
|
|
|
60
70
|
function refreshQuery () {
|
|
61
71
|
if (!mounted) return
|
|
@@ -108,7 +118,7 @@
|
|
|
108
118
|
}
|
|
109
119
|
|
|
110
120
|
function addDays (value: Date, days: number): Date {
|
|
111
|
-
let copy = new Date(value)
|
|
121
|
+
let copy = new Date(value) // eslint-disable-line svelte/prefer-svelte-reactivity
|
|
112
122
|
copy.setDate(copy.getDate() + days)
|
|
113
123
|
return copy
|
|
114
124
|
}
|
|
@@ -128,13 +138,13 @@
|
|
|
128
138
|
}
|
|
129
139
|
|
|
130
140
|
function addMonths (value: Date, months: number): Date {
|
|
131
|
-
let copy = new Date(value)
|
|
141
|
+
let copy = new Date(value) // eslint-disable-line svelte/prefer-svelte-reactivity
|
|
132
142
|
copy.setMonth(copy.getMonth() + months)
|
|
133
143
|
return copy
|
|
134
144
|
}
|
|
135
145
|
|
|
136
146
|
function addYears (value: Date, years: number): Date {
|
|
137
|
-
let copy = new Date(value)
|
|
147
|
+
let copy = new Date(value) // eslint-disable-line svelte/prefer-svelte-reactivity
|
|
138
148
|
copy.setFullYear(copy.getFullYear() + years)
|
|
139
149
|
return copy
|
|
140
150
|
}
|
|
@@ -263,12 +273,12 @@
|
|
|
263
273
|
<div class="input-description">{description}</div>
|
|
264
274
|
{/if}
|
|
265
275
|
<div class="range-row">
|
|
266
|
-
<input id={`daterange-${name}-start`} class="date-input" type="date" value={currentStart || ''}
|
|
276
|
+
<input id={`daterange-${name}-start`} class="date-input" type="date" value={currentStart || ''} onchange={onStartChange} />
|
|
267
277
|
<span class="range-separator">to</span>
|
|
268
|
-
<input id={`daterange-${name}-end`} class="date-input" type="date" value={currentEnd || ''}
|
|
278
|
+
<input id={`daterange-${name}-end`} class="date-input" type="date" value={currentEnd || ''} onchange={onEndChange} />
|
|
269
279
|
</div>
|
|
270
280
|
{#if presetList.length}
|
|
271
|
-
<select class="preset-select"
|
|
281
|
+
<select class="preset-select" onchange={onPresetChange}>
|
|
272
282
|
<option value="">Custom range</option>
|
|
273
283
|
{#each presetList as preset (preset)}
|
|
274
284
|
<option value={preset} selected={preset === currentPreset}>{preset}</option>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {onMount, setContext, tick} from 'svelte'
|
|
2
|
+
import {onMount, setContext, tick, type Snippet} from 'svelte'
|
|
3
3
|
import {DROPDOWN_CONTEXT} from '../component-utilities/dropdownContext'
|
|
4
4
|
import {ensureArray, toBoolean} from '../component-utilities/inputUtils'
|
|
5
5
|
|
|
@@ -8,38 +8,48 @@
|
|
|
8
8
|
label: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
11
|
+
interface Props {
|
|
12
|
+
name: string
|
|
13
|
+
data?: string
|
|
14
|
+
value?: string
|
|
15
|
+
label?: string
|
|
16
|
+
optionLabel?: string
|
|
17
|
+
labelField?: string
|
|
18
|
+
title?: string
|
|
19
|
+
placeholder?: string
|
|
20
|
+
multiple?: boolean | string
|
|
21
|
+
defaultValue?: string | string[]
|
|
22
|
+
selectAllByDefault?: boolean | string
|
|
23
|
+
noDefault?: boolean | string
|
|
24
|
+
disableSelectAll?: boolean | string
|
|
25
|
+
hideDuringPrint?: boolean | string
|
|
26
|
+
description?: string
|
|
27
|
+
disabled?: boolean | string
|
|
28
|
+
children?: Snippet
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
name, data = undefined, value = 'value', label = undefined, optionLabel = undefined,
|
|
33
|
+
labelField = undefined, title = undefined, placeholder = 'Select option', multiple = false,
|
|
34
|
+
defaultValue = undefined, selectAllByDefault = false, noDefault = false, disableSelectAll = false,
|
|
35
|
+
hideDuringPrint = true, description = undefined, disabled = false, children = undefined,
|
|
36
|
+
}: Props = $props()
|
|
27
37
|
|
|
28
38
|
let mounted = false
|
|
29
|
-
let queryOptions: Option[] = []
|
|
30
|
-
let manualOptions: Option[] = []
|
|
31
|
-
let selection: any[] = []
|
|
39
|
+
let queryOptions: Option[] = $state([])
|
|
40
|
+
let manualOptions: Option[] = $state([])
|
|
41
|
+
let selection: any[] = $state([])
|
|
32
42
|
let touched = false
|
|
33
43
|
let queryHandler: ((res: {rows?: any[]; error?: any}) => void) | null = null
|
|
34
44
|
let queryKey = ''
|
|
35
45
|
|
|
36
|
-
let isOpen = false
|
|
37
|
-
let searchTerm = ''
|
|
38
|
-
let activeIndex = -1
|
|
39
|
-
let triggerEl: HTMLButtonElement | null = null
|
|
40
|
-
let menuEl: HTMLDivElement | null = null
|
|
41
|
-
let searchInput: HTMLInputElement | null = null
|
|
42
|
-
let triggerWidth = 0
|
|
46
|
+
let isOpen = $state(false)
|
|
47
|
+
let searchTerm = $state('')
|
|
48
|
+
let activeIndex = $state(-1)
|
|
49
|
+
let triggerEl: HTMLButtonElement | null = $state(null)
|
|
50
|
+
let menuEl: HTMLDivElement | null = $state(null)
|
|
51
|
+
let searchInput: HTMLInputElement | null = $state(null)
|
|
52
|
+
let triggerWidth = $state(0)
|
|
43
53
|
|
|
44
54
|
const registerOption = (opt: Option) => {
|
|
45
55
|
manualOptions = [...manualOptions, opt]
|
|
@@ -65,7 +75,7 @@
|
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
const combineOptions = (manual: Option[], queried: Option[]) => {
|
|
68
|
-
let map = new Map<string, Option>()
|
|
78
|
+
let map = new Map<string, Option>() // eslint-disable-line svelte/prefer-svelte-reactivity
|
|
69
79
|
for (let opt of [...manual, ...queried]) {
|
|
70
80
|
let key = optionKey(opt.value)
|
|
71
81
|
if (!map.has(key)) map.set(key, opt)
|
|
@@ -73,23 +83,26 @@
|
|
|
73
83
|
return Array.from(map.values())
|
|
74
84
|
}
|
|
75
85
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
let multi = $derived(toBoolean(multiple))
|
|
87
|
+
let selectAllDefault = $derived(toBoolean(selectAllByDefault))
|
|
88
|
+
let hasNoDefault = $derived(toBoolean(noDefault))
|
|
89
|
+
let hidePrint = $derived(toBoolean(hideDuringPrint))
|
|
90
|
+
let isDisabled = $derived(toBoolean(disabled))
|
|
91
|
+
let disableSelectAllButton = $derived(toBoolean(disableSelectAll))
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
let resolvedLabelField = $derived(optionLabel || labelField || (label && data ? label : undefined))
|
|
94
|
+
let resolvedTitle = $derived(title || (!data ? label : undefined))
|
|
95
|
+
let triggerPlaceholder = $derived(placeholder || resolvedTitle || 'Select option')
|
|
96
|
+
let searchPlaceholder = $derived(resolvedTitle || placeholder || 'Search options')
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
let availableOptions = $derived(combineOptions(manualOptions, queryOptions))
|
|
99
|
+
let valueMap = $derived(new Map(availableOptions.map(opt => [optionKey(opt.value), opt])))
|
|
100
|
+
let filteredOptions = $derived(filterOptions(availableOptions, searchTerm))
|
|
101
|
+
let selectedDisplayOptions = $derived(selection.map(val => valueMap.get(optionKey(val)) || {value: val, label: String(val ?? '')}))
|
|
102
|
+
|
|
103
|
+
$effect(() => {
|
|
104
|
+
if (isOpen) activeIndex = ensureActiveIndex(activeIndex, filteredOptions)
|
|
105
|
+
})
|
|
93
106
|
|
|
94
107
|
function setupQuery () {
|
|
95
108
|
if (!mounted) return
|
|
@@ -276,7 +289,9 @@
|
|
|
276
289
|
optionEl?.scrollIntoView({block: 'nearest'})
|
|
277
290
|
}
|
|
278
291
|
|
|
279
|
-
|
|
292
|
+
$effect(() => {
|
|
293
|
+
if (isOpen && activeIndex >= 0) tick().then(scrollActiveIntoView)
|
|
294
|
+
})
|
|
280
295
|
|
|
281
296
|
onMount(() => {
|
|
282
297
|
mounted = true
|
|
@@ -303,9 +318,13 @@
|
|
|
303
318
|
}
|
|
304
319
|
})
|
|
305
320
|
|
|
306
|
-
|
|
321
|
+
$effect(() => {
|
|
322
|
+
setupQuery()
|
|
323
|
+
})
|
|
307
324
|
|
|
308
|
-
|
|
325
|
+
$effect(() => {
|
|
326
|
+
if (triggerEl) updateTriggerWidth()
|
|
327
|
+
})
|
|
309
328
|
|
|
310
329
|
function syncSelection (fromUser: boolean) {
|
|
311
330
|
let opts = availableOptions
|
|
@@ -356,8 +375,8 @@
|
|
|
356
375
|
setSelection([], true)
|
|
357
376
|
}
|
|
358
377
|
|
|
359
|
-
|
|
360
|
-
|
|
378
|
+
let elementId = $derived(`dropdown-${name}`)
|
|
379
|
+
let menuId = $derived(`${elementId}-menu`)
|
|
361
380
|
|
|
362
381
|
function getContainerClass () {
|
|
363
382
|
if (!hidePrint) return 'input-block'
|
|
@@ -377,6 +396,7 @@
|
|
|
377
396
|
}
|
|
378
397
|
</script>
|
|
379
398
|
|
|
399
|
+
{@render children?.()}
|
|
380
400
|
<div class={getContainerClass()}>
|
|
381
401
|
{#if resolvedTitle}
|
|
382
402
|
<label class="input-label" for={elementId}>{resolvedTitle}</label>
|
|
@@ -397,8 +417,8 @@
|
|
|
397
417
|
aria-expanded={isOpen}
|
|
398
418
|
aria-controls={menuId}
|
|
399
419
|
aria-activedescendant={isOpen && activeIndex >= 0 ? `${menuId}-option-${activeIndex}` : undefined}
|
|
400
|
-
|
|
401
|
-
|
|
420
|
+
onclick={toggleMenu}
|
|
421
|
+
onkeydown={handleTriggerKeydown}
|
|
402
422
|
>
|
|
403
423
|
<span class="dropdown-trigger-label">
|
|
404
424
|
{#if multi}
|
|
@@ -432,7 +452,7 @@
|
|
|
432
452
|
tabindex="0"
|
|
433
453
|
aria-multiselectable={multi}
|
|
434
454
|
style={`min-width: ${Math.max(triggerWidth, 220)}px`}
|
|
435
|
-
|
|
455
|
+
onkeydown={handleMenuKeydown}
|
|
436
456
|
>
|
|
437
457
|
<div class="dropdown-search">
|
|
438
458
|
<input
|
|
@@ -455,10 +475,10 @@
|
|
|
455
475
|
aria-selected={isOptionSelected(opt)}
|
|
456
476
|
data-index={index}
|
|
457
477
|
id={`${menuId}-option-${index}`}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
478
|
+
onmousedown={(e) => e.preventDefault()}
|
|
479
|
+
onmouseenter={() => activeIndex = index}
|
|
480
|
+
onclick={() => handleOptionSelect(opt, false)}
|
|
481
|
+
onkeydown={(event) => handleOptionKeydown(event, opt)}
|
|
462
482
|
>
|
|
463
483
|
<span class="dropdown-check" aria-hidden="true">
|
|
464
484
|
{#if multi}
|
|
@@ -501,13 +521,13 @@
|
|
|
501
521
|
{#if multi}
|
|
502
522
|
<div class="dropdown-footer">
|
|
503
523
|
{#if !disableSelectAllButton}
|
|
504
|
-
<button type="button" class="dropdown-footer-action"
|
|
524
|
+
<button type="button" class="dropdown-footer-action" onclick={(event) => { event.stopPropagation(); event.preventDefault(); selectAll() }}>Select all</button>
|
|
505
525
|
{/if}
|
|
506
526
|
<button
|
|
507
527
|
type="button"
|
|
508
528
|
class="dropdown-footer-action"
|
|
509
529
|
disabled={!selection.length}
|
|
510
|
-
|
|
530
|
+
onclick={(event) => { event.stopPropagation(); event.preventDefault(); clearSelection() }}
|
|
511
531
|
>
|
|
512
532
|
Clear selection
|
|
513
533
|
</button>
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {getContext,
|
|
2
|
+
import {getContext, onMount} from 'svelte'
|
|
3
3
|
import {DROPDOWN_CONTEXT} from '../component-utilities/dropdownContext'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
interface Props {
|
|
6
|
+
value: any
|
|
7
|
+
valueLabel?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let {value, valueLabel = undefined}: Props = $props()
|
|
7
11
|
|
|
8
12
|
type RegisterFn = ((option: {value: any; label: string}) => (() => void) | void) | undefined
|
|
9
13
|
const register = getContext<RegisterFn>(DROPDOWN_CONTEXT)
|
|
@@ -13,9 +17,8 @@
|
|
|
13
17
|
onMount(() => {
|
|
14
18
|
if (!register) return
|
|
15
19
|
unregister = register({value, label: valueLabel ?? String(value)})
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (typeof unregister === 'function') unregister()
|
|
20
|
+
return () => {
|
|
21
|
+
if (typeof unregister === 'function') unregister()
|
|
22
|
+
}
|
|
20
23
|
})
|
|
21
24
|
</script>
|