@graphenedata/cli 0.0.14 → 0.0.16
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/LICENSE.md +3 -3
- package/README.md +138 -0
- package/THIRD_PARTY_NOTICES.md +1 -0
- package/bin.js +2 -2
- package/dist/cli/bigQuery-I3F46SC6.js +75 -0
- package/dist/cli/bigQuery-I3F46SC6.js.map +7 -0
- package/dist/cli/chunk-OVWODUTJ.js +12849 -0
- package/dist/cli/chunk-OVWODUTJ.js.map +7 -0
- package/dist/cli/chunk-QAXEOZ43.js +53 -0
- package/dist/cli/chunk-QAXEOZ43.js.map +7 -0
- package/dist/cli/cli.js +245 -10290
- package/dist/cli/clickhouse-ZN5AN2UL.js +64 -0
- package/dist/cli/clickhouse-ZN5AN2UL.js.map +7 -0
- package/dist/cli/duckdb-IYBIO5KJ.js +87 -0
- package/dist/cli/duckdb-IYBIO5KJ.js.map +7 -0
- package/dist/cli/serve2-TNN5EROW.js +447 -0
- package/dist/cli/serve2-TNN5EROW.js.map +7 -0
- package/dist/cli/snowflake-MOQB5GA4.js +128 -0
- package/dist/cli/snowflake-MOQB5GA4.js.map +7 -0
- package/dist/index.d.ts +63 -0
- package/dist/lang/index.d.ts +63 -0
- package/dist/skills/graphene/SKILL.md +235 -0
- package/dist/skills/graphene/references/big-value.md +20 -0
- package/dist/skills/graphene/references/date-range.md +64 -0
- package/dist/skills/graphene/references/dropdown.md +62 -0
- package/dist/skills/graphene/references/echarts.md +162 -0
- package/dist/skills/graphene/references/gsql.md +393 -0
- package/dist/skills/graphene/references/model-gsql.md +72 -0
- package/dist/skills/graphene/references/table.md +143 -0
- package/dist/skills/graphene/references/text-input.md +29 -0
- package/dist/ui/app.css +263 -299
- package/dist/ui/component-utilities/dataShaping.ts +484 -0
- package/dist/ui/component-utilities/dataSummary.ts +57 -0
- package/dist/ui/component-utilities/enrich.ts +763 -0
- package/dist/ui/component-utilities/format.ts +177 -0
- package/dist/ui/component-utilities/inputUtils.ts +48 -9
- package/dist/ui/component-utilities/theme.ts +200 -0
- package/dist/ui/component-utilities/themeStores.ts +26 -21
- package/dist/ui/component-utilities/types.ts +70 -0
- package/dist/ui/components/AreaChart.svelte +57 -105
- package/dist/ui/components/BarChart.svelte +71 -129
- package/dist/ui/components/BigValue.svelte +24 -40
- package/dist/ui/components/Column.svelte +11 -19
- package/dist/ui/components/DateRange.svelte +71 -34
- package/dist/ui/components/Dropdown.svelte +82 -49
- package/dist/ui/components/DropdownOption.svelte +1 -2
- package/dist/ui/components/ECharts.svelte +179 -60
- package/dist/ui/components/InlineDelta.svelte +51 -32
- package/dist/ui/components/LineChart.svelte +54 -125
- package/dist/ui/components/PieChart.svelte +27 -37
- package/dist/ui/components/QueryLoad.svelte +78 -44
- package/dist/ui/components/Row.svelte +2 -1
- package/dist/ui/components/ScatterPlot.svelte +52 -0
- package/dist/ui/components/Skeleton.svelte +32 -0
- package/dist/ui/components/Table.svelte +3 -2
- package/dist/ui/components/TableGroupRow.svelte +28 -36
- package/dist/ui/components/TableHarness.svelte +32 -0
- package/dist/ui/components/TableHeader.svelte +34 -59
- package/dist/ui/components/TableRow.svelte +15 -39
- package/dist/ui/components/TableSubtotalRow.svelte +26 -21
- package/dist/ui/components/TableTotalRow.svelte +27 -37
- package/dist/ui/components/TextInput.svelte +17 -14
- package/dist/ui/components/Value.svelte +25 -0
- package/dist/ui/components/_Table.svelte +80 -76
- package/dist/ui/internal/ChartGallery.svelte +527 -0
- package/dist/ui/internal/ErrorDisplay.svelte +60 -0
- package/dist/ui/internal/LocalApp.svelte +87 -19
- package/dist/ui/internal/PageNavGroup.svelte +269 -0
- package/dist/ui/internal/Sidebar.svelte +178 -0
- package/dist/ui/internal/SidebarToggle.svelte +47 -0
- package/dist/ui/internal/StyleGallery.svelte +244 -0
- package/dist/ui/internal/clientCache.ts +15 -13
- package/dist/ui/internal/pageInputs.svelte.js +292 -0
- package/dist/ui/internal/queryEngine.ts +124 -132
- package/dist/ui/internal/runSocket.ts +59 -0
- package/dist/ui/internal/sidebar.svelte.js +18 -0
- package/dist/ui/internal/telemetry.ts +52 -17
- package/dist/ui/internal/types.d.ts +7 -0
- package/dist/ui/web.js +55 -13
- package/package.json +40 -41
- package/dist/docs/agent-instructions.md +0 -18
- package/dist/docs/base.md +0 -98
- package/dist/docs/cli.md +0 -22
- package/dist/docs/graphene.md +0 -1462
- package/dist/ui/component-utilities/autoFormatting.js +0 -301
- package/dist/ui/component-utilities/builtInFormats.js +0 -482
- package/dist/ui/component-utilities/chartContext.js +0 -12
- package/dist/ui/component-utilities/chartWindowDebug.js +0 -21
- package/dist/ui/component-utilities/checkInputs.js +0 -95
- package/dist/ui/component-utilities/convert.js +0 -15
- package/dist/ui/component-utilities/dateParsing.js +0 -57
- package/dist/ui/component-utilities/dropdownContext.ts +0 -1
- package/dist/ui/component-utilities/echarts.js +0 -272
- package/dist/ui/component-utilities/echartsThemes.js +0 -453
- package/dist/ui/component-utilities/formatTitle.js +0 -24
- package/dist/ui/component-utilities/formatting.js +0 -250
- package/dist/ui/component-utilities/getColumnExtents.js +0 -79
- package/dist/ui/component-utilities/getColumnSummary.js +0 -67
- package/dist/ui/component-utilities/getCompletedData.js +0 -114
- package/dist/ui/component-utilities/getDistinctCount.js +0 -7
- package/dist/ui/component-utilities/getDistinctValues.js +0 -15
- package/dist/ui/component-utilities/getSeriesConfig.js +0 -237
- package/dist/ui/component-utilities/getSortedData.js +0 -7
- package/dist/ui/component-utilities/getStackPercentages.js +0 -43
- package/dist/ui/component-utilities/getStackedData.js +0 -17
- package/dist/ui/component-utilities/getYAxisIndex.js +0 -15
- package/dist/ui/component-utilities/globalContexts.js +0 -1
- package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +0 -119
- package/dist/ui/component-utilities/replaceNulls.js +0 -14
- package/dist/ui/component-utilities/tableUtils.ts +0 -120
- package/dist/ui/components/Area.svelte +0 -214
- package/dist/ui/components/Bar.svelte +0 -350
- package/dist/ui/components/Chart.svelte +0 -989
- package/dist/ui/components/ErrorChart.svelte +0 -118
- package/dist/ui/components/Line.svelte +0 -227
- package/dist/ui/internal/NavSidebar.svelte +0 -396
- package/dist/ui/internal/PageError.svelte +0 -23
- package/dist/ui/internal/checkSocket.ts +0 -48
- package/dist/ui/internal/theme.ts +0 -88
- package/dist/ui/public/inter-latin-ext.woff2 +0 -0
- package/dist/ui/public/inter-latin.woff2 +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import {onMount} from 'svelte'
|
|
3
3
|
import {toBoolean} from '../component-utilities/inputUtils'
|
|
4
|
+
import {captureInitial, getPageInputs} from '../internal/pageInputs.svelte.js'
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
7
|
name: string
|
|
@@ -27,13 +28,15 @@
|
|
|
27
28
|
let mounted = false
|
|
28
29
|
let queryKey = ''
|
|
29
30
|
let queryHandler: ((res: {rows?: any[]; error?: any}) => void) | null = null
|
|
31
|
+
let pageInputs = getPageInputs()
|
|
32
|
+
let field = captureInitial(() => pageInputs.dateRange(name))
|
|
30
33
|
|
|
31
34
|
let domainStart: string | null = $state(null)
|
|
32
35
|
let domainEnd: string | null = $state(null)
|
|
33
36
|
|
|
34
37
|
let currentStart: string | null = $state(null)
|
|
35
38
|
let currentEnd: string | null = $state(null)
|
|
36
|
-
let currentPreset: string = $state(
|
|
39
|
+
let currentPreset: string | null = $state(null)
|
|
37
40
|
let touched = false
|
|
38
41
|
|
|
39
42
|
let hidePrint = $derived(toBoolean(hideDuringPrint))
|
|
@@ -46,16 +49,16 @@
|
|
|
46
49
|
|
|
47
50
|
onMount(() => {
|
|
48
51
|
mounted = true
|
|
49
|
-
currentStart = normalizeInput(start)
|
|
50
|
-
currentEnd = normalizeInput(end)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
52
|
+
currentStart = field.hasExternalValue ? field.value.start : normalizeInput(start)
|
|
53
|
+
currentEnd = field.hasExternalValue ? field.value.end : normalizeInput(end)
|
|
54
|
+
currentPreset = inferPreset(currentStart, currentEnd)
|
|
55
|
+
if (field.hasExternalValue) updateParams()
|
|
56
|
+
else if (defaultValue && presetList.includes(defaultValue)) applyPreset(defaultValue, false)
|
|
57
|
+
else updateParams()
|
|
56
58
|
refreshQuery()
|
|
57
59
|
return () => {
|
|
58
60
|
mounted = false
|
|
61
|
+
field.destroy()
|
|
59
62
|
if (queryHandler) {
|
|
60
63
|
window.$GRAPHENE?.unsubscribe?.(queryHandler)
|
|
61
64
|
queryHandler = null
|
|
@@ -67,7 +70,13 @@
|
|
|
67
70
|
refreshQuery()
|
|
68
71
|
})
|
|
69
72
|
|
|
70
|
-
|
|
73
|
+
$effect(() => {
|
|
74
|
+
if (currentStart === field.value.start && currentEnd === field.value.end) return
|
|
75
|
+
if (!mounted) return
|
|
76
|
+
setRange(field.value.start, field.value.end, inferPreset(field.value.start, field.value.end), {persist: false})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
function refreshQuery() {
|
|
71
80
|
if (!mounted) return
|
|
72
81
|
let key = data && dates ? `${data}::${dates}` : ''
|
|
73
82
|
if (key === queryKey) return
|
|
@@ -78,7 +87,7 @@
|
|
|
78
87
|
queryKey = key
|
|
79
88
|
if (!data || !dates) return
|
|
80
89
|
let handler = (res: {rows?: any[]; error?: any}) => {
|
|
81
|
-
if (res.error || !res.rows?.length) return
|
|
90
|
+
if (!res || res.error || !res.rows?.length) return
|
|
82
91
|
let values = res.rows
|
|
83
92
|
.map(row => normalizeInput(row[dates]))
|
|
84
93
|
.filter((val): val is string => !!val)
|
|
@@ -86,13 +95,15 @@
|
|
|
86
95
|
values.sort()
|
|
87
96
|
domainStart = values[0]
|
|
88
97
|
domainEnd = values[values.length - 1]
|
|
89
|
-
if (
|
|
98
|
+
if (field.hasExternalValue) {
|
|
99
|
+
currentPreset = inferPreset(currentStart, currentEnd)
|
|
100
|
+
} else if (!touched) {
|
|
90
101
|
if (defaultValue && presetList.includes(defaultValue)) {
|
|
91
102
|
applyPreset(defaultValue, false)
|
|
92
103
|
} else {
|
|
93
104
|
let startCandidate = currentStart ?? domainStart
|
|
94
105
|
let endCandidate = currentEnd ?? (domainEnd ? addDaysString(domainEnd, 1) : null)
|
|
95
|
-
setRange(startCandidate, endCandidate, currentPreset, false)
|
|
106
|
+
setRange(startCandidate, endCandidate, currentPreset, {markTouched: false, persist: true})
|
|
96
107
|
}
|
|
97
108
|
}
|
|
98
109
|
}
|
|
@@ -102,7 +113,7 @@
|
|
|
102
113
|
}
|
|
103
114
|
}
|
|
104
115
|
|
|
105
|
-
function normalizeInput
|
|
116
|
+
function normalizeInput(value: string | Date | null | undefined): string | null {
|
|
106
117
|
if (value === null || value === undefined) return null
|
|
107
118
|
if (value instanceof Date) return formatDate(value)
|
|
108
119
|
let parsed = new Date(value)
|
|
@@ -110,69 +121,90 @@
|
|
|
110
121
|
return formatDate(parsed)
|
|
111
122
|
}
|
|
112
123
|
|
|
113
|
-
function formatDate
|
|
124
|
+
function formatDate(value: Date): string {
|
|
114
125
|
let year = value.getFullYear()
|
|
115
126
|
let month = String(value.getMonth() + 1).padStart(2, '0')
|
|
116
127
|
let day = String(value.getDate()).padStart(2, '0')
|
|
117
128
|
return `${year}-${month}-${day}`
|
|
118
129
|
}
|
|
119
130
|
|
|
120
|
-
function addDays
|
|
131
|
+
function addDays(value: Date, days: number): Date {
|
|
121
132
|
let copy = new Date(value) // eslint-disable-line svelte/prefer-svelte-reactivity
|
|
122
133
|
copy.setDate(copy.getDate() + days)
|
|
123
134
|
return copy
|
|
124
135
|
}
|
|
125
136
|
|
|
126
|
-
function addDaysString
|
|
137
|
+
function addDaysString(value: string, days: number): string {
|
|
127
138
|
let parsed = new Date(value)
|
|
128
139
|
if (Number.isNaN(parsed.getTime())) return value
|
|
129
140
|
return formatDate(addDays(parsed, days))
|
|
130
141
|
}
|
|
131
142
|
|
|
132
|
-
function startOfMonth
|
|
143
|
+
function startOfMonth(value: Date): Date {
|
|
133
144
|
return new Date(value.getFullYear(), value.getMonth(), 1)
|
|
134
145
|
}
|
|
135
146
|
|
|
136
|
-
function startOfYear
|
|
147
|
+
function startOfYear(value: Date): Date {
|
|
137
148
|
return new Date(value.getFullYear(), 0, 1)
|
|
138
149
|
}
|
|
139
150
|
|
|
140
|
-
function addMonths
|
|
151
|
+
function addMonths(value: Date, months: number): Date {
|
|
141
152
|
let copy = new Date(value) // eslint-disable-line svelte/prefer-svelte-reactivity
|
|
142
153
|
copy.setMonth(copy.getMonth() + months)
|
|
143
154
|
return copy
|
|
144
155
|
}
|
|
145
156
|
|
|
146
|
-
function addYears
|
|
157
|
+
function addYears(value: Date, years: number): Date {
|
|
147
158
|
let copy = new Date(value) // eslint-disable-line svelte/prefer-svelte-reactivity
|
|
148
159
|
copy.setFullYear(copy.getFullYear() + years)
|
|
149
160
|
return copy
|
|
150
161
|
}
|
|
151
162
|
|
|
152
|
-
|
|
163
|
+
// We only persist start/end in URL state, so the preset label is inferred by matching
|
|
164
|
+
// the current range against the configured preset definitions.
|
|
165
|
+
function inferPreset(startValue: string | null, endValue: string | null): string | null {
|
|
166
|
+
if (!startValue && !endValue) return null
|
|
167
|
+
let baseEnd = (() => {
|
|
168
|
+
if (endValue) {
|
|
169
|
+
let parsed = new Date(endValue)
|
|
170
|
+
if (!Number.isNaN(parsed.getTime())) return addDays(parsed, -1)
|
|
171
|
+
}
|
|
172
|
+
if (domainEnd) return new Date(domainEnd)
|
|
173
|
+
return new Date()
|
|
174
|
+
})()
|
|
175
|
+
if (Number.isNaN(baseEnd.getTime())) return null
|
|
176
|
+
for (let preset of presetList) {
|
|
177
|
+
let range = computePresetRange(preset, baseEnd)
|
|
178
|
+
let presetStart = range?.start ? formatDate(range.start) : null
|
|
179
|
+
let presetEnd = range?.end ? formatDate(range.end) : null
|
|
180
|
+
if (presetStart === startValue && presetEnd === endValue) return preset
|
|
181
|
+
}
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function setRange(startValue: string | null, endValue: string | null, preset: string | null, {markTouched = false, persist = true}: {markTouched?: boolean; persist?: boolean} = {}) {
|
|
153
186
|
currentStart = startValue
|
|
154
187
|
currentEnd = endValue
|
|
155
188
|
currentPreset = preset
|
|
156
189
|
if (markTouched) touched = true
|
|
157
|
-
updateParams()
|
|
190
|
+
if (persist) updateParams()
|
|
158
191
|
}
|
|
159
192
|
|
|
160
|
-
function updateParams
|
|
161
|
-
|
|
162
|
-
window.$GRAPHENE.updateParam(`${name}_end`, currentEnd)
|
|
193
|
+
function updateParams() {
|
|
194
|
+
field.set({start: currentStart, end: currentEnd})
|
|
163
195
|
}
|
|
164
196
|
|
|
165
|
-
function onStartChange
|
|
197
|
+
function onStartChange(event: Event) {
|
|
166
198
|
let value = (event.currentTarget as HTMLInputElement).value || null
|
|
167
|
-
setRange(value, currentEnd,
|
|
199
|
+
setRange(value, currentEnd, null, {markTouched: true, persist: true})
|
|
168
200
|
}
|
|
169
201
|
|
|
170
|
-
function onEndChange
|
|
202
|
+
function onEndChange(event: Event) {
|
|
171
203
|
let value = (event.currentTarget as HTMLInputElement).value || null
|
|
172
|
-
setRange(currentStart, value,
|
|
204
|
+
setRange(currentStart, value, null, {markTouched: true, persist: true})
|
|
173
205
|
}
|
|
174
206
|
|
|
175
|
-
function applyPreset
|
|
207
|
+
function applyPreset(preset: string, markTouched = true) {
|
|
176
208
|
let baseEnd = (() => {
|
|
177
209
|
if (currentEnd) return new Date(currentEnd)
|
|
178
210
|
if (domainEnd) return new Date(domainEnd)
|
|
@@ -183,10 +215,10 @@
|
|
|
183
215
|
if (!range) return
|
|
184
216
|
let startVal = range.start ? formatDate(range.start) : null
|
|
185
217
|
let endVal = range.end ? formatDate(range.end) : null
|
|
186
|
-
setRange(startVal, endVal, preset, markTouched)
|
|
218
|
+
setRange(startVal, endVal, preset, {markTouched, persist: true})
|
|
187
219
|
}
|
|
188
220
|
|
|
189
|
-
function computePresetRange
|
|
221
|
+
function computePresetRange(preset: string, baseEndInclusive: Date): {start: Date | null; end: Date | null} | null {
|
|
190
222
|
let label = preset.trim()
|
|
191
223
|
let today = new Date()
|
|
192
224
|
let endExclusive = addDays(baseEndInclusive, 1)
|
|
@@ -254,10 +286,10 @@
|
|
|
254
286
|
return null
|
|
255
287
|
}
|
|
256
288
|
|
|
257
|
-
function onPresetChange
|
|
289
|
+
function onPresetChange(event: Event) {
|
|
258
290
|
let value = (event.currentTarget as HTMLSelectElement).value
|
|
259
291
|
if (!value) {
|
|
260
|
-
currentPreset =
|
|
292
|
+
currentPreset = null
|
|
261
293
|
touched = true
|
|
262
294
|
return
|
|
263
295
|
}
|
|
@@ -300,6 +332,7 @@
|
|
|
300
332
|
}
|
|
301
333
|
}
|
|
302
334
|
.input-label {
|
|
335
|
+
font-family: var(--font-ui);
|
|
303
336
|
font-size: 12px;
|
|
304
337
|
font-weight: 600;
|
|
305
338
|
color: var(--input-label-color, #374151);
|
|
@@ -323,6 +356,8 @@
|
|
|
323
356
|
border: 1px solid rgba(107, 114, 128, 0.4);
|
|
324
357
|
font-size: 14px;
|
|
325
358
|
min-width: 150px;
|
|
359
|
+
font-family: var(--font-ui);
|
|
360
|
+
font-synthesis: none;
|
|
326
361
|
}
|
|
327
362
|
.preset-select {
|
|
328
363
|
max-width: 220px;
|
|
@@ -330,5 +365,7 @@
|
|
|
330
365
|
border-radius: 6px;
|
|
331
366
|
border: 1px solid rgba(107, 114, 128, 0.4);
|
|
332
367
|
font-size: 13px;
|
|
368
|
+
font-family: var(--font-ui);
|
|
369
|
+
font-synthesis: none;
|
|
333
370
|
}
|
|
334
371
|
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import {onMount, setContext, tick, type Snippet} from 'svelte'
|
|
3
|
-
import {DROPDOWN_CONTEXT} from '../component-utilities/dropdownContext'
|
|
4
3
|
import {ensureArray, toBoolean} from '../component-utilities/inputUtils'
|
|
4
|
+
import {captureInitial, getPageInputs} from '../internal/pageInputs.svelte.js'
|
|
5
5
|
|
|
6
6
|
interface Option {
|
|
7
7
|
value: any
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
let touched = false
|
|
43
43
|
let queryHandler: ((res: {rows?: any[]; error?: any}) => void) | null = null
|
|
44
44
|
let queryKey = ''
|
|
45
|
+
let pageInputs = getPageInputs()
|
|
46
|
+
let field = captureInitial(() => pageInputs.dropdown(name, toBoolean(multiple)))
|
|
45
47
|
|
|
46
48
|
let isOpen = $state(false)
|
|
47
49
|
let searchTerm = $state('')
|
|
@@ -59,7 +61,7 @@
|
|
|
59
61
|
syncSelection(false)
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
|
-
setContext(
|
|
64
|
+
setContext('dropdown', registerOption)
|
|
63
65
|
|
|
64
66
|
const optionKey = (val: any): string => {
|
|
65
67
|
if (val === null) return 'null'
|
|
@@ -80,7 +82,7 @@
|
|
|
80
82
|
let key = optionKey(opt.value)
|
|
81
83
|
if (!map.has(key)) map.set(key, opt)
|
|
82
84
|
}
|
|
83
|
-
return Array.from(map.values())
|
|
85
|
+
return Array.from(map.values()).sort((a, b) => String(a.label ?? a.value ?? '').localeCompare(String(b.label ?? b.value ?? ''), undefined, {numeric: true}))
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
let multi = $derived(toBoolean(multiple))
|
|
@@ -104,7 +106,13 @@
|
|
|
104
106
|
if (isOpen) activeIndex = ensureActiveIndex(activeIndex, filteredOptions)
|
|
105
107
|
})
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
$effect(() => {
|
|
110
|
+
if (!sameSelection(selection, field.value)) {
|
|
111
|
+
setSelection(field.value, {persist: false})
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
function setupQuery() {
|
|
108
116
|
if (!mounted) return
|
|
109
117
|
let key = data ? `${data}::${value}::${resolvedLabelField || ''}` : ''
|
|
110
118
|
if (key === queryKey) return
|
|
@@ -121,7 +129,7 @@
|
|
|
121
129
|
let columns = [value]
|
|
122
130
|
if (resolvedLabelField && resolvedLabelField !== value) columns.push(resolvedLabelField)
|
|
123
131
|
let handler = (res: {rows?: any[]; error?: any}) => {
|
|
124
|
-
if (res.error) return
|
|
132
|
+
if (!res || res.error) return
|
|
125
133
|
if (!res.rows) return
|
|
126
134
|
queryOptions = res.rows.map(row => ({
|
|
127
135
|
value: row[value],
|
|
@@ -142,7 +150,7 @@
|
|
|
142
150
|
closeMenu(false)
|
|
143
151
|
}
|
|
144
152
|
|
|
145
|
-
function filterOptions
|
|
153
|
+
function filterOptions(opts: Option[], term: string): Option[] {
|
|
146
154
|
let trimmed = term.trim().toLowerCase()
|
|
147
155
|
if (!trimmed) return opts
|
|
148
156
|
return opts.filter(opt => {
|
|
@@ -151,24 +159,24 @@
|
|
|
151
159
|
})
|
|
152
160
|
}
|
|
153
161
|
|
|
154
|
-
function ensureActiveIndex
|
|
162
|
+
function ensureActiveIndex(current: number, opts: Option[]): number {
|
|
155
163
|
if (!opts.length) return -1
|
|
156
164
|
if (current >= 0 && current < opts.length) return current
|
|
157
165
|
let selectedIdx = opts.findIndex(opt => isOptionSelected(opt))
|
|
158
166
|
return selectedIdx >= 0 ? selectedIdx : 0
|
|
159
167
|
}
|
|
160
168
|
|
|
161
|
-
function isOptionSelected
|
|
169
|
+
function isOptionSelected(opt: Option): boolean {
|
|
162
170
|
let key = optionKey(opt.value)
|
|
163
171
|
return selection.some(val => optionKey(val) === key)
|
|
164
172
|
}
|
|
165
173
|
|
|
166
|
-
async function focusSearchInput
|
|
174
|
+
async function focusSearchInput() {
|
|
167
175
|
await tick()
|
|
168
176
|
if (searchInput) searchInput.focus()
|
|
169
177
|
}
|
|
170
178
|
|
|
171
|
-
function updateTriggerWidth
|
|
179
|
+
function updateTriggerWidth() {
|
|
172
180
|
if (!triggerEl) {
|
|
173
181
|
if (triggerWidth !== 0) triggerWidth = 0
|
|
174
182
|
return
|
|
@@ -177,14 +185,14 @@
|
|
|
177
185
|
if (width !== triggerWidth) triggerWidth = width
|
|
178
186
|
}
|
|
179
187
|
|
|
180
|
-
function openMenu
|
|
188
|
+
function openMenu(focusSearch = true) {
|
|
181
189
|
if (isDisabled) return
|
|
182
190
|
updateTriggerWidth()
|
|
183
191
|
isOpen = true
|
|
184
192
|
if (focusSearch) focusSearchInput()
|
|
185
193
|
}
|
|
186
194
|
|
|
187
|
-
function closeMenu
|
|
195
|
+
function closeMenu(focusTrigger: boolean) {
|
|
188
196
|
if (!isOpen) return
|
|
189
197
|
isOpen = false
|
|
190
198
|
activeIndex = -1
|
|
@@ -192,7 +200,7 @@
|
|
|
192
200
|
if (focusTrigger) triggerEl?.focus()
|
|
193
201
|
}
|
|
194
202
|
|
|
195
|
-
function toggleMenu
|
|
203
|
+
function toggleMenu() {
|
|
196
204
|
if (isDisabled) return
|
|
197
205
|
if (isOpen) {
|
|
198
206
|
closeMenu(false)
|
|
@@ -201,7 +209,7 @@
|
|
|
201
209
|
}
|
|
202
210
|
}
|
|
203
211
|
|
|
204
|
-
function moveActive
|
|
212
|
+
function moveActive(delta: number) {
|
|
205
213
|
if (!filteredOptions.length) {
|
|
206
214
|
activeIndex = -1
|
|
207
215
|
return
|
|
@@ -215,12 +223,12 @@
|
|
|
215
223
|
activeIndex = next
|
|
216
224
|
}
|
|
217
225
|
|
|
218
|
-
function selectActiveOption
|
|
226
|
+
function selectActiveOption() {
|
|
219
227
|
if (!filteredOptions.length || activeIndex < 0 || activeIndex >= filteredOptions.length) return
|
|
220
228
|
handleOptionSelect(filteredOptions[activeIndex], true)
|
|
221
229
|
}
|
|
222
230
|
|
|
223
|
-
function handleTriggerKeydown
|
|
231
|
+
function handleTriggerKeydown(event: KeyboardEvent) {
|
|
224
232
|
if (event.defaultPrevented) return
|
|
225
233
|
if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter' || event.key === ' ') {
|
|
226
234
|
event.preventDefault()
|
|
@@ -236,7 +244,7 @@
|
|
|
236
244
|
}
|
|
237
245
|
}
|
|
238
246
|
|
|
239
|
-
function handleMenuKeydown
|
|
247
|
+
function handleMenuKeydown(event: KeyboardEvent) {
|
|
240
248
|
if (event.key === 'ArrowDown') {
|
|
241
249
|
event.preventDefault()
|
|
242
250
|
moveActive(1)
|
|
@@ -260,30 +268,30 @@
|
|
|
260
268
|
}
|
|
261
269
|
}
|
|
262
270
|
|
|
263
|
-
function handleOptionKeydown
|
|
271
|
+
function handleOptionKeydown(event: KeyboardEvent, opt: Option) {
|
|
264
272
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
265
273
|
event.preventDefault()
|
|
266
274
|
handleOptionSelect(opt, true)
|
|
267
275
|
}
|
|
268
276
|
}
|
|
269
277
|
|
|
270
|
-
function handleOptionSelect
|
|
278
|
+
function handleOptionSelect(opt: Option, fromKeyboard: boolean) {
|
|
271
279
|
let key = optionKey(opt.value)
|
|
272
280
|
if (multi) {
|
|
273
281
|
let exists = selection.some(val => optionKey(val) === key)
|
|
274
282
|
if (exists) {
|
|
275
283
|
let next = selection.filter(val => optionKey(val) !== key)
|
|
276
|
-
setSelection(next, true)
|
|
284
|
+
setSelection(next, {fromUser: true, persist: true})
|
|
277
285
|
} else {
|
|
278
|
-
setSelection([...selection, opt.value], true)
|
|
286
|
+
setSelection([...selection, opt.value], {fromUser: true, persist: true})
|
|
279
287
|
}
|
|
280
288
|
} else {
|
|
281
|
-
setSelection([opt.value], true)
|
|
289
|
+
setSelection([opt.value], {fromUser: true, persist: true})
|
|
282
290
|
closeMenu(fromKeyboard)
|
|
283
291
|
}
|
|
284
292
|
}
|
|
285
293
|
|
|
286
|
-
function scrollActiveIntoView
|
|
294
|
+
function scrollActiveIntoView() {
|
|
287
295
|
if (!isOpen || activeIndex < 0) return
|
|
288
296
|
let optionEl = menuEl?.querySelector<HTMLElement>(`[data-index="${activeIndex}"]`)
|
|
289
297
|
optionEl?.scrollIntoView({block: 'nearest'})
|
|
@@ -295,8 +303,11 @@
|
|
|
295
303
|
|
|
296
304
|
onMount(() => {
|
|
297
305
|
mounted = true
|
|
298
|
-
|
|
299
|
-
|
|
306
|
+
if (field.hasExternalValue) setSelection(field.value, {persist: false})
|
|
307
|
+
else {
|
|
308
|
+
let defaults = ensureArray(defaultValue)
|
|
309
|
+
if (!hasNoDefault && defaults.length) setSelection(defaults, {persist: true})
|
|
310
|
+
}
|
|
300
311
|
syncSelection(false)
|
|
301
312
|
setupQuery()
|
|
302
313
|
if (typeof document !== 'undefined') {
|
|
@@ -305,6 +316,7 @@
|
|
|
305
316
|
}
|
|
306
317
|
return () => {
|
|
307
318
|
mounted = false
|
|
319
|
+
field.destroy()
|
|
308
320
|
if (queryHandler) {
|
|
309
321
|
window.$GRAPHENE?.unsubscribe?.(queryHandler)
|
|
310
322
|
queryHandler = null
|
|
@@ -326,69 +338,77 @@
|
|
|
326
338
|
if (triggerEl) updateTriggerWidth()
|
|
327
339
|
})
|
|
328
340
|
|
|
329
|
-
function syncSelection
|
|
341
|
+
function syncSelection(fromUser: boolean) {
|
|
342
|
+
// Reconcile persisted selection with the current option set, while keeping external values
|
|
343
|
+
// authoritative and only applying defaults/select-all before the user has interacted.
|
|
330
344
|
let opts = availableOptions
|
|
331
345
|
if (!opts.length) {
|
|
332
|
-
|
|
346
|
+
// Keep the bound param initialized even before options load.
|
|
347
|
+
// This prevents $param references from throwing "Missing param" on first render.
|
|
348
|
+
updateInputPayload(selection)
|
|
333
349
|
return
|
|
334
350
|
}
|
|
335
351
|
let nextSelection = selection.filter(val => valueMap.has(optionKey(val)))
|
|
336
352
|
if (!fromUser) {
|
|
337
353
|
let defaults = ensureArray(defaultValue)
|
|
338
|
-
if (
|
|
354
|
+
if (field.hasExternalValue) {
|
|
355
|
+
nextSelection = nextSelection
|
|
356
|
+
} else if (multi && selectAllDefault) {
|
|
339
357
|
nextSelection = opts.map(o => o.value)
|
|
340
358
|
} else if (!touched) {
|
|
341
|
-
if (defaults.length) {
|
|
359
|
+
if (defaults.length && !hasNoDefault) {
|
|
342
360
|
nextSelection = defaults.filter(val => valueMap.has(optionKey(val)))
|
|
343
361
|
} else if (!multi && !hasNoDefault) {
|
|
344
362
|
nextSelection = selection.length ? nextSelection : []
|
|
345
363
|
}
|
|
346
364
|
}
|
|
347
365
|
}
|
|
348
|
-
setSelection(nextSelection, fromUser)
|
|
366
|
+
setSelection(nextSelection, {fromUser, persist: true})
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function sameSelection(left: any[], right: any[]) {
|
|
370
|
+
let leftKeys = left.map(optionKey)
|
|
371
|
+
let rightKeys = right.map(optionKey)
|
|
372
|
+
return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index])
|
|
349
373
|
}
|
|
350
374
|
|
|
351
|
-
function setSelection
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
let changed = keys.length !== existingKeys.length || keys.some((k, idx) => k !== existingKeys[idx])
|
|
355
|
-
if (!changed) {
|
|
356
|
-
if (!fromUser) updateInputPayload(selection)
|
|
375
|
+
function setSelection(values: any[], {fromUser = false, persist = true}: {fromUser?: boolean; persist?: boolean} = {}) {
|
|
376
|
+
if (sameSelection(values, selection)) {
|
|
377
|
+
if (persist && !fromUser) updateInputPayload(selection)
|
|
357
378
|
return
|
|
358
379
|
}
|
|
359
380
|
selection = values
|
|
360
381
|
if (fromUser) touched = true
|
|
361
|
-
updateInputPayload(selection)
|
|
382
|
+
if (persist) updateInputPayload(selection)
|
|
362
383
|
}
|
|
363
384
|
|
|
364
|
-
function updateInputPayload
|
|
365
|
-
|
|
366
|
-
window.$GRAPHENE.updateParam(name, paramValue)
|
|
385
|
+
function updateInputPayload(values: any[]) {
|
|
386
|
+
field.set(values)
|
|
367
387
|
}
|
|
368
388
|
|
|
369
|
-
function selectAll
|
|
389
|
+
function selectAll() {
|
|
370
390
|
if (!multi) return
|
|
371
|
-
setSelection(availableOptions.map(opt => opt.value), true)
|
|
391
|
+
setSelection(availableOptions.map(opt => opt.value), {fromUser: true, persist: true})
|
|
372
392
|
}
|
|
373
393
|
|
|
374
|
-
function clearSelection
|
|
375
|
-
setSelection([], true)
|
|
394
|
+
function clearSelection() {
|
|
395
|
+
setSelection([], {fromUser: true, persist: true})
|
|
376
396
|
}
|
|
377
397
|
|
|
378
398
|
let elementId = $derived(`dropdown-${name}`)
|
|
379
399
|
let menuId = $derived(`${elementId}-menu`)
|
|
380
400
|
|
|
381
|
-
function getContainerClass
|
|
401
|
+
function getContainerClass() {
|
|
382
402
|
if (!hidePrint) return 'input-block'
|
|
383
403
|
return 'input-block hide-print'
|
|
384
404
|
}
|
|
385
405
|
|
|
386
|
-
function getDropdownClass
|
|
406
|
+
function getDropdownClass() {
|
|
387
407
|
if (!isDisabled) return 'dropdown'
|
|
388
408
|
return 'dropdown is-disabled'
|
|
389
409
|
}
|
|
390
410
|
|
|
391
|
-
function getOptionClass
|
|
411
|
+
function getOptionClass(opt: Option, idx: number) {
|
|
392
412
|
let classes = 'dropdown-option'
|
|
393
413
|
if (isOptionSelected(opt)) classes += ' is-selected'
|
|
394
414
|
if (activeIndex === idx) classes += ' is-active'
|
|
@@ -496,7 +516,7 @@
|
|
|
496
516
|
class={`checkbox-border${isOptionSelected(opt) ? ' is-filled' : ''}`}
|
|
497
517
|
/>
|
|
498
518
|
{#if isOptionSelected(opt)}
|
|
499
|
-
<path d="M4 8l2.5 2.5L12 5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
|
|
519
|
+
<path d="M4 8l2.5 2.5L12 5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" class="checkbox-checkmark" />
|
|
500
520
|
{/if}
|
|
501
521
|
</svg>
|
|
502
522
|
{:else}
|
|
@@ -554,6 +574,7 @@
|
|
|
554
574
|
}
|
|
555
575
|
}
|
|
556
576
|
.input-label {
|
|
577
|
+
font-family: var(--font-ui);
|
|
557
578
|
font-size: 12px;
|
|
558
579
|
font-weight: 600;
|
|
559
580
|
color: var(--input-label-color, #374151);
|
|
@@ -583,7 +604,8 @@
|
|
|
583
604
|
background: #ffffff;
|
|
584
605
|
color: #1f2937;
|
|
585
606
|
font-size: 14px;
|
|
586
|
-
font-family: var(--
|
|
607
|
+
font-family: var(--font-ui);
|
|
608
|
+
font-synthesis: none;
|
|
587
609
|
cursor: pointer;
|
|
588
610
|
transition: border-color 120ms ease, box-shadow 120ms ease, background 120ms ease;
|
|
589
611
|
}
|
|
@@ -658,6 +680,9 @@
|
|
|
658
680
|
border: 1px solid #d1d5db;
|
|
659
681
|
padding: 6px 10px;
|
|
660
682
|
font-size: 13px;
|
|
683
|
+
line-height: 16px;
|
|
684
|
+
font-family: var(--font-ui);
|
|
685
|
+
font-synthesis: none;
|
|
661
686
|
background: #f9fafb;
|
|
662
687
|
color: inherit;
|
|
663
688
|
box-sizing: border-box;
|
|
@@ -683,6 +708,9 @@
|
|
|
683
708
|
border-radius: 8px;
|
|
684
709
|
cursor: pointer;
|
|
685
710
|
font-size: 14px;
|
|
711
|
+
line-height: 18px;
|
|
712
|
+
font-family: var(--font-ui);
|
|
713
|
+
font-synthesis: none;
|
|
686
714
|
transition: background 100ms ease, color 100ms ease;
|
|
687
715
|
color: #1f2937;
|
|
688
716
|
}
|
|
@@ -721,6 +749,9 @@
|
|
|
721
749
|
fill: #2563eb;
|
|
722
750
|
stroke: #2563eb;
|
|
723
751
|
}
|
|
752
|
+
.dropdown-option.is-selected .checkbox-checkmark {
|
|
753
|
+
stroke: #ffffff;
|
|
754
|
+
}
|
|
724
755
|
.checkmark {
|
|
725
756
|
opacity: 0;
|
|
726
757
|
stroke: #2563eb;
|
|
@@ -745,6 +776,8 @@
|
|
|
745
776
|
background: none;
|
|
746
777
|
color: #2563eb;
|
|
747
778
|
font-size: 13px;
|
|
779
|
+
font-family: var(--font-ui);
|
|
780
|
+
font-synthesis: none;
|
|
748
781
|
cursor: pointer;
|
|
749
782
|
padding: 4px 0;
|
|
750
783
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import {getContext, onMount} from 'svelte'
|
|
3
|
-
import {DROPDOWN_CONTEXT} from '../component-utilities/dropdownContext'
|
|
4
3
|
|
|
5
4
|
interface Props {
|
|
6
5
|
value: any
|
|
@@ -10,7 +9,7 @@
|
|
|
10
9
|
let {value, valueLabel = undefined}: Props = $props()
|
|
11
10
|
|
|
12
11
|
type RegisterFn = ((option: {value: any; label: string}) => (() => void) | void) | undefined
|
|
13
|
-
const register = getContext<RegisterFn>(
|
|
12
|
+
const register = getContext<RegisterFn>('dropdown')
|
|
14
13
|
|
|
15
14
|
let unregister: (() => void) | void
|
|
16
15
|
|