@sqlrooms/deck 0.29.0-rc.11 → 0.29.0-rc.12

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 (126) hide show
  1. package/README.md +49 -13
  2. package/dist/BlockMapSettings.d.ts.map +1 -1
  3. package/dist/BlockMapSettings.js +4 -16
  4. package/dist/BlockMapSettings.js.map +1 -1
  5. package/dist/DashboardMapSettings.d.ts.map +1 -1
  6. package/dist/DashboardMapSettings.js +7 -10
  7. package/dist/DashboardMapSettings.js.map +1 -1
  8. package/dist/DeckJsonMap.d.ts.map +1 -1
  9. package/dist/DeckJsonMap.js +18 -12
  10. package/dist/DeckJsonMap.js.map +1 -1
  11. package/dist/DeckJsonMapSpec.d.ts +41 -0
  12. package/dist/DeckJsonMapSpec.d.ts.map +1 -1
  13. package/dist/DeckJsonMapSpec.js +2 -0
  14. package/dist/DeckJsonMapSpec.js.map +1 -1
  15. package/dist/DeckMapSurface.d.ts +1 -1
  16. package/dist/DeckMapSurface.js +1 -1
  17. package/dist/DeckMapSurface.js.map +1 -1
  18. package/dist/MapSettings.d.ts +14 -0
  19. package/dist/MapSettings.d.ts.map +1 -1
  20. package/dist/MapSettings.js +547 -192
  21. package/dist/MapSettings.js.map +1 -1
  22. package/dist/MapSettingsControls.d.ts +8 -1
  23. package/dist/MapSettingsControls.d.ts.map +1 -1
  24. package/dist/MapSettingsControls.js +24 -2
  25. package/dist/MapSettingsControls.js.map +1 -1
  26. package/dist/ai.d.ts.map +1 -1
  27. package/dist/ai.js +6 -3
  28. package/dist/ai.js.map +1 -1
  29. package/dist/aiNormalize.d.ts.map +1 -1
  30. package/dist/aiNormalize.js +2 -1
  31. package/dist/aiNormalize.js.map +1 -1
  32. package/dist/block.d.ts +1 -1
  33. package/dist/block.d.ts.map +1 -1
  34. package/dist/block.js +1 -1
  35. package/dist/block.js.map +1 -1
  36. package/dist/createOrUpdateDeckMapResource.d.ts +6 -2
  37. package/dist/createOrUpdateDeckMapResource.d.ts.map +1 -1
  38. package/dist/createOrUpdateDeckMapResource.js +57 -4
  39. package/dist/createOrUpdateDeckMapResource.js.map +1 -1
  40. package/dist/datasets/PreparedDatasetStore.d.ts +3 -4
  41. package/dist/datasets/PreparedDatasetStore.d.ts.map +1 -1
  42. package/dist/datasets/PreparedDatasetStore.js +4 -8
  43. package/dist/datasets/PreparedDatasetStore.js.map +1 -1
  44. package/dist/datasets/helpers.d.ts +39 -2
  45. package/dist/datasets/helpers.d.ts.map +1 -1
  46. package/dist/datasets/helpers.js +86 -3
  47. package/dist/datasets/helpers.js.map +1 -1
  48. package/dist/datasets/usePreparedDatasetStates.d.ts.map +1 -1
  49. package/dist/datasets/usePreparedDatasetStates.js +12 -8
  50. package/dist/datasets/usePreparedDatasetStates.js.map +1 -1
  51. package/dist/index.d.ts +2 -2
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +1 -1
  54. package/dist/index.js.map +1 -1
  55. package/dist/json/colorScaleFunction.d.ts +12 -4
  56. package/dist/json/colorScaleFunction.d.ts.map +1 -1
  57. package/dist/json/colorScaleFunction.js.map +1 -1
  58. package/dist/json/compileColorScale.d.ts +3 -2
  59. package/dist/json/compileColorScale.d.ts.map +1 -1
  60. package/dist/json/compileColorScale.js +16 -1
  61. package/dist/json/compileColorScale.js.map +1 -1
  62. package/dist/json/createDeckJsonConfiguration.d.ts.map +1 -1
  63. package/dist/json/createDeckJsonConfiguration.js +235 -175
  64. package/dist/json/createDeckJsonConfiguration.js.map +1 -1
  65. package/dist/json/defaultClasses.d.ts.map +1 -1
  66. package/dist/json/defaultClasses.js +3 -3
  67. package/dist/json/defaultClasses.js.map +1 -1
  68. package/dist/json/extractColorScaleLegends.d.ts.map +1 -1
  69. package/dist/json/extractColorScaleLegends.js +41 -26
  70. package/dist/json/extractColorScaleLegends.js.map +1 -1
  71. package/dist/json/heatmapDefaults.d.ts +17 -7
  72. package/dist/json/heatmapDefaults.d.ts.map +1 -1
  73. package/dist/json/heatmapDefaults.js +54 -12
  74. package/dist/json/heatmapDefaults.js.map +1 -1
  75. package/dist/json/layers/DeckHeatmapLayer.d.ts +32 -0
  76. package/dist/json/layers/DeckHeatmapLayer.d.ts.map +1 -0
  77. package/dist/json/layers/DeckHeatmapLayer.js +69 -0
  78. package/dist/json/layers/DeckHeatmapLayer.js.map +1 -0
  79. package/dist/json/layers/index.d.ts +1 -0
  80. package/dist/json/layers/index.d.ts.map +1 -1
  81. package/dist/json/layers/index.js +1 -0
  82. package/dist/json/layers/index.js.map +1 -1
  83. package/dist/json/rewriteGeoArrowAccessors.d.ts.map +1 -1
  84. package/dist/json/rewriteGeoArrowAccessors.js +13 -5
  85. package/dist/json/rewriteGeoArrowAccessors.js.map +1 -1
  86. package/dist/json/scaleFunction.d.ts +5 -8
  87. package/dist/json/scaleFunction.d.ts.map +1 -1
  88. package/dist/json/scaleFunction.js +30 -5
  89. package/dist/json/scaleFunction.js.map +1 -1
  90. package/dist/mapAiConfig.d.ts +6 -0
  91. package/dist/mapAiConfig.d.ts.map +1 -1
  92. package/dist/mapAiConfig.js +7 -0
  93. package/dist/mapAiConfig.js.map +1 -1
  94. package/dist/mapAiSharedInstructions.d.ts +1 -1
  95. package/dist/mapAiSharedInstructions.d.ts.map +1 -1
  96. package/dist/mapAiSharedInstructions.js +3 -3
  97. package/dist/mapAiSharedInstructions.js.map +1 -1
  98. package/dist/mapConfig.d.ts +1 -1
  99. package/dist/mapConfig.d.ts.map +1 -1
  100. package/dist/mapConfig.js.map +1 -1
  101. package/dist/mapConfigUtils.d.ts +22 -0
  102. package/dist/mapConfigUtils.d.ts.map +1 -1
  103. package/dist/mapConfigUtils.js +145 -3
  104. package/dist/mapConfigUtils.js.map +1 -1
  105. package/dist/mapLayerConfigUtils.d.ts +55 -0
  106. package/dist/mapLayerConfigUtils.d.ts.map +1 -1
  107. package/dist/mapLayerConfigUtils.js +222 -11
  108. package/dist/mapLayerConfigUtils.js.map +1 -1
  109. package/dist/mapResourceAuthoring.d.ts +1 -1
  110. package/dist/mapResourceAuthoring.d.ts.map +1 -1
  111. package/dist/mapResourceAuthoring.js +14 -7
  112. package/dist/mapResourceAuthoring.js.map +1 -1
  113. package/dist/prepare/prepareDeckDataset.d.ts.map +1 -1
  114. package/dist/prepare/prepareDeckDataset.js +1 -0
  115. package/dist/prepare/prepareDeckDataset.js.map +1 -1
  116. package/dist/prepare/types.d.ts +3 -0
  117. package/dist/prepare/types.d.ts.map +1 -1
  118. package/dist/prepare/types.js.map +1 -1
  119. package/dist/prepare/wkbDecoder.d.ts.map +1 -1
  120. package/dist/prepare/wkbDecoder.js +62 -16
  121. package/dist/prepare/wkbDecoder.js.map +1 -1
  122. package/dist/prepare/wkbParser.d.ts +2 -0
  123. package/dist/prepare/wkbParser.d.ts.map +1 -1
  124. package/dist/prepare/wkbParser.js +1 -0
  125. package/dist/prepare/wkbParser.js.map +1 -1
  126. package/package.json +9 -9
@@ -1,43 +1,50 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useMemo, useState } from 'react';
2
+ import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
3
3
  import { getTableIdentity, resolveTableReference, } from '@sqlrooms/duckdb';
4
- import { binnedNumericSchemes, categoricalSchemes, continuousDivergingSchemes, continuousSequentialSchemes, continuousSequentialInterpolators, parseColorString, } from '@sqlrooms/color-scales';
5
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Slider, ScrollArea, SettingsPanelHeader, Switch, } from '@sqlrooms/ui';
4
+ import { binnedNumericSchemes, categoricalSchemeColors, categoricalSchemes, continuousDivergingInterpolators, continuousDivergingSchemes, continuousSequentialSchemes, continuousSequentialInterpolators, parseColorString, } from '@sqlrooms/color-scales';
5
+ import { cn, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Slider, ScrollArea, SettingsPanelHeader, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Tooltip, TooltipContent, TooltipTrigger, } from '@sqlrooms/ui';
6
+ import { AlertTriangleIcon } from 'lucide-react';
6
7
  import { isDeckMapTableDatasetSource } from './mapConfig';
