kepler.gl 3.1.10 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/src/ai-assistant/src/config/models.d.ts +8 -0
  2. package/dist/src/components/src/side-panel/layer-panel/vector-tile-layer-configurator.d.ts +3 -2
  3. package/dist/src/localization/src/translations/en.d.ts +1 -0
  4. package/package.json +2 -2
  5. package/src/actions/package.json +8 -8
  6. package/src/ai-assistant/dist/components/ai-assistant-config.js +40 -24
  7. package/src/ai-assistant/dist/config/models.d.ts +8 -0
  8. package/src/ai-assistant/dist/config/models.js +16 -8
  9. package/src/ai-assistant/dist/localization.js +7 -7
  10. package/src/ai-assistant/dist/reducers/index.js +3 -2
  11. package/src/ai-assistant/package.json +16 -16
  12. package/src/ai-assistant/src/components/ai-assistant-config.tsx +39 -28
  13. package/src/ai-assistant/src/config/models.ts +37 -25
  14. package/src/ai-assistant/src/localization.ts +6 -6
  15. package/src/ai-assistant/src/reducers/index.ts +2 -1
  16. package/src/cloud-providers/package.json +2 -2
  17. package/src/common-utils/package.json +3 -3
  18. package/src/components/dist/common/color-legend.js +8 -8
  19. package/src/components/dist/common/data-table/index.js +13 -9
  20. package/src/components/dist/map/map-legend-panel.js +10 -4
  21. package/src/components/dist/map/map-legend.js +2 -2
  22. package/src/components/dist/modal-container.js +3 -3
  23. package/src/components/dist/modals/tilesets-modals/tileset-raster-form.js +38 -9
  24. package/src/components/dist/plot-container.js +4 -2
  25. package/src/components/dist/side-panel/interaction-panel/interaction-panel.js +2 -2
  26. package/src/components/dist/side-panel/layer-panel/color-breaks-panel.js +11 -2
  27. package/src/components/dist/side-panel/layer-panel/color-scale-selector.js +65 -12
  28. package/src/components/dist/side-panel/layer-panel/custom-palette.js +18 -7
  29. package/src/components/dist/side-panel/layer-panel/raster-tile-layer-configurator.js +3 -3
  30. package/src/components/dist/side-panel/layer-panel/vector-tile-layer-configurator.d.ts +3 -2
  31. package/src/components/dist/side-panel/layer-panel/vector-tile-layer-configurator.js +18 -4
  32. package/src/components/package.json +15 -15
  33. package/src/components/src/common/color-legend.tsx +15 -2
  34. package/src/components/src/common/data-table/index.tsx +1 -1
  35. package/src/components/src/map/map-legend-panel.tsx +21 -2
  36. package/src/components/src/map/map-legend.tsx +1 -1
  37. package/src/components/src/modal-container.tsx +6 -2
  38. package/src/components/src/modals/tilesets-modals/tileset-raster-form.tsx +60 -4
  39. package/src/components/src/plot-container.tsx +2 -1
  40. package/src/components/src/side-panel/interaction-panel/interaction-panel.tsx +1 -0
  41. package/src/components/src/side-panel/layer-panel/color-breaks-panel.tsx +14 -3
  42. package/src/components/src/side-panel/layer-panel/color-scale-selector.tsx +62 -8
  43. package/src/components/src/side-panel/layer-panel/custom-palette.tsx +11 -5
  44. package/src/components/src/side-panel/layer-panel/raster-tile-layer-configurator.tsx +2 -1
  45. package/src/components/src/side-panel/layer-panel/vector-tile-layer-configurator.tsx +19 -2
  46. package/src/constants/dist/default-settings.js +2 -2
  47. package/src/constants/node_modules/.cache/terser-webpack-plugin/content-v2/sha512/56/48/71c2c5381c01c063c164e15fa04b4b1f3faae02e6727f2f2daf483e15205feca68334bad721af0c6f31abb20b56d60504623838127764aab6883912e7f71 +1 -0
  48. package/src/constants/node_modules/.cache/terser-webpack-plugin/content-v2/sha512/b3/bd/e4f91c7af9e320b979e29ad3b9e3a4dfcd61e4a09d6e4579104c40dcace7651b443fdd6d7ff5d947ed6c9df2c0bbbcc76a2ea86d6398e4945257eeb80dd5 +1 -0
  49. package/src/constants/node_modules/.cache/terser-webpack-plugin/index-v5/b4/c5/a43653dff3b380f7b0793080a57c980a9a136764933e79454de0fe43a5ca +2 -0
  50. package/src/constants/node_modules/.cache/terser-webpack-plugin/index-v5/bf/88/ea60c7619185516464a4ae21f51394966047bfdf190a5463de651c61931c +2 -0
  51. package/src/constants/package.json +2 -2
  52. package/src/constants/umd/keplergl.min.js +1 -1
  53. package/src/deckgl-arrow-layers/package.json +2 -2
  54. package/src/deckgl-layers/package.json +4 -4
  55. package/src/duckdb/dist/table/duckdb-table-utils.js +3 -2
  56. package/src/duckdb/package.json +6 -6
  57. package/src/duckdb/src/table/duckdb-table-utils.ts +5 -1
  58. package/src/effects/package.json +5 -5
  59. package/src/layers/dist/icon-layer/icon-layer.js +12 -2
  60. package/src/layers/dist/raster-tile/gpu-utils.d.ts +11 -10
  61. package/src/layers/dist/raster-tile/gpu-utils.js +7 -6
  62. package/src/layers/dist/raster-tile/image.d.ts +4 -0
  63. package/src/layers/dist/raster-tile/image.js +30 -16
  64. package/src/layers/dist/raster-tile/raster-tile-layer.d.ts +4 -0
  65. package/src/layers/dist/raster-tile/raster-tile-layer.js +18 -6
  66. package/src/layers/dist/raster-tile/request-throttle.d.ts +1 -1
  67. package/src/layers/dist/raster-tile/request-throttle.js +17 -16
  68. package/src/layers/dist/raster-tile/types.d.ts +16 -0
  69. package/src/layers/dist/raster-tile/types.js +1 -1
  70. package/src/layers/dist/raster-tile/url.d.ts +1 -1
  71. package/src/layers/dist/raster-tile/url.js +5 -4
  72. package/src/layers/dist/vector-tile/common-tile/tile-dataset.d.ts +4 -0
  73. package/src/layers/dist/vector-tile/common-tile/tile-dataset.js +10 -1
  74. package/src/layers/dist/vector-tile/vector-tile-layer.d.ts +11 -0
  75. package/src/layers/dist/vector-tile/vector-tile-layer.js +113 -7
  76. package/src/layers/package.json +9 -9
  77. package/src/layers/src/icon-layer/icon-layer.ts +49 -38
  78. package/src/layers/src/raster-tile/gpu-utils.ts +96 -78
  79. package/src/layers/src/raster-tile/image.ts +35 -9
  80. package/src/layers/src/raster-tile/raster-tile-layer.ts +23 -5
  81. package/src/layers/src/raster-tile/request-throttle.ts +10 -5
  82. package/src/layers/src/raster-tile/types.ts +19 -1
  83. package/src/layers/src/raster-tile/url.ts +10 -3
  84. package/src/layers/src/vector-tile/common-tile/tile-dataset.ts +7 -0
  85. package/src/layers/src/vector-tile/vector-tile-layer.ts +110 -6
  86. package/src/localization/dist/translations/en.d.ts +1 -0
  87. package/src/localization/dist/translations/en.js +2 -1
  88. package/src/localization/package.json +1 -1
  89. package/src/localization/src/translations/en.ts +1 -0
  90. package/src/processors/dist/data-processor.js +15 -5
  91. package/src/processors/package.json +7 -7
  92. package/src/processors/src/data-processor.ts +19 -4
  93. package/src/reducers/package.json +16 -16
  94. package/src/schemas/package.json +7 -7
  95. package/src/styles/node_modules/.cache/terser-webpack-plugin/content-v2/sha512/21/4a/50f77069dbf70c6792dd82d4e149375a888e25ddddd3209e2db013be62091f79082291fa06eb518cccb111b09a778e2946c1fb7a27bb6e32f72da0ccf250 +1 -0
  96. package/src/styles/node_modules/.cache/terser-webpack-plugin/content-v2/sha512/52/82/28cacf6fd462b779c22675964b655bf3a9f3ce9ec3c9b141a401a5bf90cb7d904ac65ab8e484570130b12771af0a0d5bab20e6976cfa773666763e046a6a +1 -0
  97. package/src/styles/node_modules/.cache/terser-webpack-plugin/index-v5/56/db/b5a812c56b0d1dcdcd33da37e464062a19c8752cb2296603dc72d2c54d31 +2 -0
  98. package/src/styles/node_modules/.cache/terser-webpack-plugin/index-v5/c5/b6/9c4bdb089acc30ac8a16a6b9b93898757537f7017b2f725ddb832b4614d6 +2 -0
  99. package/src/styles/package.json +2 -2
  100. package/src/styles/umd/keplergl.min.js +1 -1
  101. package/src/table/dist/tileset/vector-tile-utils.js +9 -5
  102. package/src/table/package.json +5 -5
  103. package/src/table/src/tileset/vector-tile-utils.ts +9 -6
  104. package/src/tasks/package.json +2 -2
  105. package/src/types/package.json +1 -1
  106. package/src/utils/dist/application-config.js +5 -5
  107. package/src/utils/package.json +4 -4
  108. package/src/utils/src/application-config.ts +4 -6
  109. package/umd/keplergl.min.js +1385 -1332
