@graphenedata/cli 0.0.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.
Files changed (123) hide show
  1. package/LICENSE.md +100 -0
  2. package/THIRD_PARTY_NOTICES.md +12 -0
  3. package/cli.ts +157 -0
  4. package/dist/cli/cli.js +43 -0
  5. package/dist/docs/data_apps/components/charts/annotations.md +673 -0
  6. package/dist/docs/data_apps/components/charts/area-chart.md +202 -0
  7. package/dist/docs/data_apps/components/charts/bar-chart.md +317 -0
  8. package/dist/docs/data_apps/components/charts/box-plot.md +190 -0
  9. package/dist/docs/data_apps/components/charts/bubble-chart.md +151 -0
  10. package/dist/docs/data_apps/components/charts/calendar-heatmap.md +112 -0
  11. package/dist/docs/data_apps/components/charts/custom-echarts.md +308 -0
  12. package/dist/docs/data_apps/components/charts/echarts-options.md +217 -0
  13. package/dist/docs/data_apps/components/charts/funnel-chart.md +106 -0
  14. package/dist/docs/data_apps/components/charts/heatmap.md +180 -0
  15. package/dist/docs/data_apps/components/charts/histogram.md +107 -0
  16. package/dist/docs/data_apps/components/charts/line-chart.md +265 -0
  17. package/dist/docs/data_apps/components/charts/mixed-type-charts.md +240 -0
  18. package/dist/docs/data_apps/components/charts/sankey-diagram.md +301 -0
  19. package/dist/docs/data_apps/components/charts/scatter-plot.md +134 -0
  20. package/dist/docs/data_apps/components/charts/sparkline.md +68 -0
  21. package/dist/docs/data_apps/components/data/big-value.md +153 -0
  22. package/dist/docs/data_apps/components/data/delta.md +89 -0
  23. package/dist/docs/data_apps/components/data/table.md +470 -0
  24. package/dist/docs/data_apps/components/data/value.md +97 -0
  25. package/dist/docs/data_apps/components/inputs/button-group.md +154 -0
  26. package/dist/docs/data_apps/components/inputs/checkbox.md +52 -0
  27. package/dist/docs/data_apps/components/inputs/date-input.md +131 -0
  28. package/dist/docs/data_apps/components/inputs/date-range.md +124 -0
  29. package/dist/docs/data_apps/components/inputs/dimension-grid.md +67 -0
  30. package/dist/docs/data_apps/components/inputs/dropdown.md +199 -0
  31. package/dist/docs/data_apps/components/inputs/index.md +3 -0
  32. package/dist/docs/data_apps/components/inputs/slider.md +126 -0
  33. package/dist/docs/data_apps/components/inputs/text-input.md +86 -0
  34. package/dist/docs/data_apps/components/maps/area-map.md +397 -0
  35. package/dist/docs/data_apps/components/maps/base-map.md +269 -0
  36. package/dist/docs/data_apps/components/maps/bubble-map.md +361 -0
  37. package/dist/docs/data_apps/components/maps/point-map.md +326 -0
  38. package/dist/docs/data_apps/components/maps/us-map.md +167 -0
  39. package/dist/docs/data_apps/components/ui/accordion.md +116 -0
  40. package/dist/docs/data_apps/components/ui/alert.md +37 -0
  41. package/dist/docs/data_apps/components/ui/big-link.md +19 -0
  42. package/dist/docs/data_apps/components/ui/details.md +58 -0
  43. package/dist/docs/data_apps/components/ui/download-data.md +41 -0
  44. package/dist/docs/data_apps/components/ui/embed.md +47 -0
  45. package/dist/docs/data_apps/components/ui/grid.md +45 -0
  46. package/dist/docs/data_apps/components/ui/image.md +61 -0
  47. package/dist/docs/data_apps/components/ui/info.md +47 -0
  48. package/dist/docs/data_apps/components/ui/last-refreshed.md +28 -0
  49. package/dist/docs/data_apps/components/ui/link-button.md +20 -0
  50. package/dist/docs/data_apps/components/ui/link.md +40 -0
  51. package/dist/docs/data_apps/components/ui/modal.md +57 -0
  52. package/dist/docs/data_apps/components/ui/note.md +32 -0
  53. package/dist/docs/data_apps/components/ui/print-format-components.md +85 -0
  54. package/dist/docs/data_apps/components/ui/tabs.md +122 -0
  55. package/dist/docs/graphene.md +129 -0
  56. package/dist/ui/app.css +332 -0
  57. package/dist/ui/assets/favicon.ico +0 -0
  58. package/dist/ui/component-utilities/autoFormatting.js +301 -0
  59. package/dist/ui/component-utilities/builtInFormats.js +482 -0
  60. package/dist/ui/component-utilities/chartContext.js +12 -0
  61. package/dist/ui/component-utilities/chartWindowDebug.js +21 -0
  62. package/dist/ui/component-utilities/checkInputs.js +95 -0
  63. package/dist/ui/component-utilities/convert.js +15 -0
  64. package/dist/ui/component-utilities/dateParsing.js +57 -0
  65. package/dist/ui/component-utilities/dropdownContext.ts +1 -0
  66. package/dist/ui/component-utilities/echarts.js +262 -0
  67. package/dist/ui/component-utilities/echartsThemes.js +453 -0
  68. package/dist/ui/component-utilities/formatTitle.js +24 -0
  69. package/dist/ui/component-utilities/formatting.js +258 -0
  70. package/dist/ui/component-utilities/getColumnExtents.js +79 -0
  71. package/dist/ui/component-utilities/getColumnSummary.js +67 -0
  72. package/dist/ui/component-utilities/getCompletedData.js +114 -0
  73. package/dist/ui/component-utilities/getDistinctCount.js +7 -0
  74. package/dist/ui/component-utilities/getDistinctValues.js +15 -0
  75. package/dist/ui/component-utilities/getSeriesConfig.js +236 -0
  76. package/dist/ui/component-utilities/getSortedData.js +7 -0
  77. package/dist/ui/component-utilities/getStackPercentages.js +43 -0
  78. package/dist/ui/component-utilities/getStackedData.js +17 -0
  79. package/dist/ui/component-utilities/getYAxisIndex.js +15 -0
  80. package/dist/ui/component-utilities/globalContexts.js +1 -0
  81. package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +119 -0
  82. package/dist/ui/component-utilities/inputUtils.ts +25 -0
  83. package/dist/ui/component-utilities/replaceNulls.js +14 -0
  84. package/dist/ui/component-utilities/tableUtils.ts +120 -0
  85. package/dist/ui/component-utilities/themeStores.ts +116 -0
  86. package/dist/ui/components/Area.svelte +174 -0
  87. package/dist/ui/components/AreaChart.svelte +150 -0
  88. package/dist/ui/components/Bar.svelte +326 -0
  89. package/dist/ui/components/BarChart.svelte +194 -0
  90. package/dist/ui/components/BigValue.svelte +69 -0
  91. package/dist/ui/components/Chart.svelte +1070 -0
  92. package/dist/ui/components/Column.svelte +172 -0
  93. package/dist/ui/components/DateRange.svelte +324 -0
  94. package/dist/ui/components/Dropdown.svelte +738 -0
  95. package/dist/ui/components/DropdownOption.svelte +21 -0
  96. package/dist/ui/components/ECharts.svelte +77 -0
  97. package/dist/ui/components/ErrorChart.svelte +54 -0
  98. package/dist/ui/components/GrapheneQuery.svelte +12 -0
  99. package/dist/ui/components/InlineDelta.svelte +150 -0
  100. package/dist/ui/components/Line.svelte +210 -0
  101. package/dist/ui/components/LineChart.svelte +178 -0
  102. package/dist/ui/components/PieChart.svelte +36 -0
  103. package/dist/ui/components/QueryLoad.svelte +82 -0
  104. package/dist/ui/components/Row.svelte +14 -0
  105. package/dist/ui/components/SortIcon.svelte +32 -0
  106. package/dist/ui/components/Table.svelte +19 -0
  107. package/dist/ui/components/TableCell.svelte +75 -0
  108. package/dist/ui/components/TableGroupRow.svelte +136 -0
  109. package/dist/ui/components/TableGroupToggle.svelte +42 -0
  110. package/dist/ui/components/TableHeader.svelte +242 -0
  111. package/dist/ui/components/TableRow.svelte +283 -0
  112. package/dist/ui/components/TableSubtotalRow.svelte +62 -0
  113. package/dist/ui/components/TableTotalRow.svelte +88 -0
  114. package/dist/ui/components/TextInput.svelte +92 -0
  115. package/dist/ui/components/_Table.svelte +516 -0
  116. package/dist/ui/internal/clientCache.ts +43 -0
  117. package/dist/ui/internal/queryEngine.ts +169 -0
  118. package/dist/ui/internal/telemetry.ts +28 -0
  119. package/dist/ui/internal/theme.ts +88 -0
  120. package/dist/ui/layout.svelte +3 -0
  121. package/dist/ui/playwright.config.ts +30 -0
  122. package/dist/ui/web.js +106 -0
  123. package/package.json +71 -0