7
- import { clearDeckMapLayerColorScale, createDeckMapLayerColorScale, DECK_MAP_COLOR_SCALE_TYPE_OPTIONS, DECK_MAP_LAYER_TYPE_OPTIONS, getDeckMapColorAccessorOptions, getDeckMapLayerDatasetId, getDeckMapLayerColorScale, getDeckMapLayerRecords, setDeckMapLayerColorScale, setDeckMapLayerGeometryColumn, setDeckMapLayerHexagonColumn, setDeckMapLayerArcColumns, setDeckMapLayerTimestampColumn, setDeckMapLayerType, updateDeckMapLayer, usesGeometryColumnSetting, usesH3ColumnSetting, usesArcColumnSetting, usesRadiusSetting, usesColumnRadiusSetting, usesTripsSettings, usesExtrusionSettings, } from './mapLayerConfigUtils';
8
- import { DeckMapCodeViewerPanel, DeckMapCodeViewToggleButton, DeckMapColumnSelector as ColumnSelector, DeckMapColumnsProvider as ColumnsProvider, DeckMapSettingsField as Field, DeckMapTableSelector as DataTableSelector, } from './MapSettingsControls';
8
+ import { clearDeckMapLayerColorScale, createDeckMapLayerColorScale, DECK_MAP_COLOR_SCALE_TYPE_OPTIONS, DECK_MAP_DEFAULT_LAYER_COLOR, DECK_MAP_DEFAULT_STROKE_COLOR, DECK_MAP_LAYER_TYPE_OPTIONS, deckMapRgbaToHex, getDeckMapColorAccessorOptions, getDeckMapLayerDatasetId, getDeckMapLayerColorScale, getDeckMapLayerExtruded, getDeckMapLayerFlatColor, getDeckMapLayerRecords, setDeckMapLayerFlatColor, setDeckMapLayerGeometryColumn, setDeckMapLayerHexagonColumn, setDeckMapLayerArcColumns, setDeckMapLayerTimestampColumn, setDeckMapLayerType, setDeckMapLayerColumnRadius, updateDeckMapLayer, usesGeometryColumnSetting, usesH3ColumnSetting, usesArcColumnSetting, usesRadiusSetting, usesColumnRadiusSetting, usesTripsSettings, usesExtrusionSettings, usesStrokeSetting, usesStrokeExtrusionWarning, getDeckMapLayerStrokeDefault, getDeckMapColorScaleOpacity, getDeckMapLayerChannelOpacityPercent, detachDeckMapLayerOpacity, replaceDeckMapLayerColorScalesWithFlat, replaceDeckMapLayerColorScaleWithFlat, } from './mapLayerConfigUtils';
9
+ import { DeckMapCodeViewerPanel, DeckMapCodeViewToggleButton, DeckMapColumnSelector as ColumnSelector, DeckMapColumnsProvider as ColumnsProvider, DeckMapSettingsField as Field, DeckMapTableSelector as DataTableSelector, filterDeckMapColumns, isDeckMapCategoricalColorColumn, } from './MapSettingsControls';
9
10
  import { regenerateMapConfigForTable } from './mapConfigUtils';
10
11
  import { useDeckMapDatasetSchema } from './useDeckMapDatasetSchema';
11
- const HEATMAP_COLOR_STEPS = 6;
12
+ import { detectHeatmapScheme, heatmapSchemeToColorRange, } from './json/heatmapDefaults';
12
13
  const EMPTY_COLUMNS = [];