@@ -15,11 +15,12 @@ import {getApplicationConfig} from '@kepler.gl/utils';
15
15
  import {default as useFetchJson} from '../../hooks/use-fetch-raster-tile-metadata';
16
16
  import {DatasetCreationAttributes, MetaResponse} from './common';
17
17
  import {InputLight} from '../../common';
18
+ import {Help} from '../../common/icons';
18
19
 
19
20
  const TilesetInputContainer = styled.div`
20
21
  display: grid;
21
22
  grid-template-rows: repeat(3, 1fr);
22
- row-gap: 18px;
23
+ row-gap: 0px;
23
24
  font-size: 12px;
24
25
  `;
25
26
 
@@ -29,6 +30,11 @@ const TilesetInputDescription = styled.div`
29
30
  font-size: 11px;
30
31
  `;
31
32
 
33
+ const LabelRow = styled.div`
34
+ display: flex;
35
+ align-items: center;
36
+ `;
37
+
32
38
  export type RasterTilesetMeta = {
33
39
  name: string;
34
40
  metadataUrl: string;
@@ -40,12 +46,20 @@ export function getDatasetAttributesFromRasterTile({
40
46
  metadataUrl,
41
47
  rasterTileServerUrls
42
48
  }: RasterTilesetMeta): DatasetCreationAttributes {
49
+ const appConfig = getApplicationConfig();
43
50
  return {
44
51
  name,
45
52
  type: DatasetType.RASTER_TILE,
46
53
  metadata: {
47
54
  metadataUrl,
48
- ...(rasterTileServerUrls ? {rasterTileServerUrls} : {})
55
+ ...(rasterTileServerUrls ? {rasterTileServerUrls} : {}),
56
+ // Persist raster server-related application config with the layer
57
+ rasterServerUseLatestTitiler: appConfig.rasterServerUseLatestTitiler,
58
+ rasterServerSupportsElevation: appConfig.rasterServerSupportsElevation,
59
+ rasterServerMaxRetries: appConfig.rasterServerMaxRetries,
60
+ rasterServerRetryDelay: appConfig.rasterServerRetryDelay,
61
+ rasterServerServerErrorsToRetry: appConfig.rasterServerServerErrorsToRetry,
62
+ rasterServerMaxPerServerRequests: appConfig.rasterServerMaxPerServerRequests
49
63
  }
50
64
  };
51
65
  }
@@ -54,6 +68,28 @@ type RasterTileFormProps = {
54
68
  setResponse: (response: MetaResponse) => void;
55
69
  };
56
70
 
71
+ const InfoIconLink = styled.a`
72
+ margin-left: 4px;
73
+ color: ${props => props.theme.labelColor};
74
+ text-decoration: none;
75
+ display: inline-flex;
76
+ align-items: center;
77
+ line-height: 0;
78
+ vertical-align: middle;
79
+ opacity: 0.7;
80
+
81
+ &:hover {
82
+ opacity: 1;
83
+ }
84
+
85
+ svg {
86
+ display: block;
87
+ }
88
+ `;
89
+
90
+ const RASTER_TILE_DOCUMENTATION_URL =
91
+ 'https://docs.kepler.gl/docs/user-guides/c-types-of-layers/n-raster-tile-layer';
92
+
57
93
  const parseMetadataAllowCollections = (
58
94
  metadata: JsonObjectOrArray | PMTilesMetadata,
59
95
  {metadataUrl, rasterTileType}: {metadataUrl: string; rasterTileType: RasterTileType}
@@ -201,7 +237,17 @@ const RasterTileForm: React.FC<RasterTileFormProps> = ({setResponse}) => {
201
237
  />
202
238
  </div>
203
239
  <div>
204
- <label htmlFor="tile-metadata">Tileset metadata URL</label>
240
+ <LabelRow>
241
+ <label htmlFor="tile-metadata">Tileset metadata URL</label>
242
+ <InfoIconLink
243
+ href={RASTER_TILE_DOCUMENTATION_URL}
244
+ target="_blank"
245
+ rel="noopener noreferrer"
246
+ aria-label="Open Raster Tile Layer documentation"
247
+ >
248
+ <Help height="16px" />
249
+ </InfoIconLink>
250
+ </LabelRow>
205
251
  <InputLight
206
252
  id="tile-metadata"
207
253
  placeholder="Tileset metadata URL"
@@ -214,7 +260,17 @@ const RasterTileForm: React.FC<RasterTileFormProps> = ({setResponse}) => {
214
260
  </div>
215
261
  {showServerInput && (
216
262
  <div>
217
- <label htmlFor="tileset-raster-servers">Raster tile servers</label>
263
+ <LabelRow>
264
+ <label htmlFor="tileset-raster-servers">Raster tile servers</label>
265
+ <InfoIconLink
266
+ href={RASTER_TILE_DOCUMENTATION_URL}
267
+ target="_blank"
268
+ rel="noopener noreferrer"
269
+ aria-label="Open Raster Tile Layer documentation"
270
+ >
271
+ <Help height="16px" />
272
+ </InfoIconLink>
273
+ </LabelRow>
218
274
  <InputLight
219
275
  id="tileset-raster-servers"
220
276
  placeholder="Raster tile servers (separated by commas)"
@@ -276,7 +276,8 @@ export default function PlotContainerFactory(
276
276
  mapControls: {
277
277
  mapLegend: {
278
278
  show: Boolean(legend),
279
- active: true
279
+ active: true,
280
+ settings: mapFields.mapControls?.mapLegend?.settings
280
281
  }
281
282
  },
282
283
  MapComponent: Map,
@@ -35,6 +35,7 @@ interface InteractionPanelProps {
35
35
 
36
36
  const StyledInteractionPanel = styled.div`
37
37
  padding-bottom: 6px;
38
+ contain: layout paint;
38
39
  `;
39
40
 
40
41
  InteractionPanelFactory.deps = [TooltipConfigFactory, BrushConfigFactory];
@@ -12,7 +12,7 @@ import {
12
12
  colorMapToColorBreaks,
13
13
  isNumericColorBreaks as notOrdinalColorBreaks
14
14
  } from '@kepler.gl/utils';
15
- import React, {useCallback, useMemo} from 'react';
15
+ import React, {useCallback, useMemo, useEffect} from 'react';
16
16
  import styled from 'styled-components';
17
17
  import ColumnStatsChartFactory from '../../common/column-stats-chart';
18
18
  import {Edit} from '../../common/icons';
@@ -155,6 +155,17 @@ function ColorBreaksPanelFactory(
155
155
  [customPalette.colorMap, isEditingCustomBreaks, colorBreaks]
156
156
  );
157
157
 
158
+ // Update layers on editing custom breaks
159
+ useEffect(() => {
160
+ const {type} = customPalette || {};
161
+ if (
162
+ isEditingCustomBreaks &&
163
+ (type === SCALE_TYPES.customOrdinal || type === SCALE_TYPES.custom)
164
+ ) {
165
+ onScaleChange(type, customPalette);
166
+ }
167
+ }, [isEditingCustomBreaks, customPalette, onScaleChange]);
168
+
158
169
  const onClickEditCustomBreaks = useCallback(() => {
159
170
  setColorUI({
160
171
  colorRangeConfig: {
@@ -231,8 +242,8 @@ function ColorBreaksPanelFactory(
231
242
  currentBreaks={currentBreaks}
232
243
  onEdit={isCustomBreaks ? onClickEditCustomBreaks : null}
233
244
  />
234
- ) : customPalette.colorMap &&
235
- customPalette.type === 'customOrdinal' &&
245
+ ) : (isCustomBreaks || customPalette.type === SCALE_TYPES.customOrdinal) &&
246
+ customPalette.colorMap &&
236
247
  customPalette.name?.endsWith(colorField.name) ? (
237
248
  <CategoricalColorDisplay
238
249
  colorMap={customPalette.colorMap}
@@ -148,6 +148,28 @@ function ColorScaleSelectorFactory(
148
148
  );
149
149
  const [tippyInstance, setTippyInstance] = useState<TippyInstance>();
150
150
  const isEditingColorBreaks = colorUIConfig?.colorRangeConfig?.customBreaks;
151
+
152
+ // Stores the previous selection for live preview: when choosing Custom/Custom Ordinal, we apply a temporary palette.
153
+ // Cancel restores {scale, range} from this ref; Confirm keeps the change and clears the ref.
154
+ // If the user switches between different custom scale types (e.g., from "Custom" to "Custom Ordinal") or is already in a custom scale state,
155
+ // this ref is updated to always store the most recent non-custom selection. Only the latest non-custom selection is restorable on cancel.
156
+ const prevSelectionRef = React.useRef<{scale: string; range: ColorRange} | null>(null);
157
+
158
+ // when custom color scale - but Confirm is not clicked yet
159
+ const pendingOption = useMemo(
160
+ () =>
161
+ isEditingColorBreaks
162
+ ? (dropdownSelectProps.options || []).find(
163
+ o => getOptionValue(o) === colorUIConfig?.customPalette?.type
164
+ ) || null
165
+ : null,
166
+ [
167
+ isEditingColorBreaks,
168
+ dropdownSelectProps.options,
169
+ getOptionValue,
170
+ colorUIConfig?.customPalette?.type
171
+ ]
172
+ );
151
173
  const colorScale = useMemo(
152
174
  () =>
153
175
  getLayerColorScale({
@@ -234,14 +256,16 @@ function ColorScaleSelectorFactory(
234
256
  const onSelectScale = useCallback(
235
257
  val => {
236
258
  // highlight selected option
237
- if (!val || isEditingColorBreaks) return;
259
+ if (!val) return;
260
+
238
261
  const selectedScale = getOptionValue(val);
239
- if (selectedScale === SCALE_TYPES.custom) {
262
+ if (selectedScale === SCALE_TYPES.custom || selectedScale === SCALE_TYPES.customOrdinal) {
240
263
  const customPalette = initCustomPaletteByCustomScale({
241
264
  scale: selectedScale,
242
265
  field,
243
266
  range,
244
- colorBreaks
267
+ colorBreaks,
268
+ ...(selectedScale === SCALE_TYPES.customOrdinal ? {ordinalDomain} : {})
245
269
  });
246
270
  setColorUI({
247
271
  showColorChart: true,
@@ -250,28 +274,55 @@ function ColorScaleSelectorFactory(
250
274
  },
251
275
  customPalette
252
276
  });
277
+ // store previous selection for cancel, then preview custom on the map
278
+ if (!prevSelectionRef.current) {
279
+ prevSelectionRef.current = {scale: scaleType, range};
280
+ }
253
281
  onSelect(selectedScale, customPalette);
254
- } else if (hasColorMap(range) && selectedScale !== SCALE_TYPES.customOrdinal) {
282
+ } else if (hasColorMap(range)) {
255
283
  // not custom
256
284
  // remove colorMap
257
285
  // eslint-disable-next-line no-unused-vars
258
286
  const {colorMap: _, ...newRange} = range;
287
+ // reset colorUI before changing the scale
288
+ setColorUI({
289
+ showColorChart: false,
290
+ colorRangeConfig: {
291
+ customBreaks: false
292
+ }
293
+ });
259
294
  onSelect(selectedScale, newRange);
260
295
  } else {
296
+ // reset colorUI before changing the scale
297
+ setColorUI({
298
+ showColorChart: false,
299
+ colorRangeConfig: {
300
+ customBreaks: false
301
+ }
302
+ });
261
303
  onSelect(selectedScale);
262
304
  }
263
305
  },
264
- [isEditingColorBreaks, field, setColorUI, onSelect, range, getOptionValue, colorBreaks]
306
+ [field, setColorUI, onSelect, range, getOptionValue, colorBreaks, ordinalDomain, scaleType]
265
307
  );
266
308
 
267
309
  const onApply = useCallback(() => {
268
- onSelect(scaleType, colorUIConfig.customPalette);
310
+ // change scale type only if confirmed
311
+ const nextScaleType = colorUIConfig?.customPalette?.type || scaleType;
312
+ onSelect(nextScaleType, colorUIConfig.customPalette);
269
313
  hideTippy(tippyInstance);
314
+ prevSelectionRef.current = null;
270
315
  }, [onSelect, colorUIConfig.customPalette, tippyInstance, scaleType]);
271
316
 
272
317
  const onCancel = useCallback(() => {
318
+ // restore previous selection if any
319
+ if (prevSelectionRef.current) {
320
+ const {scale: prevScale, range: prevRange} = prevSelectionRef.current;
321
+ onSelect(prevScale, prevRange);
322
+ }
273
323
  hideTippy(tippyInstance);
274
- }, [tippyInstance]);
324
+ prevSelectionRef.current = null;
325
+ }, [tippyInstance, onSelect]);
275
326
 
276
327
  const isCustomBreaks =
277
328
  scaleType === SCALE_TYPES.custom || scaleType === SCALE_TYPES.customOrdinal;
@@ -317,6 +368,9 @@ function ColorScaleSelectorFactory(
317
368
  customListComponent={ColorScaleSelectDropdown}
318
369
  searchable={false}
319
370
  showOptionsWhenEmpty
371
+ selectedItems={
372
+ pendingOption ? [pendingOption] : dropdownSelectProps.selectedItems
373
+ }
320
374
  />
321
375
  )}
322
376
  </DropdownWrapper>
@@ -327,7 +381,7 @@ function ColorScaleSelectorFactory(
327
381
  <DropdownSelect
328
382
  {...dropdownSelectProps}
329
383
  displayOption={displayOption}
330
- value={dropdownSelectProps.selectedItems[0]}
384
+ value={pendingOption || dropdownSelectProps.selectedItems[0]}
331
385
  />
332
386
  </div>
333
387
  </LazyTippy>
@@ -377,8 +377,11 @@ export const EditableColorRange: React.FC<EditableColorRangeProps> = ({
377
377
  editColorMap,
378
378
  editable
379
379
  }) => {
380
- const noMinBound = !Number.isFinite(item.inputs[0]) && index === 0;
381
- const noMaxBound = !Number.isFinite(item.inputs[1]) && isLast;
380
+ const hasInputs = Array.isArray(item?.inputs);
381
+ const leftInput = hasInputs ? item.inputs[0] : undefined;
382
+ const rightInput = hasInputs ? item.inputs[1] : undefined;
383
+ const noMinBound = !Number.isFinite(leftInput) && index === 0;
384
+ const noMaxBound = !Number.isFinite(rightInput) && isLast;
382
385
  const onChangeLeft = useCallback(
383
386
  val => {
384
387
  if (editable && editColorMap) editColorMap(parseFloat(val), index - 1);
@@ -395,7 +398,7 @@ export const EditableColorRange: React.FC<EditableColorRangeProps> = ({
395
398
  return (
396
399
  <StyledRangeInput>
397
400
  <ColorPaletteInput
398
- value={noMinBound ? 'Less' : item.inputs[0].toString()}
401
+ value={noMinBound ? 'Less' : String(leftInput ?? '')}
399
402
  id={`color-palette-input-${index}-left`}
400
403
  width="50px"
401
404
  textAlign="end"
@@ -404,7 +407,7 @@ export const EditableColorRange: React.FC<EditableColorRangeProps> = ({
404
407
  />
405
408
  <Dash />
406
409
  <ColorPaletteInput
407
- value={noMaxBound ? 'More' : item.inputs[1].toString()}
410
+ value={noMaxBound ? 'More' : String(rightInput ?? '')}
408
411
  id={`color-palette-input-${index}-right`}
409
412
  width="50px"
410
413
  textAlign="end"
@@ -468,7 +471,7 @@ export const CustomPaletteInput: React.FC<CustomPaletteInputProps> = ({
468
471
  />
469
472
  </StyledColorHexInput>
470
473
  ) : null}
471
- {isNumericColorBreaks(colorBreaks) ? (
474
+ {colorBreaks && index < colorBreaks.length && isNumericColorBreaks(colorBreaks) ? (
472
475
  <EditableColorRange
473
476
  item={colorBreaks[index]}
474
477
  isLast={index === colorBreaks.length - 1}
@@ -719,6 +722,9 @@ export const CategoricalSelector: React.FC<CategoricalSelectorProps> = ({
719
722
  listAnchor: 'list__item__anchor'
720
723
  }}
721
724
  options={allValues}
725
+ // add safe string casting for the Typeahead, so fuzzy search never receives non-strings, preventing the toLowerCase crash
726
+ displayOption={o => String(o ?? '')}
727
+ filterOption={(input, o) => String(o ?? '').includes(String(input ?? ''))}
722
728
  placeholder={'Search'}
723
729
  onOptionSelected={onOptionSelected}
724
730
  customListComponent={ModifiedDropdownList}
@@ -336,7 +336,8 @@ function RasterTileLayerConfiguratorFactory(
336
336
 
337
337
  const elevationUI = (
338
338
  <>
339
- {getApplicationConfig().rasterServerSupportsElevation &&
339
+ {(stac.rasterServerSupportsElevation ??
340
+ getApplicationConfig().rasterServerSupportsElevation) &&
340
341
  stac.rasterTileServerUrls?.length && (
341
342
  <LayerConfigGroup
342
343
  {...(layer.visConfigSettings.enableTerrain || {label: 'layer.color'})}
@@ -9,6 +9,7 @@ import {VectorTileLayer} from '@kepler.gl/layers';
9
9
  import {KeplerTable as KeplerDataset} from '@kepler.gl/table';
10
10
 
11
11
  import SourceDataSelectorFactory from '../common/source-data-selector';
12
+ import FieldSelectorFactory from '../../common/field-selector';
12
13
  import ChannelByValueSelectorFactory from './channel-by-value-selector';
13
14
  import LayerConfigGroupFactory, {ConfigGroupCollapsibleContent} from './layer-config-group';
14
15
  import {LayerColorRangeSelectorFactory, LayerColorSelectorFactory} from './layer-color-selector';
@@ -37,7 +38,8 @@ VectorTileLayerConfiguratorFactory.deps = [
37
38
  LayerConfigGroupFactory,
38
39
  VisConfigSliderFactory,
39
40
  VisConfigSwitchFactory,
40
- SourceDataSelectorFactory
41
+ SourceDataSelectorFactory,
42
+ FieldSelectorFactory
41
43
  ];
42
44
 
43
45
  function VectorTileLayerConfiguratorFactory(
@@ -46,7 +48,9 @@ function VectorTileLayerConfiguratorFactory(
46
48
  LayerColorSelector: ReturnType<typeof LayerColorSelectorFactory>,
47
49
  LayerConfigGroup: ReturnType<typeof LayerConfigGroupFactory>,
48
50
  VisConfigSlider: ReturnType<typeof VisConfigSliderFactory>,
49
- VisConfigSwitch: ReturnType<typeof VisConfigSwitchFactory>
51
+ VisConfigSwitch: ReturnType<typeof VisConfigSwitchFactory>,
52
+ _SourceDataSelector: ReturnType<typeof SourceDataSelectorFactory>,
53
+ FieldSelector: ReturnType<typeof FieldSelectorFactory>
50
54
  ): React.FC<Props> {
51
55
  const VectorTileLayerConfigurator = ({
52
56
  layer,
@@ -190,6 +194,19 @@ function VectorTileLayerConfiguratorFactory(
190
194
  <VisConfigSwitch {...layer.visConfigSettings.radiusUnits} {...visConfiguratorProps} />
191
195
  </ConfigGroupCollapsibleContent>
192
196
  </LayerConfigGroup>
197
+
198
+ {/* Unique ID Field */}
199
+ <LayerConfigGroup {...visConfiguratorProps} label="layer.uniqueIdField">
200
+ <FieldSelector
201
+ fields={layerChannelConfigProps.fields || []}
202
+ value={layer.config.uniqueIdField || null}
203
+ onSelect={(val: any) =>
204
+ layerConfiguratorProps.onChange?.({uniqueIdField: val?.name || null})
205
+ }
206
+ placeholder={'placeholder.selectField'}
207
+ erasable
208
+ />
209
+ </LayerConfigGroup>
193
210
  </StyledLayerConfigurator>
194
211
  );
195
212
  };