@@ -0,0 +1,172 @@
1
+ <script context="module">
2
+ export const evidenceInclude = true
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import {getContext, onDestroy} from 'svelte'
7
+ import {propKey, strictBuild} from '../component-utilities/chartContext.js'
8
+ import {getThemeStores} from '../component-utilities/themeStores'
9
+ import {toBoolean} from '../component-utilities/convert'
10
+
11
+ export let id: string
12
+ export let description: string | undefined = undefined
13
+ export let contentType: string | undefined = undefined
14
+ export let title: string | undefined = undefined
15
+ export let align: string | undefined = undefined
16
+ export let wrap: boolean | string | undefined = undefined
17
+ export let wrapTitle: boolean | string | undefined = undefined
18
+ export let height: string | undefined = undefined
19
+ export let width: string | undefined = undefined
20
+ export let alt: string | undefined = undefined
21
+ export let openInNewTab: boolean | string | undefined = undefined
22
+ export let linkLabel: string | undefined = undefined
23
+ export let fmt: string | undefined = undefined
24
+ export let totalAgg: string | undefined = undefined
25
+ export let totalFmt: string | undefined = undefined
26
+ export let weightCol: string | undefined = undefined
27
+ export let subtotalFmt: string | undefined = undefined
28
+ export let colorMax: string | undefined = undefined
29
+ export let colorMin: string | undefined = undefined
30
+ export let colorMid: string | undefined = undefined
31
+ export let colorBreakpoints: string[] | undefined = undefined
32
+ export let colorScale: any = 'default'
33
+ export let scaleColumn: string | undefined = undefined
34
+ export let downIsGood: boolean | string | undefined = undefined
35
+ export let showValue: boolean | string | undefined = undefined
36
+ export let deltaSymbol: boolean | string | undefined = undefined
37
+ export let neutralMin: number | string | undefined = 0
38
+ export let neutralMax: number | string | undefined = 0
39
+ export let chip: boolean | string | undefined = undefined
40
+ export let sparkWidth: number | string | undefined = undefined
41
+ export let sparkHeight: number | string | undefined = undefined
42
+ export let sparkColor: string | undefined = undefined
43
+ export let sparkX: string | undefined = undefined
44
+ export let sparkY: string | undefined = undefined
45
+ export let sparkYScale: boolean | string | undefined = undefined
46
+ export let barColor: string | undefined = '#a5cdee'
47
+ export let negativeBarColor: string | undefined = '#fca5a5'
48
+ export let backgroundColor: string | undefined = 'transparent'
49
+ export let hideLabels: boolean | string | undefined = undefined
50
+ export let colGroup: string | undefined = undefined
51
+ export let fmtColumn: string | undefined = undefined
52
+ export let redNegatives: boolean | string | undefined = undefined
53
+
54
+ const {resolveColor, resolveColorPalette} = getThemeStores()
55
+
56
+ let barColorStore = resolveColor(barColor)
57
+ let negativeBarColorStore = resolveColor(negativeBarColor)
58
+ let backgroundColorStore = resolveColor(backgroundColor)
59
+ let colorScaleStore = resolveColorPalette(colorScale)
60
+
61
+ $: barColorStore = resolveColor(barColor)
62
+ $: negativeBarColorStore = resolveColor(negativeBarColor)
63
+ $: backgroundColorStore = resolveColor(backgroundColor)
64
+ $: colorScaleStore = resolveColorPalette(colorScale)
65
+
66
+ const props = getContext(propKey)
67
+
68
+ const identifier = Symbol('GrapheneColumn')
69
+
70
+ wrap = toBoolean(wrap) ?? false
71
+ wrapTitle = toBoolean(wrapTitle) ?? false
72
+ openInNewTab = toBoolean(openInNewTab) ?? false
73
+ downIsGood = toBoolean(downIsGood) ?? false
74
+ showValue = toBoolean(showValue) ?? true
75
+ deltaSymbol = toBoolean(deltaSymbol) ?? true
76
+ chip = toBoolean(chip) ?? false
77
+ sparkYScale = toBoolean(sparkYScale) ?? false
78
+ hideLabels = toBoolean(hideLabels) ?? false
79
+ redNegatives = toBoolean(redNegatives) ?? false
80
+
81
+ const coerceNumber = (value: number | string | undefined): number | undefined => {
82
+ if (value === undefined || value === null || value === '') return undefined
83
+ let parsed = Number(value)
84
+ return Number.isNaN(parsed) ? undefined : parsed
85
+ }
86
+
87
+ const checkColumnName = () => {
88
+ try {
89
+ let data = $props.data?.[0]
90
+ if (!data || !Object.keys(data).includes(id)) {
91
+ let error = `Error in table: ${id} does not exist in the dataset`
92
+ if (strictBuild) throw new Error(error)
93
+ console.warn(error)
94
+ }
95
+ } catch (error) {
96
+ if (strictBuild) throw error
97
+ }
98
+ }
99
+
100
+ const options = () => ({
101
+ identifier,
102
+ id,
103
+ title,
104
+ align,
105
+ wrap,
106
+ wrapTitle,
107
+ contentType,
108
+ height,
109
+ width,
110
+ alt,
111
+ openInNewTab,
112
+ linkLabel,
113
+ fmt,
114
+ fmtColumn,
115
+ totalAgg,
116
+ totalFmt,
117
+ subtotalFmt,
118
+ weightCol,
119
+ downIsGood,
120
+ deltaSymbol,
121
+ chip,
122
+ neutralMin: coerceNumber(neutralMin) ?? 0,
123
+ neutralMax: coerceNumber(neutralMax) ?? 0,
124
+ showValue,
125
+ colorMax,
126
+ colorMin,
127
+ colorMid,
128
+ colorScale: $colorScaleStore,
129
+ colorBreakpoints,
130
+ scaleColumn,
131
+ colGroup,
132
+ description,
133
+ redNegatives,
134
+ sparkWidth,
135
+ sparkHeight,
136
+ sparkColor,
137
+ sparkX,
138
+ sparkY,
139
+ sparkYScale,
140
+ barColor: $barColorStore,
141
+ negativeBarColor: $negativeBarColorStore,
142
+ backgroundColor: $backgroundColorStore,
143
+ hideLabels,
144
+ })
145
+
146
+ const updateProps = () => {
147
+ checkColumnName()
148
+ props.update((state: any) => {
149
+ let next = {...state}
150
+ let existing = next.columns.findIndex((column: any) => column.identifier === identifier)
151
+ let option = options()
152
+ if (existing === -1) {
153
+ next.columns = [...next.columns, option]
154
+ } else {
155
+ next.columns = [
156
+ ...next.columns.slice(0, existing),
157
+ option,
158
+ ...next.columns.slice(existing + 1),
159
+ ]
160
+ }
161
+ return next
162
+ })
163
+ }
164
+
165
+ $: updateProps()
166
+
167
+ onDestroy(() => {
168
+ props.update((state: any) => {
169
+ return {...state, columns: state.columns.filter((column: any) => column.identifier !== identifier)}
170
+ })
171
+ })
172
+ </script>
@@ -0,0 +1,324 @@
1
+ <script lang="ts">
2
+ import {onMount} from 'svelte'
3
+ import {toBoolean} from '../component-utilities/inputUtils'
4
+
5
+ export let name: string
6
+ export let label: string | undefined = undefined
7
+ export let title: string | undefined = undefined
8
+ export let description: string | undefined = undefined
9
+ export let start: string | Date | undefined = undefined
10
+ export let end: string | Date | undefined = undefined
11
+ export let defaultValue: string | undefined = undefined
12
+ export let presetRanges: string | string[] | undefined = undefined
13
+ export let data: string | undefined = undefined
14
+ export let dates: string | undefined = undefined
15
+ export let hideDuringPrint: boolean | string = true
16
+
17
+ 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
+
19
+ let mounted = false
20
+ let queryKey = ''
21
+ let queryHandler: ((res: {rows?: any[]; error?: any}) => void) | null = null
22
+
23
+ let domainStart: string | null = null
24
+ let domainEnd: string | null = null
25
+
26
+ let currentStart: string | null = null
27
+ let currentEnd: string | null = null
28
+ let currentPreset: string = ''
29
+ let touched = false
30
+
31
+ $: hidePrint = toBoolean(hideDuringPrint)
32
+ $: presetList = (() => {
33
+ if (Array.isArray(presetRanges)) return presetRanges
34
+ if (presetRanges) return [presetRanges]
35
+ return DEFAULT_PRESETS
36
+ })()
37
+ $: displayLabel = title || label
38
+
39
+ onMount(() => {
40
+ mounted = true
41
+ currentStart = normalizeInput(start)
42
+ currentEnd = normalizeInput(end)
43
+ if (defaultValue && presetList.includes(defaultValue)) {
44
+ applyPreset(defaultValue, false)
45
+ } else {
46
+ updateParams()
47
+ }
48
+ refreshQuery()
49
+ return () => {
50
+ mounted = false
51
+ if (queryHandler) {
52
+ window.$GRAPHENE?.unsubscribe?.(queryHandler)
53
+ queryHandler = null
54
+ }
55
+ }
56
+ })
57
+
58
+ $: refreshQuery()
59
+
60
+ function refreshQuery () {
61
+ if (!mounted) return
62
+ let key = data && dates ? `${data}::${dates}` : ''
63
+ if (key === queryKey) return
64
+ if (queryHandler) {
65
+ window.$GRAPHENE?.unsubscribe?.(queryHandler)
66
+ queryHandler = null
67
+ }
68
+ queryKey = key
69
+ if (!data || !dates) return
70
+ let handler = (res: {rows?: any[]; error?: any}) => {
71
+ if (res.error || !res.rows?.length) return
72
+ let values = res.rows
73
+ .map(row => normalizeInput(row[dates]))
74
+ .filter((val): val is string => !!val)
75
+ if (!values.length) return
76
+ values.sort()
77
+ domainStart = values[0]
78
+ domainEnd = values[values.length - 1]
79
+ if (!touched) {
80
+ if (defaultValue && presetList.includes(defaultValue)) {
81
+ applyPreset(defaultValue, false)
82
+ } else {
83
+ let startCandidate = currentStart ?? domainStart
84
+ let endCandidate = currentEnd ?? (domainEnd ? addDaysString(domainEnd, 1) : null)
85
+ setRange(startCandidate, endCandidate, currentPreset, false)
86
+ }
87
+ }
88
+ }
89
+ if (typeof window !== 'undefined' && window.$GRAPHENE?.query) {
90
+ window.$GRAPHENE.query(data, [dates], handler)
91
+ queryHandler = handler
92
+ }
93
+ }
94
+
95
+ function normalizeInput (value: string | Date | null | undefined): string | null {
96
+ if (value === null || value === undefined) return null
97
+ if (value instanceof Date) return formatDate(value)
98
+ let parsed = new Date(value)
99
+ if (Number.isNaN(parsed.getTime())) return null
100
+ return formatDate(parsed)
101
+ }
102
+
103
+ function formatDate (value: Date): string {
104
+ let year = value.getFullYear()
105
+ let month = String(value.getMonth() + 1).padStart(2, '0')
106
+ let day = String(value.getDate()).padStart(2, '0')
107
+ return `${year}-${month}-${day}`
108
+ }
109
+
110
+ function addDays (value: Date, days: number): Date {
111
+ let copy = new Date(value)
112
+ copy.setDate(copy.getDate() + days)
113
+ return copy
114
+ }
115
+
116
+ function addDaysString (value: string, days: number): string {
117
+ let parsed = new Date(value)
118
+ if (Number.isNaN(parsed.getTime())) return value
119
+ return formatDate(addDays(parsed, days))
120
+ }
121
+
122
+ function startOfMonth (value: Date): Date {
123
+ return new Date(value.getFullYear(), value.getMonth(), 1)
124
+ }
125
+
126
+ function startOfYear (value: Date): Date {
127
+ return new Date(value.getFullYear(), 0, 1)
128
+ }
129
+
130
+ function addMonths (value: Date, months: number): Date {
131
+ let copy = new Date(value)
132
+ copy.setMonth(copy.getMonth() + months)
133
+ return copy
134
+ }
135
+
136
+ function addYears (value: Date, years: number): Date {
137
+ let copy = new Date(value)
138
+ copy.setFullYear(copy.getFullYear() + years)
139
+ return copy
140
+ }
141
+
142
+ function setRange (startValue: string | null, endValue: string | null, preset: string, markTouched: boolean) {
143
+ currentStart = startValue
144
+ currentEnd = endValue
145
+ currentPreset = preset
146
+ if (markTouched) touched = true
147
+ updateParams()
148
+ }
149
+
150
+ function updateParams () {
151
+ window.$GRAPHENE.updateParam(`${name}_start`, currentStart)
152
+ window.$GRAPHENE.updateParam(`${name}_end`, currentEnd)
153
+ }
154
+
155
+ function onStartChange (event: Event) {
156
+ let value = (event.currentTarget as HTMLInputElement).value || null
157
+ setRange(value, currentEnd, '', true)
158
+ }
159
+
160
+ function onEndChange (event: Event) {
161
+ let value = (event.currentTarget as HTMLInputElement).value || null
162
+ setRange(currentStart, value, '', true)
163
+ }
164
+
165
+ function applyPreset (preset: string, markTouched = true) {
166
+ let baseEnd = (() => {
167
+ if (currentEnd) return new Date(currentEnd)
168
+ if (domainEnd) return new Date(domainEnd)
169
+ return new Date()
170
+ })()
171
+ if (Number.isNaN(baseEnd.getTime())) baseEnd = new Date()
172
+ let range = computePresetRange(preset, baseEnd)
173
+ if (!range) return
174
+ let startVal = range.start ? formatDate(range.start) : null
175
+ let endVal = range.end ? formatDate(range.end) : null
176
+ setRange(startVal, endVal, preset, markTouched)
177
+ }
178
+
179
+ function computePresetRange (preset: string, baseEndInclusive: Date): {start: Date | null; end: Date | null} | null {
180
+ let label = preset.trim()
181
+ let today = new Date()
182
+ let endExclusive = addDays(baseEndInclusive, 1)
183
+
184
+ let lastDaysMatch = label.match(/^Last (\d+) Days$/i)
185
+ if (lastDaysMatch) {
186
+ let days = parseInt(lastDaysMatch[1], 10)
187
+ let startDate = addDays(endExclusive, -days)
188
+ return {start: startDate, end: endExclusive}
189
+ }
190
+
191
+ let lastMonthsMatch = label.match(/^Last (\d+) Months$/i)
192
+ if (lastMonthsMatch) {
193
+ let months = parseInt(lastMonthsMatch[1], 10)
194
+ let monthEnd = startOfMonth(endExclusive)
195
+ let startDate = addMonths(monthEnd, -months)
196
+ return {start: startDate, end: monthEnd}
197
+ }
198
+
199
+ if (label === 'Last Month') {
200
+ let monthEnd = startOfMonth(endExclusive)
201
+ let startDate = addMonths(monthEnd, -1)
202
+ return {start: startDate, end: monthEnd}
203
+ }
204
+
205
+ if (label === 'Last Year') {
206
+ let yearEnd = startOfYear(endExclusive)
207
+ let startDate = addYears(yearEnd, -1)
208
+ return {start: startDate, end: yearEnd}
209
+ }
210
+
211
+ if (label === 'Last 365 Days') {
212
+ let startDate = addDays(endExclusive, -365)
213
+ return {start: startDate, end: endExclusive}
214
+ }
215
+
216
+ if (label === 'Month to Date') {
217
+ let startDate = startOfMonth(endExclusive)
218
+ return {start: startDate, end: endExclusive}
219
+ }
220
+
221
+ if (label === 'Month to Today') {
222
+ let startDate = startOfMonth(today)
223
+ let endDate = addDays(today, 1)
224
+ return {start: startDate, end: endDate}
225
+ }
226
+
227
+ if (label === 'Year to Date') {
228
+ let startDate = startOfYear(endExclusive)
229
+ return {start: startDate, end: endExclusive}
230
+ }
231
+
232
+ if (label === 'Year to Today') {
233
+ let startDate = startOfYear(today)
234
+ let endDate = addDays(today, 1)
235
+ return {start: startDate, end: endDate}
236
+ }
237
+
238
+ if (label === 'All Time') {
239
+ let startDate = domainStart ? new Date(domainStart) : null
240
+ let endDate = domainEnd ? addDays(new Date(domainEnd), 1) : endExclusive
241
+ return {start: startDate, end: endDate}
242
+ }
243
+
244
+ return null
245
+ }
246
+
247
+ function onPresetChange (event: Event) {
248
+ let value = (event.currentTarget as HTMLSelectElement).value
249
+ if (!value) {
250
+ currentPreset = ''
251
+ touched = true
252
+ return
253
+ }
254
+ applyPreset(value, true)
255
+ }
256
+ </script>
257
+
258
+ <div class={`input-block${hidePrint ? ' hide-print' : ''}`}>
259
+ {#if displayLabel}
260
+ <label class="input-label" for={`daterange-${name}-start`}>{displayLabel}</label>
261
+ {/if}
262
+ {#if description}
263
+ <div class="input-description">{description}</div>
264
+ {/if}
265
+ <div class="range-row">
266
+ <input id={`daterange-${name}-start`} class="date-input" type="date" value={currentStart || ''} on:change={onStartChange} />
267
+ <span class="range-separator">to</span>
268
+ <input id={`daterange-${name}-end`} class="date-input" type="date" value={currentEnd || ''} on:change={onEndChange} />
269
+ </div>
270
+ {#if presetList.length}
271
+ <select class="preset-select" on:change={onPresetChange}>
272
+ <option value="">Custom range</option>
273
+ {#each presetList as preset (preset)}
274
+ <option value={preset} selected={preset === currentPreset}>{preset}</option>
275
+ {/each}
276
+ </select>
277
+ {/if}
278
+ </div>
279
+
280
+ <style>
281
+ .input-block {
282
+ display: flex;
283
+ flex-direction: column;
284
+ gap: 6px;
285
+ margin: 8px 0;
286
+ }
287
+ @media print {
288
+ .hide-print {
289
+ display: none !important;
290
+ }
291
+ }
292
+ .input-label {
293
+ font-size: 12px;
294
+ font-weight: 600;
295
+ color: var(--input-label-color, #374151);
296
+ }
297
+ .input-description {
298
+ font-size: 12px;
299
+ color: rgba(55, 65, 81, 0.8);
300
+ }
301
+ .range-row {
302
+ display: flex;
303
+ align-items: center;
304
+ gap: 8px;
305
+ }
306
+ .range-separator {
307
+ font-size: 12px;
308
+ color: rgba(55, 65, 81, 0.9);
309
+ }
310
+ .date-input {
311
+ padding: 6px 8px;
312
+ border-radius: 6px;
313
+ border: 1px solid rgba(107, 114, 128, 0.4);
314
+ font-size: 14px;
315
+ min-width: 150px;
316
+ }
317
+ .preset-select {
318
+ max-width: 220px;
319
+ padding: 6px 8px;
320
+ border-radius: 6px;
321
+ border: 1px solid rgba(107, 114, 128, 0.4);
322
+ font-size: 13px;
323
+ }
324
+ </style>