13
- function schemeToColorRange(scheme) {
14
- const interpolator = continuousSequentialInterpolators[scheme];
15
- if (!interpolator) {
16
- return continuousSequentialInterpolators.Viridis
17
- ? Array.from({ length: HEATMAP_COLOR_STEPS }, (_, i) => parseColorString(continuousSequentialInterpolators.Viridis(i / (HEATMAP_COLOR_STEPS - 1))))
18
- : [];
14
+ function getColorScaleColumnKind(type) {
15
+ return type === 'categorical' ? 'categorical' : 'quantitative';
16
+ }
17
+ /** Color-scale type matching the field's column kind. */
18
+ function resolveColorScaleTypeForField(columns, field, preferredType) {
19
+ if (!field)
20
+ return preferredType;
21
+ const column = columns.find((candidate) => candidate.name === field);
22
+ if (!column?.type)
23
+ return preferredType;
24
+ // String/boolean/binary fields only work with categorical scales.
25
+ if (isDeckMapCategoricalColorColumn(column)) {
26
+ return 'categorical';
19
27
  }
20
- return Array.from({ length: HEATMAP_COLOR_STEPS }, (_, i) => parseColorString(interpolator(i / (HEATMAP_COLOR_STEPS - 1))));
28
+ return preferredType;
21
29
  }
22
- function detectHeatmapScheme(colorRange) {
23
- if (!Array.isArray(colorRange) || colorRange.length === 0)
24
- return 'Viridis';
25
- for (const scheme of continuousSequentialSchemes) {
26
- const sampled = schemeToColorRange(scheme);
27
- if (sampled.length === colorRange.length) {
28
- const matches = sampled.every((color, idx) => {
29
- const actual = colorRange[idx];
30
- if (!Array.isArray(actual))
31
- return false;
32
- return (Math.abs(color[0] - actual[0]) < 2 &&
33
- Math.abs(color[1] - actual[1]) < 2 &&
34
- Math.abs(color[2] - actual[2]) < 2);
35
- });
36
- if (matches)
37
- return scheme;
38
- }
30
+ /** First selectable color-scale field for this type. */
31
+ function getDefaultColorScaleField(columns, type, preferred) {
32
+ const colorable = filterDeckMapColumns(columns, 'colorable');
33
+ if (preferred && colorable.some((column) => column.name === preferred)) {
34
+ return preferred;
39
35
  }
40
- return 'Viridis';
36
+ const matchingKind = filterDeckMapColumns(colorable, getColorScaleColumnKind(type));
37
+ return matchingKind[0]?.name ?? colorable[0]?.name;
38
+ }
39
+ /** Pick field first, then coerce scale type to match that field. */
40
+ export function resolveColorScaleFieldAndType(columns, preferredType, preferredField) {
41
+ const field = getDefaultColorScaleField(columns, preferredType, preferredField);
42
+ if (!field)
43
+ return undefined;
44
+ return {
45
+ field,
46
+ type: resolveColorScaleTypeForField(columns, field, preferredType),
47
+ };
41
48
  }
42
49
  function getSchemeOptions(type) {
43
50
  if (type === 'categorical') {
@@ -52,10 +59,296 @@ function getSchemeOptions(type) {
52
59
  // Quantile/quantize/threshold: ColorBrewer only (Viridis → sequential).
53
60
  return binnedNumericSchemes;
54
61
  }
55
- export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onClose, onTableChange, onTitleChange, onConfigChange, readOnly, }) => {
62
+ const SCHEME_PREVIEW_STEPS = 24;
63
+ /** Sample CSS colors for scheme dropdown previews. */
64
+ function getSchemePreviewColors(scheme, type) {
65
+ if (type === 'categorical') {
66
+ const colors = categoricalSchemeColors[scheme];
67
+ return colors ? [...colors] : [];
68
+ }
69
+ const interpolator = type === 'diverging'
70
+ ? continuousDivergingInterpolators[scheme]
71
+ : (continuousSequentialInterpolators[scheme] ??
72
+ continuousDivergingInterpolators[scheme]);
73
+ if (!interpolator)
74
+ return [];
75
+ return Array.from({ length: SCHEME_PREVIEW_STEPS }, (_, i) => interpolator(i / (SCHEME_PREVIEW_STEPS - 1)));
76
+ }
77
+ /** Edge-to-edge CSS gradient for a scheme (no fringe gaps). */
78
+ function getSchemePreviewGradient(colors, type) {
79
+ if (colors.length === 0)
80
+ return undefined;
81
+ if (type === 'categorical') {
82
+ const stops = colors.flatMap((color, index) => {
83
+ const start = (index / colors.length) * 100;
84
+ const end = ((index + 1) / colors.length) * 100;
85
+ return [`${color} ${start}%`, `${color} ${end}%`];
86
+ });
87
+ return `linear-gradient(to right, ${stops.join(', ')})`;
88
+ }
89
+ return `linear-gradient(to right, ${colors.join(', ')})`;
90
+ }
91
+ const ColorSchemeOptionLabel = ({ scheme, type }) => {
92
+ const colors = getSchemePreviewColors(scheme, type);
93
+ const gradient = getSchemePreviewGradient(colors, type);
94
+ return (_jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2", children: [gradient ? (_jsx("span", { "aria-hidden": true, className: "h-3 w-20 shrink-0 overflow-hidden rounded-sm", style: { backgroundImage: gradient } })) : null, _jsx("span", { className: "truncate", children: scheme })] }));
95
+ };
96
+ /** Compact color swatch; native picker is visually hidden. */
97
+ const ColorSwatchInput = ({ value, onChange, disabled, 'aria-label': ariaLabel }) => (_jsx("label", { className: cn('border-input relative h-5 w-9 shrink-0 overflow-hidden rounded-[3px] border', 'ring-offset-background focus-within:ring-ring focus-within:ring-1 focus-within:ring-offset-1', disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'), style: { backgroundColor: value }, children: _jsx("input", { type: "color", "aria-label": ariaLabel, value: value, disabled: disabled, className: "absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed", onChange: (event) => onChange(event.target.value) }) }));
98
+ function opacityPercentToAlpha(percent) {
99
+ return Math.round((Math.max(0, Math.min(100, percent)) / 100) * 255);
100
+ }
101
+ function getLayerOpacityPercent(layer) {
102
+ const opacity = layer?.opacity;
103
+ if (typeof opacity === 'number' && Number.isFinite(opacity)) {
104
+ return Math.round(Math.max(0, Math.min(1, opacity)) * 100);
105
+ }
106
+ return 100;
107
+ }
108
+ const SettingsSliderField = ({ label, children }) => (_jsx(Field, { label: label, children: _jsx("div", { className: "pt-0.5", children: children }) }));
109
+ const AppearanceOpacitySlider = ({ valuePercent, onChange, disabled }) => (_jsx(SettingsSliderField, { label: `Opacity: ${valuePercent}%`, children: _jsx(Slider, { min: 0, max: 100, step: 1, value: [valuePercent], disabled: disabled, onValueChange: (values) => onChange(values[0] ?? valuePercent) }) }));
110
+ const AppearanceColorChannel = ({ accessor, layer, columns, mapConfig, layerIndex, applyConfig, lastColorScaleFieldsRef, readOnly, enabled, onEnabledChange, enableLabel = 'Enabled', defaultFlatColor = DECK_MAP_DEFAULT_LAYER_COLOR, widthPixels, onWidthChange, widthLabel = 'Width', radiusValue, onRadiusChange, radiusLabel = 'Radius', radiusUnit = '', radiusMin = 0.1, radiusMax = 50, radiusStep = 0.1, extrusionDisablesStroke = false, }) => {
111
+ const colorScale = getDeckMapLayerColorScale(layer, accessor);
112
+ const flatColor = getDeckMapLayerFlatColor(layer, accessor) ?? [
113
+ ...defaultFlatColor,
114
+ ];
115
+ const colorScaleType = colorScale?.type ?? 'sequential';
116
+ const schemeOptions = getSchemeOptions(colorScaleType);
117
+ // Don't read lastColorScaleFieldsRef during render (react-hooks/refs).
118
+ const defaultField = getDefaultColorScaleField(columns, colorScaleType);
119
+ const showControls = enabled !== false;
120
+ const opacityPercent = getDeckMapLayerChannelOpacityPercent(layer, accessor, flatColor[3] ?? 255);
121
+ const updateColorScale = (patch) => {
122
+ const preferredField = patch.field ??
123
+ colorScale?.field ??
124
+ lastColorScaleFieldsRef.current[accessor];
125
+ const resolved = resolveColorScaleFieldAndType(columns, patch.type ?? colorScale?.type ?? 'sequential', preferredField);
126
+ if (!resolved)
127
+ return;
128
+ const { field, type } = resolved;
129
+ lastColorScaleFieldsRef.current[accessor] = field;
130
+ const scheme = patch.scheme ??
131
+ (patch.type && patch.type !== colorScale?.type
132
+ ? undefined
133
+ : type !== colorScale?.type
134
+ ? undefined
135
+ : colorScale?.scheme);
136
+ applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => {
137
+ const detached = detachDeckMapLayerOpacity(nextLayer);
138
+ const existingScale = getDeckMapLayerColorScale(detached, accessor);
139
+ const currentFlat = getDeckMapLayerFlatColor(detached, accessor) ?? flatColor;
140
+ return {
141
+ ...detached,
142
+ [accessor]: createDeckMapLayerColorScale({
143
+ field,
144
+ type,
145
+ scheme,
146
+ title: field,
147
+ opacity: existingScale
148
+ ? getDeckMapColorScaleOpacity(existingScale)
149
+ : (currentFlat[3] ?? 255) / 255,
150
+ }),
151
+ };
152
+ }));
153
+ };
154
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [onEnabledChange ? (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("span", { className: "flex items-center gap-1 text-xs font-medium", children: [enableLabel, extrusionDisablesStroke ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "inline-flex", "aria-label": "no strokes with extrusion enabled", children: _jsx(AlertTriangleIcon, { className: "h-3.5 w-3.5 text-amber-500" }) }) }), _jsx(TooltipContent, { children: "no strokes with extrusion enabled" })] })) : null] }), _jsx(Switch, { checked: enabled !== false, onCheckedChange: onEnabledChange, "aria-label": enableLabel })] })) : null, showControls && onWidthChange && widthPixels !== undefined ? (_jsx(SettingsSliderField, { label: `${widthLabel}: ${Number(widthPixels.toFixed(1))}px`, children: _jsx(Slider, { min: 0.1, max: 20, step: 0.1, value: [widthPixels], onValueChange: (values) => onWidthChange(values[0] ?? 1) }) })) : null, showControls && onRadiusChange && radiusValue !== undefined ? (_jsx(SettingsSliderField, { label: `${radiusLabel}: ${radiusStep < 1 ? Number(radiusValue.toFixed(1)) : radiusValue}${radiusUnit}`, children: _jsx(Slider, { min: radiusMin, max: radiusMax, step: radiusStep, value: [radiusValue], onValueChange: (values) => onRadiusChange(values[0] ?? radiusValue) }) })) : null, showControls ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Color scale" }), _jsx(Switch, { checked: Boolean(colorScale), disabled: readOnly || (!colorScale && !defaultField), "aria-label": "Color scale", onCheckedChange: (checked) => {
155
+ if (checked) {
156
+ updateColorScale({});
157
+ return;
158
+ }
159
+ if (colorScale?.field) {
160
+ lastColorScaleFieldsRef.current[accessor] = colorScale.field;
161
+ }
162
+ applyConfig(clearDeckMapLayerColorScale(mapConfig, layerIndex, accessor));
163
+ } })] }), !colorScale ? (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Color" }), _jsx(ColorSwatchInput, { "aria-label": "Color", value: deckMapRgbaToHex(flatColor), disabled: readOnly, onChange: (hex) => {
164
+ const next = parseColorString(hex, flatColor[3] ?? 255);
165
+ applyConfig(setDeckMapLayerFlatColor(mapConfig, layerIndex, accessor, next));
166
+ } })] })) : null, colorScale ? (_jsxs("div", { className: "flex flex-col gap-2", children: [columns.length > 0 ? (_jsx(ColumnsProvider, { columns: columns, children: _jsx(Field, { label: "Field", required: true, children: _jsx(ColumnSelector.Colorable, { value: colorScale.field, onChange: (field) => updateColorScale({ field }), disabled: readOnly }) }) })) : null, _jsx(Field, { label: "Type", children: _jsxs(Select, { value: colorScaleType, onValueChange: (value) => updateColorScale({ type: value }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: DECK_MAP_COLOR_SCALE_TYPE_OPTIONS.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }) }), _jsx(Field, { label: "Scheme", children: _jsxs(Select, { value: colorScale.scheme, onValueChange: (value) => updateColorScale({ scheme: value }), children: [_jsx(SelectTrigger, { className: "w-full [&>span]:line-clamp-none", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: schemeOptions.map((scheme) => (_jsx(SelectItem, { value: scheme, children: _jsx(ColorSchemeOptionLabel, { scheme: scheme, type: colorScaleType }) }, scheme))) })] }) })] })) : null, _jsx(AppearanceOpacitySlider, { valuePercent: opacityPercent, disabled: readOnly, onChange: (percent) => {
167
+ applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => {
168
+ const detached = detachDeckMapLayerOpacity(nextLayer);
169
+ if (colorScale) {
170
+ const scale = getDeckMapLayerColorScale(detached, accessor) ??
171
+ colorScale;
172
+ return {
173
+ ...detached,
174
+ [accessor]: {
175
+ ...scale,
176
+ opacity: percent / 100,
177
+ },
178
+ };
179
+ }
180
+ const current = getDeckMapLayerFlatColor(detached, accessor) ?? flatColor;
181
+ return {
182
+ ...detached,
183
+ [accessor]: [
184
+ current[0],
185
+ current[1],
186
+ current[2],
187
+ opacityPercentToAlpha(percent),
188
+ ],
189
+ };
190
+ }));
191
+ } })] })) : null] }));
192
+ };
193
+ /** Arc source/target colors; optional shared color scale. */
194
+ const AppearanceArcColorPanel = ({ layer, columns, mapConfig, layerIndex, applyConfig, lastColorScaleFieldsRef, readOnly, }) => {
195
+ const sourceScale = getDeckMapLayerColorScale(layer, 'getSourceColor');
196
+ const targetScale = getDeckMapLayerColorScale(layer, 'getTargetColor');
197
+ const colorScale = sourceScale ?? targetScale;
198
+ const colorScaleType = colorScale?.type ?? 'sequential';
199
+ const schemeOptions = getSchemeOptions(colorScaleType);
200
+ // Don't read lastColorScaleFieldsRef during render (react-hooks/refs).
201
+ const defaultField = getDefaultColorScaleField(columns, colorScaleType);
202
+ const sourceFlat = getDeckMapLayerFlatColor(layer, 'getSourceColor') ?? [
203
+ ...DECK_MAP_DEFAULT_LAYER_COLOR,
204
+ ];
205
+ const targetFlat = getDeckMapLayerFlatColor(layer, 'getTargetColor') ?? [
206
+ ...DECK_MAP_DEFAULT_LAYER_COLOR,
207
+ ];
208
+ const opacityPercent = Math.round((getDeckMapLayerChannelOpacityPercent(layer, 'getSourceColor', sourceFlat[3] ?? 255) +
209
+ getDeckMapLayerChannelOpacityPercent(layer, 'getTargetColor', targetFlat[3] ?? 255)) /
210
+ 2);
211
+ const applySharedColorScale = (patch) => {
212
+ const preferredField = patch.field ??
213
+ colorScale?.field ??
214
+ lastColorScaleFieldsRef.current.getSourceColor ??
215
+ lastColorScaleFieldsRef.current.getTargetColor;
216
+ const resolved = resolveColorScaleFieldAndType(columns, patch.type ?? colorScale?.type ?? 'sequential', preferredField);
217
+ if (!resolved)
218
+ return;
219
+ const { field, type } = resolved;
220
+ lastColorScaleFieldsRef.current.getSourceColor = field;
221
+ lastColorScaleFieldsRef.current.getTargetColor = field;
222
+ const scheme = patch.scheme ??
223
+ (patch.type && patch.type !== colorScale?.type
224
+ ? undefined
225
+ : type !== colorScale?.type
226
+ ? undefined
227
+ : colorScale?.scheme);
228
+ applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => {
229
+ const detached = detachDeckMapLayerOpacity(nextLayer);
230
+ const existing = getDeckMapLayerColorScale(detached, 'getSourceColor') ??
231
+ getDeckMapLayerColorScale(detached, 'getTargetColor');
232
+ const source = getDeckMapLayerFlatColor(detached, 'getSourceColor') ?? sourceFlat;
233
+ const target = getDeckMapLayerFlatColor(detached, 'getTargetColor') ?? targetFlat;
234
+ const opacity = existing
235
+ ? getDeckMapColorScaleOpacity(existing)
236
+ : Math.round(((source[3] ?? 255) + (target[3] ?? 255)) / 2) / 255;
237
+ const scale = createDeckMapLayerColorScale({
238
+ field,
239
+ type,
240
+ scheme,
241
+ title: field,
242
+ opacity,
243
+ });
244
+ return {
245
+ ...detached,
246
+ getSourceColor: scale,
247
+ getTargetColor: scale,
248
+ };
249
+ }));
250
+ };
251
+ const setFlatColor = (accessor, color) => {
252
+ applyConfig(setDeckMapLayerFlatColor(mapConfig, layerIndex, accessor, color));
253
+ };
254
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Color scale" }), _jsx(Switch, { checked: Boolean(colorScale), disabled: readOnly || (!colorScale && !defaultField), "aria-label": "Color scale", onCheckedChange: (checked) => {
255
+ if (checked) {
256
+ applySharedColorScale({});
257
+ return;
258
+ }
259
+ if (colorScale?.field) {
260
+ lastColorScaleFieldsRef.current.getSourceColor = colorScale.field;
261
+ lastColorScaleFieldsRef.current.getTargetColor = colorScale.field;
262
+ }
263
+ applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => {
264
+ const replacements = {};
265
+ if (getDeckMapLayerColorScale(nextLayer, 'getSourceColor')) {
266
+ replacements.getSourceColor = DECK_MAP_DEFAULT_LAYER_COLOR;
267
+ }
268
+ if (getDeckMapLayerColorScale(nextLayer, 'getTargetColor')) {
269
+ replacements.getTargetColor = DECK_MAP_DEFAULT_LAYER_COLOR;
270
+ }
271
+ return replaceDeckMapLayerColorScalesWithFlat(nextLayer, replacements);
272
+ }));
273
+ } })] }), !colorScale ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Source color" }), _jsx(ColorSwatchInput, { "aria-label": "Source color", value: deckMapRgbaToHex(sourceFlat), disabled: readOnly, onChange: (hex) => {
274
+ const next = parseColorString(hex, sourceFlat[3] ?? 255);
275
+ setFlatColor('getSourceColor', next);
276
+ } })] }), _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Target color" }), _jsx(ColorSwatchInput, { "aria-label": "Target color", value: deckMapRgbaToHex(targetFlat), disabled: readOnly, onChange: (hex) => {
277
+ const next = parseColorString(hex, targetFlat[3] ?? 255);
278
+ setFlatColor('getTargetColor', next);
279
+ } })] })] })) : (_jsxs("div", { className: "flex flex-col gap-2", children: [columns.length > 0 ? (_jsx(ColumnsProvider, { columns: columns, children: _jsx(Field, { label: "Field", required: true, children: _jsx(ColumnSelector.Colorable, { value: colorScale.field, onChange: (field) => applySharedColorScale({ field }), disabled: readOnly }) }) })) : null, _jsx(Field, { label: "Type", children: _jsxs(Select, { value: colorScaleType, onValueChange: (value) => applySharedColorScale({
280
+ type: value,
281
+ }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: DECK_MAP_COLOR_SCALE_TYPE_OPTIONS.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }) }), _jsx(Field, { label: "Scheme", children: _jsxs(Select, { value: colorScale.scheme, onValueChange: (value) => applySharedColorScale({ scheme: value }), children: [_jsx(SelectTrigger, { className: "w-full [&>span]:line-clamp-none", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: schemeOptions.map((scheme) => (_jsx(SelectItem, { value: scheme, children: _jsx(ColorSchemeOptionLabel, { scheme: scheme, type: colorScaleType }) }, scheme))) })] }) })] })), _jsx(AppearanceOpacitySlider, { valuePercent: opacityPercent, disabled: readOnly, onChange: (percent) => {
282
+ applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => {
283
+ const detached = detachDeckMapLayerOpacity(nextLayer);
284
+ if (colorScale) {
285
+ const scale = getDeckMapLayerColorScale(detached, 'getSourceColor') ??
286
+ getDeckMapLayerColorScale(detached, 'getTargetColor') ??
287
+ colorScale;
288
+ const nextScale = { ...scale, opacity: percent / 100 };
289
+ return {
290
+ ...detached,
291
+ getSourceColor: nextScale,
292
+ getTargetColor: nextScale,
293
+ };
294
+ }
295
+ const alpha = opacityPercentToAlpha(percent);
296
+ const source = getDeckMapLayerFlatColor(detached, 'getSourceColor') ??
297
+ sourceFlat;
298
+ const target = getDeckMapLayerFlatColor(detached, 'getTargetColor') ??
299
+ targetFlat;
300
+ return {
301
+ ...detached,
302
+ getSourceColor: [source[0], source[1], source[2], alpha],
303
+ getTargetColor: [target[0], target[1], target[2], alpha],
304
+ };
305
+ }));
306
+ } })] }));
307
+ };
308
+ const AppearanceExtrusionPanel = ({ layer, columns, mapConfig, layerIndex, applyConfig, readOnly }) => {
309
+ const extruded = getDeckMapLayerExtruded(layer);
310
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Extruded" }), _jsx(Switch, { checked: extruded, onCheckedChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => ({
311
+ ...nextLayer,
312
+ extruded: checked,
313
+ getElevation: checked
314
+ ? (nextLayer.getElevation ?? 1)
315
+ : nextLayer.getElevation,
316
+ }))) })] }), extruded && columns.length > 0 ? (_jsx(ColumnsProvider, { columns: columns, children: _jsx(Field, { label: "Elevation column", children: _jsx(ColumnSelector.Numeric, { value: (() => {
317
+ const elev = layer?.getElevation;
318
+ if (elev &&
319
+ typeof elev === 'object' &&
320
+ '@@function' in elev) {
321
+ return elev.field;
322
+ }
323
+ if (typeof elev === 'string' && elev.startsWith('@@=')) {
324
+ return elev.slice(3);
325
+ }
326
+ return undefined;
327
+ })(), onChange: (elevationColumn) => applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => ({
328
+ ...nextLayer,
329
+ getElevation: elevationColumn
330
+ ? {
331
+ '@@function': 'scale',
332
+ field: elevationColumn,
333
+ type: 'linear',
334
+ domain: 'auto',
335
+ range: [0, 200],
336
+ }
337
+ : undefined,
338
+ elevationScale: nextLayer.elevationScale ?? 1,
339
+ }))), placeholder: "Select elevation column...", disabled: readOnly }) }) })) : null, extruded ? (_jsx(SettingsSliderField, { label: `Elevation scale: ${layer?.elevationScale ?? 1}x`, children: _jsx(Slider, { min: 0.01, max: 1000, step: 0.01, value: [layer?.elevationScale ?? 1], onValueChange: (values) => {
340
+ const value = values[0] ?? 1;
341
+ applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => ({
342
+ ...nextLayer,
343
+ elevationScale: value,
344
+ })));
345
+ } }) })) : null] }));
346
+ };
347
+ export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onClose, onTableChange, onTitleChange, onConfigChange, readOnly, customConfig = false, preferDatasetSource = false, }) => {
56
348
  const [layerIndex, setLayerIndex] = useState(0);
57
- const [colorAccessor, setColorAccessor] = useState('getFillColor');
58
- const [viewMode, setViewMode] = useState('settings');
349
+ const [viewMode, setViewMode] = useState(customConfig ? 'code' : 'settings');
350
+ // Last color-scale field per accessor (restored when re-enabling a scale).
351
+ const lastColorScaleFieldsRef = useRef({});
59
352
  const selectedDataTable = useMemo(() => selectedTable
60
353
  ? resolveTableReference(tables, selectedTable).table
61
354
  : undefined, [selectedTable, tables]);
@@ -66,7 +359,7 @@ export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onC
66
359
  }, [onTableChange, readOnly]);
67
360
  const mapConfig = config;
68
361
  const serializedMapConfig = useMemo(() => JSON.stringify(config, null, 2), [config]);
69
- const showCode = viewMode === 'code';
362
+ const showCode = customConfig || viewMode === 'code';
70
363
  const layers = getDeckMapLayerRecords(mapConfig);
71
364
  const activeLayerIndex = Math.min(layerIndex, Math.max(layers.length - 1, 0));
72
365
  const activeLayer = layers[activeLayerIndex];
@@ -74,9 +367,7 @@ export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onC
74
367
  const activeLayerDataset = activeLayerDatasetId
75
368
  ? mapConfig.datasets?.[activeLayerDatasetId]
76
369
  : undefined;
77
- // Resolve the structured source table separately from the compiled dataset
78
- // output. Coordinate selectors edit the source transform, while render
79
- // bindings such as geometry/color/elevation target output columns.
370
+ // Source table (coords/transform) vs compiled output columns (bindings).
80
371
  const activeLayerDatasetSource = activeLayerDataset?.source;
81
372
  const fallbackTableName = isDeckMapTableDatasetSource(activeLayerDatasetSource)
82
373
  ? activeLayerDatasetSource.tableName
@@ -84,7 +375,9 @@ export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onC
84
375
  const fallbackTable = useMemo(() => fallbackTableName
85
376
  ? resolveTableReference(tables, fallbackTableName).table
86
377
  : undefined, [fallbackTableName, tables]);
87
- const sourceDataTable = selectedDataTable ?? fallbackTable;
378
+ const sourceDataTable = preferDatasetSource
379
+ ? (fallbackTable ?? selectedDataTable)
380
+ : (selectedDataTable ?? fallbackTable);
88
381
  const sourceColumns = sourceDataTable?.columns ?? EMPTY_COLUMNS;
89
382
  const resolvedActiveLayerDatasetSource = useMemo(() => {
90
383
  if (!activeLayerDatasetSource) {
@@ -96,14 +389,14 @@ export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onC
96
389
  return activeLayerDatasetSource;
97
390
  }
98
391
  return {
99
- tableName: selectedDataTable
392
+ tableName: !preferDatasetSource && selectedDataTable
100
393
  ? getTableIdentity(selectedDataTable.table)
101
394
  : activeLayerDatasetSource.tableName,
102
395
  ...(activeLayerDatasetSource.transformSql
103
396
  ? { transformSql: activeLayerDatasetSource.transformSql }
104
397
  : {}),
105
398
  };
106
- }, [activeLayerDatasetSource, selectedDataTable]);
399
+ }, [activeLayerDatasetSource, preferDatasetSource, selectedDataTable]);
107
400
  const datasetSchema = useDeckMapDatasetSchema({
108
401
  source: resolvedActiveLayerDatasetSource,
109
402
  sourceColumns,
@@ -118,181 +411,243 @@ export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onC
118
411
  const showColumnRadiusSetting = usesColumnRadiusSetting(activeLayer?.['@@type']);
119
412
  const showTripsSettings = usesTripsSettings(activeLayer?.['@@type']);
120
413
  const showExtrusionSettings = usesExtrusionSettings(activeLayer?.['@@type']);
414
+ const showStrokeSetting = usesStrokeSetting(activeLayer?.['@@type']);
415
+ const strokeEnabled = typeof activeLayer?.stroked === 'boolean'
416
+ ? activeLayer.stroked
417
+ : getDeckMapLayerStrokeDefault(activeLayer?.['@@type'], {
418
+ extruded: getDeckMapLayerExtruded(activeLayer),
419
+ });
420
+ const strokeWidthPixels = activeLayer?.lineWidthUnits === 'pixels' &&
421
+ typeof activeLayer?.getLineWidth === 'number'
422
+ ? activeLayer.getLineWidth
423
+ : (activeLayer?.lineWidthMinPixels ?? 1);
121
424
  const colorAccessorOptions = getDeckMapColorAccessorOptions(activeLayer?.['@@type']);
122
- const effectiveColorAccessor = colorAccessorOptions.find((option) => option.value === colorAccessor)
123
- ?.value ?? colorAccessorOptions[0]?.value;
425
+ const hasFillColor = colorAccessorOptions.some((option) => option.value === 'getFillColor');
426
+ const hasLineColor = colorAccessorOptions.some((option) => option.value === 'getLineColor');
427
+ const hasPathColor = colorAccessorOptions.some((option) => option.value === 'getColor');
428
+ const hasSourceColor = colorAccessorOptions.some((option) => option.value === 'getSourceColor');
429
+ const hasTargetColor = colorAccessorOptions.some((option) => option.value === 'getTargetColor');
430
+ const isPathLayer = activeLayer?.['@@type'] === 'GeoArrowPathLayer';
124
431
  const isGeoJsonLayer = activeLayer?.['@@type'] === 'GeoJsonLayer';
432
+ // Keep unknown types visible if the layer already uses them.
433
+ const activeLayerType = typeof activeLayer?.['@@type'] === 'string'
434
+ ? activeLayer['@@type']
435
+ : undefined;
436
+ const layerTypeOptions = activeLayerType &&
437
+ !DECK_MAP_LAYER_TYPE_OPTIONS.some((option) => option.value === activeLayerType)
438
+ ? [
439
+ ...DECK_MAP_LAYER_TYPE_OPTIONS,
440
+ { value: activeLayerType, label: activeLayerType },
441
+ ]
442
+ : DECK_MAP_LAYER_TYPE_OPTIONS;
443
+ // GeoJsonLayer uses pointRadius* / getPointRadius; scatterplot uses radius* / getRadius.
125
444
  const pointRadiusPixels = isGeoJsonLayer
126
- ? typeof activeLayer?.getPointRadius === 'number'
445
+ ? activeLayer?.pointRadiusUnits === 'pixels' &&
446
+ typeof activeLayer?.getPointRadius === 'number'
127
447
  ? activeLayer.getPointRadius
128
448
  : (activeLayer?.pointRadiusMinPixels ?? 2)
129
449
  : activeLayer?.radiusUnits === 'pixels' &&
130
450
  typeof activeLayer?.getRadius === 'number'
131
451
  ? activeLayer.getRadius
132
452
  : (activeLayer?.radiusMinPixels ?? 2);
133
- const colorScale = effectiveColorAccessor
134
- ? getDeckMapLayerColorScale(activeLayer, effectiveColorAccessor)
135
- : undefined;
136
- const colorScaleType = colorScale?.type ?? 'sequential';
137
- const schemeOptions = getSchemeOptions(colorScaleType);
138
453
  const isHeatmapLayer = activeLayer?.['@@type'] === 'GeoArrowHeatmapLayer';
139
- const firstColumnName = dataOutputColumns[0]?.name;
454
+ const showAppearanceCard = isHeatmapLayer || colorAccessorOptions.length > 0;
455
+ const lineWidthPixels = activeLayer?.widthUnits === 'pixels' &&
456
+ typeof activeLayer?.getWidth === 'number'
457
+ ? activeLayer.getWidth
458
+ : (activeLayer?.widthMinPixels ??
459
+ (showTripsSettings ? 3 : 1));
140
460
  const applyConfig = useCallback((nextConfig) => {
141
461
  if (readOnly)
142
462
  return;
143
463
  onConfigChange(nextConfig);
144
464
  }, [onConfigChange, readOnly]);
145
- const updateColorScale = (patch) => {
146
- const field = patch.field ?? colorScale?.field ?? firstColumnName;
147
- if (!field || !effectiveColorAccessor)
465
+ // Drop point/heatmap radius leftovers on Column layers (pixels vs meters).
466
+ useEffect(() => {
467
+ if (!showColumnRadiusSetting || !activeLayer || readOnly)
148
468
  return;
149
- const type = patch.type ?? colorScale?.type ?? 'sequential';
150
- const scheme = patch.scheme ??
151
- (patch.type && patch.type !== colorScale?.type
152
- ? undefined
153
- : colorScale?.scheme);
154
- applyConfig(setDeckMapLayerColorScale(mapConfig, activeLayerIndex, effectiveColorAccessor, createDeckMapLayerColorScale({
155
- field,
156
- type,
157
- scheme,
158
- title: field,
469
+ const needsHeal = activeLayer.radiusUnits === 'pixels' ||
470
+ activeLayer.getRadius !== undefined ||
471
+ activeLayer.radiusMinPixels !== undefined ||
472
+ activeLayer.radiusMaxPixels !== undefined ||
473
+ activeLayer.radiusPixels !== undefined;
474
+ if (!needsHeal)
475
+ return;
476
+ const radius = typeof activeLayer.radius === 'number' &&
477
+ Number.isFinite(activeLayer.radius) &&
478
+ activeLayer.radius > 0
479
+ ? activeLayer.radius
480
+ : 50;
481
+ applyConfig(setDeckMapLayerColumnRadius(mapConfig, activeLayerIndex, radius));
482
+ }, [
483
+ activeLayer,
484
+ activeLayerIndex,
485
+ applyConfig,
486
+ mapConfig,
487
+ readOnly,
488
+ showColumnRadiusSetting,
489
+ ]);
490
+ // Fix path/arc/trips where widthMaxPixels < widthMinPixels (clamps the slider).
491
+ useEffect(() => {
492
+ const isLineWidthLayer = showTripsSettings ||
493
+ activeLayer?.['@@type'] === 'GeoArrowPathLayer' ||
494
+ showArcColumnSetting;
495
+ if (!isLineWidthLayer || !activeLayer || readOnly)
496
+ return;
497
+ const min = typeof activeLayer.widthMinPixels === 'number'
498
+ ? activeLayer.widthMinPixels
499
+ : undefined;
500
+ const max = typeof activeLayer.widthMaxPixels === 'number'
501
+ ? activeLayer.widthMaxPixels
502
+ : undefined;
503
+ if (min === undefined || max === undefined || max >= min)
504
+ return;
505
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
506
+ ...layer,
507
+ widthMinPixels: Math.min(min, max),
508
+ widthMaxPixels: Math.max(min, max),
159
509
  })));
510
+ }, [
511
+ activeLayer,
512
+ activeLayerIndex,
513
+ applyConfig,
514
+ mapConfig,
515
+ readOnly,
516
+ showArcColumnSetting,
517
+ showTripsSettings,
518
+ ]);
519
+ const setStrokeWidth = (value) => {
520
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => {
521
+ const nextLayer = {
522
+ ...layer,
523
+ lineWidthUnits: 'pixels',
524
+ lineWidthMinPixels: value,
525
+ lineWidthMaxPixels: value,
526
+ };
527
+ if (typeof layer.getLineWidth !== 'string') {
528
+ nextLayer.getLineWidth = value;
529
+ }
530
+ return nextLayer;
531
+ }));
532
+ };
533
+ const setLineWidth = (value) => {
534
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => {
535
+ const nextLayer = {
536
+ ...layer,
537
+ widthUnits: 'pixels',
538
+ widthMinPixels: value,
539
+ widthMaxPixels: value,
540
+ };
541
+ if (typeof layer.getWidth !== 'string') {
542
+ nextLayer.getWidth = value;
543
+ }
544
+ return nextLayer;
545
+ }));
160
546
  };
161
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx(SettingsPanelHeader, { title: "Map settings", className: "shrink-0 p-2", actions: _jsx(DeckMapCodeViewToggleButton, { label: showCode ? 'Show settings' : 'View code', selected: showCode, onClick: () => setViewMode((currentViewMode) => currentViewMode === 'code' ? 'settings' : 'code') }), onClose: onClose, closeLabel: "Close map settings" }), showCode ? (_jsx(DeckMapCodeViewerPanel, { value: serializedMapConfig, copyTooltipLabel: "Copy map config" })) : (_jsx(ScrollArea, { className: "min-h-0 flex-1 [&_[data-radix-scroll-area-viewport]>div]:!block", children: _jsxs("div", { className: "flex flex-col gap-2 p-2 pt-0", children: [_jsx(Field, { label: "Title", children: _jsx("input", { value: title, onChange: (e) => onTitleChange(e.target.value), placeholder: "Map title", disabled: readOnly, className: "border-input placeholder:text-muted-foreground focus-visible:ring-ring h-8 w-full rounded-md border bg-transparent px-3 py-2 text-xs font-medium shadow-sm outline-hidden transition-colors focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50" }) }), _jsx(Field, { label: "Dataset", required: true, children: _jsx(DataTableSelector, { onChange: handleTableChange, tables: tables, value: selectedDataTable, disabled: readOnly }) }), layers.length > 0 && (_jsxs("div", { className: "flex flex-col gap-3", children: [layers.length > 1 && (_jsx(Field, { label: "Layer", children: _jsxs(Select, { value: String(activeLayerIndex), onValueChange: (value) => setLayerIndex(Number(value)), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: layers.map((layer, index) => (_jsx(SelectItem, { value: String(index), children: String(layer.id ?? `Layer ${index + 1}`) }, index))) })] }) })), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-medium", children: "Visible" }), _jsx(Switch, { checked: activeLayer?.visible !== false, onCheckedChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
547
+ const setPointRadius = (value) => {
548
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => {
549
+ if (layer['@@type'] === 'GeoJsonLayer') {
550
+ const nextLayer = {
551
+ ...layer,
552
+ pointRadiusUnits: 'pixels',
553
+ pointRadiusMinPixels: value,
554
+ pointRadiusMaxPixels: value,
555
+ };
556
+ if (typeof layer.getPointRadius !== 'string') {
557
+ nextLayer.getPointRadius = value;
558
+ }
559
+ return nextLayer;
560
+ }
561
+ const nextLayer = {
562
+ ...layer,
563
+ radiusUnits: 'pixels',
564
+ radiusMinPixels: value,
565
+ radiusMaxPixels: value,
566
+ };
567
+ if (typeof layer.getRadius !== 'string') {
568
+ nextLayer.getRadius = value;
569
+ }
570
+ return nextLayer;
571
+ }));
572
+ };
573
+ const setColumnRadius = (value) => {
574
+ applyConfig(setDeckMapLayerColumnRadius(mapConfig, activeLayerIndex, value));
575
+ };
576
+ const fillRadiusProps = showRadiusSetting
577
+ ? {
578
+ radiusValue: pointRadiusPixels,
579
+ onRadiusChange: setPointRadius,
580
+ radiusLabel: 'Point radius',
581
+ radiusUnit: 'px',
582
+ radiusMin: 0.1,
583
+ radiusMax: 50,
584
+ radiusStep: 0.1,
585
+ }
586
+ : showColumnRadiusSetting
587
+ ? {
588
+ radiusValue: activeLayer?.radius ?? 50,
589
+ onRadiusChange: setColumnRadius,
590
+ radiusLabel: 'Column radius',
591
+ radiusUnit: 'm',
592
+ radiusMin: 1,
593
+ radiusMax: 10000,
594
+ radiusStep: 1,
595
+ }
596
+ : {};
597
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx(SettingsPanelHeader, { title: "Map settings", className: "shrink-0 p-2", actions: customConfig ? undefined : (_jsx(DeckMapCodeViewToggleButton, { label: showCode ? 'Show settings' : 'View code', selected: showCode, onClick: () => setViewMode((currentViewMode) => currentViewMode === 'code' ? 'settings' : 'code') })), onClose: onClose, closeLabel: "Close map settings" }), showCode ? (_jsx(DeckMapCodeViewerPanel, { value: serializedMapConfig, copyTooltipLabel: "Copy map config" })) : (_jsx(ScrollArea, { className: "min-h-0 flex-1 [&_[data-radix-scroll-area-viewport]>div]:!block", children: _jsxs("div", { className: "flex flex-col gap-2 p-2 pt-0", children: [_jsx(Field, { label: "Title", children: _jsx("input", { value: title, onChange: (e) => onTitleChange(e.target.value), placeholder: "Map title", disabled: readOnly, className: "border-input placeholder:text-muted-foreground focus-visible:ring-ring h-8 w-full rounded-md border bg-transparent px-3 py-2 text-xs font-medium shadow-sm outline-hidden transition-colors focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50" }) }), _jsx(Field, { label: "Dataset", required: true, children: _jsx(DataTableSelector, { onChange: handleTableChange, tables: tables, value: sourceDataTable, disabled: readOnly }) }), layers.length > 0 && (_jsxs("div", { className: "flex flex-col gap-3", children: [layers.length > 1 && (_jsx(Field, { label: "Layer", children: _jsxs(Select, { value: String(activeLayerIndex), onValueChange: (value) => setLayerIndex(Number(value)), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: layers.map((layer, index) => (_jsx(SelectItem, { value: String(index), children: String(layer.id ?? `Layer ${index + 1}`) }, index))) })] }) })), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-medium", children: "Visible" }), _jsx(Switch, { checked: activeLayer?.visible !== false, onCheckedChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
162
598
  ...layer,
163
599
  visible: checked,
164
600
  }))) })] }), _jsx(Field, { label: "Layer type", children: _jsxs(Select, { value: typeof activeLayer?.['@@type'] === 'string'
165
601
  ? activeLayer['@@type']
166
- : undefined, onValueChange: (value) => applyConfig(setDeckMapLayerType(mapConfig, activeLayerIndex, value)), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: "Select layer type" }) }), _jsx(SelectContent, { children: DECK_MAP_LAYER_TYPE_OPTIONS.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }) }), (datasetSchema.isLoading || datasetSchemaErrorMessage) && (_jsx("div", { className: "text-muted-foreground rounded-md border px-2 py-1.5 text-xs", children: datasetSchema.isLoading
602
+ : undefined, onValueChange: (value) => applyConfig(setDeckMapLayerType(mapConfig, activeLayerIndex, value)), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: "Select layer type" }) }), _jsx(SelectContent, { children: layerTypeOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }) }), (datasetSchema.isLoading || datasetSchemaErrorMessage) && (_jsx("div", { className: "text-muted-foreground rounded-md border px-2 py-1.5 text-xs", children: datasetSchema.isLoading
167
603
  ? 'Inspecting dataset schema...'
168
- : `Dataset schema unavailable: ${datasetSchemaErrorMessage}` })), showTripsSettings && (_jsxs(_Fragment, { children: [outputColumns.length > 0 && (_jsx(ColumnsProvider, { columns: outputColumns, children: _jsx(Field, { label: "Timestamp column", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding?.timestampColumn, onChange: (timestampColumn) => applyConfig(setDeckMapLayerTimestampColumn(mapConfig, activeLayerIndex, timestampColumn)), placeholder: "Select timestamp column...", disabled: readOnly }) }) })), _jsx(Field, { label: `Line width: ${activeLayer?.widthMinPixels ?? 3}px`, children: _jsx(Slider, { min: 1, max: 20, step: 1, value: [
169
- activeLayer?.widthMinPixels ??
170
- 3,
171
- ], onValueChange: (values) => {
172
- const value = values[0] ?? 3;
173
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
604
+ : `Dataset schema unavailable: ${datasetSchemaErrorMessage}` })), showAppearanceCard && (_jsx(Field, { label: "Appearance", children: _jsx("div", { className: "flex flex-col gap-2 rounded-md border p-2", children: isHeatmapLayer ? (_jsxs(_Fragment, { children: [_jsx(SettingsSliderField, { label: `Radius: ${activeLayer?.radiusPixels ?? 30}px`, children: _jsx(Slider, { min: 1, max: 100, step: 1, value: [
605
+ activeLayer?.radiusPixels ?? 30,
606
+ ], onValueChange: (values) => {
607
+ const value = values[0] ?? 30;
608
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
609
+ ...layer,
610
+ radiusPixels: value,
611
+ })));
612
+ } }) }), _jsxs(Select, { value: detectHeatmapScheme(activeLayer?.colorRange), onValueChange: (value) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
174
613
  ...layer,
175
- widthMinPixels: value,
176
- })));
177
- } }) }), _jsx(Field, { label: `Trail length: ${Math.round((activeLayer?._trailLengthFactor ?? 0.4) * 100)}%`, children: _jsx(Slider, { min: 5, max: 100, step: 5, value: [
178
- Math.round((activeLayer?._trailLengthFactor ?? 0.4) * 100),
179
- ], onValueChange: (values) => {
180
- const value = (values[0] ?? 40) / 100;
181
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
182
- ...layer,
183
- _trailLengthFactor: value,
184
- })));
185
- } }) })] })), isHeatmapLayer ? (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Color scheme (density)" }), _jsx(Field, { label: "Scheme", children: _jsxs(Select, { value: detectHeatmapScheme(activeLayer?.colorRange), onValueChange: (value) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
186
- ...layer,
187
- colorRange: schemeToColorRange(value),
188
- }))), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: continuousSequentialSchemes.map((scheme) => (_jsx(SelectItem, { value: scheme, children: scheme }, scheme))) })] }) })] })) : (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Color scale" }), _jsx(Switch, { checked: Boolean(colorScale), onCheckedChange: (checked) => {
189
- if (!effectiveColorAccessor)
190
- return;
191
- if (checked) {
192
- updateColorScale({});
193
- return;
194
- }
195
- applyConfig(clearDeckMapLayerColorScale(mapConfig, activeLayerIndex, effectiveColorAccessor));
196
- }, disabled: !firstColumnName || !effectiveColorAccessor })] }), effectiveColorAccessor && (_jsx(Field, { label: "Color property", children: _jsxs(Select, { value: effectiveColorAccessor, onValueChange: (value) => setColorAccessor(value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: colorAccessorOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }) })), colorScale && dataOutputColumns.length > 0 && (_jsx(ColumnsProvider, { columns: dataOutputColumns, children: _jsx(Field, { label: "Color field", required: true, children: colorScaleType === 'categorical' ? (_jsx(ColumnSelector.Categorical, { value: colorScale.field, onChange: (field) => updateColorScale({ field }), disabled: readOnly })) : (_jsx(ColumnSelector.Quantitative, { value: colorScale.field, onChange: (field) => updateColorScale({ field }), disabled: readOnly })) }) })), colorScale && (_jsxs(_Fragment, { children: [_jsx(Field, { label: "Scale type", children: _jsxs(Select, { value: colorScaleType, onValueChange: (value) => updateColorScale({
197
- type: value,
198
- }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: DECK_MAP_COLOR_SCALE_TYPE_OPTIONS.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }) }), _jsx(Field, { label: "Scheme", children: _jsxs(Select, { value: colorScale.scheme, onValueChange: (value) => updateColorScale({
199
- scheme: value,
200
- }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: schemeOptions.map((scheme) => (_jsx(SelectItem, { value: scheme, children: scheme }, scheme))) })] }) })] }))] }))] })), isHeatmapLayer && (_jsx(Field, { label: `Radius: ${activeLayer?.radiusPixels ?? 30}px`, children: _jsx("div", { className: "pt-0.5", children: _jsx(Slider, { min: 1, max: 100, step: 1, value: [
201
- activeLayer?.radiusPixels ?? 30,
202
- ], onValueChange: (values) => {
203
- const value = values[0] ?? 30;
204
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
205
- ...layer,
206
- radiusPixels: value,
207
- })));
208
- } }) }) })), showRadiusSetting && (_jsx(Field, { label: `Point radius: ${pointRadiusPixels}px`, children: _jsx("div", { className: "pt-0.5", children: _jsx(Slider, { min: 1, max: 50, step: 1, value: [pointRadiusPixels], onValueChange: (values) => {
209
- const value = values[0] ?? 2;
210
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => {
211
- const nextLayer = layer['@@type'] === 'GeoJsonLayer'
212
- ? {
213
- ...layer,
214
- pointRadiusUnits: 'pixels',
215
- pointRadiusMinPixels: value,
216
- pointRadiusMaxPixels: Math.max(value, layer.pointRadiusMaxPixels ?? value),
217
- getPointRadius: value,
218
- }
219
- : {
220
- ...layer,
221
- radiusMinPixels: value,
222
- radiusMaxPixels: Math.max(value, layer.radiusMaxPixels ?? value),
223
- };
224
- if (layer['@@type'] !== 'GeoJsonLayer' &&
225
- layer.radiusUnits === 'pixels' &&
226
- typeof layer.getRadius !== 'string') {
227
- nextLayer.getRadius = value;
228
- }
229
- return nextLayer;
230
- }));
231
- } }) }) })), showColumnRadiusSetting && (_jsx(Field, { label: `Column radius: ${activeLayer?.radius ?? 50}m`, children: _jsx("div", { className: "pt-0.5", children: _jsx(Slider, { min: 1, max: 10000, step: 1, value: [activeLayer?.radius ?? 50], onValueChange: (values) => {
232
- const value = values[0] ?? 50;
233
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
234
- ...layer,
235
- radius: value,
236
- })));
237
- } }) }) })), outputColumns.length > 0 && showGeometryColumnSetting && (_jsx(ColumnsProvider, { columns: outputColumns, children: _jsx(Field, { label: "Geometry column", required: true, children: _jsx(ColumnSelector, { value: activeLayerDataset?.geometryColumn, onChange: (geometryColumn) => applyConfig(setDeckMapLayerGeometryColumn(mapConfig, activeLayerIndex, geometryColumn)), placeholder: "Select geometry column...", disabled: readOnly }) }) })), outputColumns.length > 0 && showH3ColumnSetting && (_jsx(ColumnsProvider, { columns: outputColumns, children: _jsx(Field, { label: "H3 column", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding
238
- ?.hexagonColumn, onChange: (hexagonColumn) => applyConfig(setDeckMapLayerHexagonColumn(mapConfig, activeLayerIndex, hexagonColumn)), placeholder: "Select H3 index column...", disabled: readOnly }) }) })), outputColumns.length > 0 && showArcColumnSetting && (_jsxs(ColumnsProvider, { columns: outputColumns, children: [_jsx(Field, { label: "Source geometry", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding
239
- ?.sourceGeometryColumn, onChange: (sourceGeometryColumn) => applyConfig(setDeckMapLayerArcColumns(mapConfig, activeLayerIndex, {
240
- sourceGeometryColumn,
241
- })), placeholder: "Select source geometry...", disabled: readOnly }) }), _jsx(Field, { label: "Target geometry", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding
242
- ?.targetGeometryColumn, onChange: (targetGeometryColumn) => applyConfig(setDeckMapLayerArcColumns(mapConfig, activeLayerIndex, {
243
- targetGeometryColumn,
244
- })), placeholder: "Select target geometry...", disabled: readOnly }) })] })), showArcColumnSetting && (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-medium", children: "Flat lines" }), _jsx(Switch, { checked: activeLayer?.getHeight === 0, onCheckedChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
245
- ...layer,
246
- getHeight: checked ? 0 : undefined,
247
- }))) })] })), showArcColumnSetting && (_jsx(Field, { label: `Line width: ${activeLayer?.widthMinPixels ?? 1}px`, children: _jsx(Slider, { min: 1, max: 20, step: 1, value: [
248
- activeLayer?.widthMinPixels ?? 1,
249
- ], onValueChange: (values) => {
250
- const value = values[0] ?? 1;
251
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
252
- ...layer,
253
- widthMinPixels: value,
254
- })));
255
- } }) })), showExtrusionSettings && (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Extrusion" }), _jsx(Switch, { checked: Boolean(activeLayer?.extruded), onCheckedChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
256
- ...layer,
257
- extruded: checked,
258
- getElevation: checked
259
- ? (layer.getElevation ?? 1)
260
- : layer.getElevation,
261
- }))) })] }), Boolean(activeLayer?.extruded) &&
262
- dataOutputColumns.length > 0 && (_jsx(ColumnsProvider, { columns: dataOutputColumns, children: _jsx(Field, { label: "Elevation column", children: _jsx(ColumnSelector.Numeric, { value: (() => {
263
- const elev = activeLayer?.getElevation;
264
- if (elev &&
265
- typeof elev === 'object' &&
266
- '@@function' in elev) {
267
- return elev.field;
268
- }
269
- if (typeof elev === 'string' &&
270
- elev.startsWith('@@=')) {
271
- return elev.slice(3);
272
- }
273
- return undefined;
274
- })(), onChange: (elevationColumn) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
275
- ...layer,
276
- getElevation: elevationColumn
277
- ? {
278
- '@@function': 'scale',
279
- field: elevationColumn,
280
- type: 'linear',
281
- domain: 'auto',
282
- range: [0, 200],
283
- }
284
- : undefined,
285
- elevationScale: layer.elevationScale ?? 1,
286
- }))), placeholder: "Select elevation column...", disabled: readOnly }) }) })), Boolean(activeLayer?.extruded) && (_jsx(Field, { label: `Elevation scale: ${activeLayer?.elevationScale ?? 1}x`, children: _jsx("div", { className: "pt-1.5", children: _jsx(Slider, { min: 0.01, max: 100, step: 0.01, value: [
287
- activeLayer?.elevationScale ??
288
- 1,
289
- ], onValueChange: (values) => {
290
- const value = values[0] ?? 1;
291
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
292
- ...layer,
293
- elevationScale: value,
294
- })));
295
- } }) }) }))] })), sourceDataTable &&
614
+ colorRange: heatmapSchemeToColorRange(value),
615
+ }))), children: [_jsx(SelectTrigger, { className: "w-full [&>span]:line-clamp-none", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: continuousSequentialSchemes.map((scheme) => (_jsx(SelectItem, { value: scheme, children: _jsx(ColorSchemeOptionLabel, { scheme: scheme, type: "sequential" }) }, scheme))) })] }), _jsx(AppearanceOpacitySlider, { valuePercent: getLayerOpacityPercent(activeLayer), disabled: readOnly, onChange: (percent) => {
616
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
617
+ ...layer,
618
+ opacity: percent / 100,
619
+ })));
620
+ } })] })) : (_jsxs(_Fragment, { children: [(showTripsSettings ||
621
+ isPathLayer ||
622
+ showArcColumnSetting) && (_jsx(SettingsSliderField, { label: `${showTripsSettings ? 'Trip width' : 'Line width'}: ${lineWidthPixels}px`, children: _jsx(Slider, { min: 1, max: 20, step: 1, value: [lineWidthPixels], onValueChange: (values) => setLineWidth(values[0] ?? lineWidthPixels) }) })), showArcColumnSetting ? (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Flat lines" }), _jsx(Switch, { checked: activeLayer?.getHeight === 0, onCheckedChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
623
+ ...layer,
624
+ getHeight: checked ? 0 : undefined,
625
+ }))) })] })) : null, showTripsSettings ? (_jsx(SettingsSliderField, { label: `Trail length: ${Math.round((activeLayer?._trailLengthFactor ?? 0.4) * 100)}%`, children: _jsx(Slider, { min: 5, max: 100, step: 5, value: [
626
+ Math.round((activeLayer?._trailLengthFactor ?? 0.4) * 100),
627
+ ], onValueChange: (values) => {
628
+ const value = (values[0] ?? 40) / 100;
629
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
630
+ ...layer,
631
+ _trailLengthFactor: value,
632
+ })));
633
+ } }) })) : null, hasFillColor && hasLineColor && showStrokeSetting ? (_jsxs(Tabs, { defaultValue: "fill", className: "w-full", children: [_jsxs(TabsList, { className: `grid h-8 w-full ${showExtrusionSettings ? 'grid-cols-3' : 'grid-cols-2'}`, children: [_jsx(TabsTrigger, { value: "fill", className: "text-xs", children: "Fill" }), _jsx(TabsTrigger, { value: "stroke", className: "text-xs", children: "Stroke" }), showExtrusionSettings ? (_jsx(TabsTrigger, { value: "extrusion", className: "text-xs", children: "Extrusion" })) : null] }), _jsx(TabsContent, { value: "fill", className: "mt-2", children: _jsx(AppearanceColorChannel, { accessor: "getFillColor", layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, lastColorScaleFieldsRef: lastColorScaleFieldsRef, readOnly: readOnly, ...fillRadiusProps }) }), _jsx(TabsContent, { value: "stroke", className: "mt-2", children: _jsx(AppearanceColorChannel, { accessor: "getLineColor", layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, lastColorScaleFieldsRef: lastColorScaleFieldsRef, readOnly: readOnly, enabled: strokeEnabled, onEnabledChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => {
634
+ if (checked) {
635
+ return { ...layer, stroked: true };
636
+ }
637
+ // Clear stroke color scale when stroke is off.
638
+ const next = {
639
+ ...layer,
640
+ stroked: false,
641
+ };
642
+ if (getDeckMapLayerColorScale(next, 'getLineColor')) {
643
+ return replaceDeckMapLayerColorScaleWithFlat(next, 'getLineColor', DECK_MAP_DEFAULT_STROKE_COLOR);
644
+ }
645
+ return next;
646
+ })), enableLabel: "Stroke", defaultFlatColor: DECK_MAP_DEFAULT_STROKE_COLOR, widthPixels: strokeWidthPixels, onWidthChange: setStrokeWidth, widthLabel: "Stroke width", extrusionDisablesStroke: usesStrokeExtrusionWarning(activeLayer?.['@@type']) && getDeckMapLayerExtruded(activeLayer) }) }), showExtrusionSettings ? (_jsx(TabsContent, { value: "extrusion", className: "mt-2", children: _jsx(AppearanceExtrusionPanel, { layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, readOnly: readOnly }) })) : null] }, `appearance-fill-stroke${showExtrusionSettings ? '-extrusion' : ''}`)) : hasFillColor && showExtrusionSettings ? (_jsxs(Tabs, { defaultValue: "fill", className: "w-full", children: [_jsxs(TabsList, { className: "grid h-8 w-full grid-cols-2", children: [_jsx(TabsTrigger, { value: "fill", className: "text-xs", children: "Fill" }), _jsx(TabsTrigger, { value: "extrusion", className: "text-xs", children: "Extrusion" })] }), _jsx(TabsContent, { value: "fill", className: "mt-2", children: _jsx(AppearanceColorChannel, { accessor: "getFillColor", layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, lastColorScaleFieldsRef: lastColorScaleFieldsRef, readOnly: readOnly, ...fillRadiusProps }) }), _jsx(TabsContent, { value: "extrusion", className: "mt-2", children: _jsx(AppearanceExtrusionPanel, { layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, readOnly: readOnly }) })] }, "appearance-fill-extrusion")) : hasFillColor ? (_jsx(AppearanceColorChannel, { accessor: "getFillColor", layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, lastColorScaleFieldsRef: lastColorScaleFieldsRef, readOnly: readOnly, ...fillRadiusProps })) : null, hasSourceColor && hasTargetColor ? (_jsx(AppearanceArcColorPanel, { layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, lastColorScaleFieldsRef: lastColorScaleFieldsRef, readOnly: readOnly })) : null, hasPathColor ? (_jsx(AppearanceColorChannel, { accessor: "getColor", layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, lastColorScaleFieldsRef: lastColorScaleFieldsRef, readOnly: readOnly })) : null] })) }) })), outputColumns.length > 0 && showGeometryColumnSetting && (_jsx(ColumnsProvider, { columns: outputColumns, children: _jsx(Field, { label: "Geometry column", required: true, children: _jsx(ColumnSelector, { value: activeLayerDataset?.geometryColumn, onChange: (geometryColumn) => applyConfig(setDeckMapLayerGeometryColumn(mapConfig, activeLayerIndex, geometryColumn)), placeholder: "Select geometry column...", disabled: readOnly }) }) })), outputColumns.length > 0 && showTripsSettings && (_jsx(ColumnsProvider, { columns: outputColumns, children: _jsx(Field, { label: "Timestamp column", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding?.timestampColumn, onChange: (timestampColumn) => applyConfig(setDeckMapLayerTimestampColumn(mapConfig, activeLayerIndex, timestampColumn)), placeholder: "Select timestamp column...", disabled: readOnly }) }) })), outputColumns.length > 0 && showH3ColumnSetting && (_jsx(ColumnsProvider, { columns: outputColumns, children: _jsx(Field, { label: "H3 column", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding?.hexagonColumn, onChange: (hexagonColumn) => applyConfig(setDeckMapLayerHexagonColumn(mapConfig, activeLayerIndex, hexagonColumn)), placeholder: "Select H3 index column...", disabled: readOnly }) }) })), outputColumns.length > 0 && showArcColumnSetting && (_jsxs(ColumnsProvider, { columns: outputColumns, children: [_jsx(Field, { label: "Source geometry", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding?.sourceGeometryColumn, onChange: (sourceGeometryColumn) => applyConfig(setDeckMapLayerArcColumns(mapConfig, activeLayerIndex, {
647
+ sourceGeometryColumn,
648
+ })), placeholder: "Select source geometry...", disabled: readOnly }) }), _jsx(Field, { label: "Target geometry", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding?.targetGeometryColumn, onChange: (targetGeometryColumn) => applyConfig(setDeckMapLayerArcColumns(mapConfig, activeLayerIndex, {
649
+ targetGeometryColumn,
650
+ })), placeholder: "Select target geometry...", disabled: readOnly }) })] }))] })), sourceDataTable &&
296
651
  !showGeometryColumnSetting &&
297
652
  !showH3ColumnSetting &&
298
653
  !showArcColumnSetting && (_jsxs(ColumnsProvider, { columns: sourceColumns, children: [_jsx(Field, { label: "Latitude column", required: true, children: _jsx(ColumnSelector.Numeric, { value: mapConfig.fitToData?.latitudeColumn, onChange: (latitudeColumn) => {