@djangocfg/ui-tools 2.1.431 → 2.1.433

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 (171) hide show
  1. package/package.json +28 -14
  2. package/src/common/CodePanelHeader/CodePanelHeader.tsx +121 -0
  3. package/src/common/CodePanelHeader/index.ts +1 -0
  4. package/src/common/blocks/BlockError.tsx +46 -0
  5. package/src/common/blocks/index.ts +34 -0
  6. package/src/common/blocks/schemas.ts +143 -0
  7. package/src/common/blocks/types.ts +76 -0
  8. package/src/common/chips/README.md +67 -0
  9. package/src/common/chips/UrlChip.tsx +105 -0
  10. package/src/common/chips/index.ts +20 -0
  11. package/src/common/link-preview/LinkPreviewCard.tsx +178 -0
  12. package/src/common/link-preview/index.ts +12 -0
  13. package/src/common/link-preview/types.ts +44 -0
  14. package/src/common/tiptap/index.ts +10 -0
  15. package/src/common/tiptap/useNodeAttrs.ts +128 -0
  16. package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +1 -1
  17. package/src/tools/chat/README.md +88 -2
  18. package/src/tools/chat/composer/Composer.tsx +45 -12
  19. package/src/tools/chat/composer/ComposerRichTextarea.tsx +16 -4
  20. package/src/tools/chat/constants.ts +9 -2
  21. package/src/tools/chat/core/extractFirstUrl.ts +45 -0
  22. package/src/tools/chat/core/reducer.ts +32 -8
  23. package/src/tools/chat/core/transport/mappers/pydantic-ai.ts +1 -1
  24. package/src/tools/chat/core/transport/mock.ts +4 -1
  25. package/src/tools/chat/hooks/useChat.ts +20 -20
  26. package/src/tools/chat/lazy.tsx +10 -0
  27. package/src/tools/chat/messages/Attachments.tsx +267 -42
  28. package/src/tools/chat/messages/BubbleContextMenu.tsx +203 -0
  29. package/src/tools/chat/messages/MessageBubble.tsx +175 -31
  30. package/src/tools/chat/messages/MessageList.tsx +107 -21
  31. package/src/tools/chat/messages/ToolCalls.tsx +28 -13
  32. package/src/tools/chat/messages/blocks/MessageBlocks.tsx +70 -17
  33. package/src/tools/chat/messages/blocks/builtin.tsx +6 -3
  34. package/src/tools/chat/messages/blocks/renderers/AudioBlock.tsx +6 -0
  35. package/src/tools/chat/messages/blocks/renderers/ChatImageAlbum.tsx +154 -0
  36. package/src/tools/chat/messages/blocks/renderers/CodeBlock.tsx +94 -6
  37. package/src/tools/chat/messages/blocks/renderers/DiffBlock.tsx +29 -0
  38. package/src/tools/chat/messages/blocks/renderers/GalleryBlock.tsx +17 -10
  39. package/src/tools/chat/messages/blocks/renderers/ImageBlock.tsx +37 -16
  40. package/src/tools/chat/messages/blocks/renderers/JsonBlock.tsx +11 -6
  41. package/src/tools/chat/messages/blocks/renderers/LinkBlock.tsx +32 -0
  42. package/src/tools/chat/messages/blocks/renderers/MapBlock.tsx +181 -19
  43. package/src/tools/chat/messages/blocks/renderers/VideoBlock.tsx +2 -5
  44. package/src/tools/chat/messages/index.ts +13 -0
  45. package/src/tools/chat/public.ts +5 -1
  46. package/src/tools/chat/shell/ChatRoot.tsx +29 -0
  47. package/src/tools/chat/styles/bubbleTokens.ts +18 -0
  48. package/src/tools/chat/styles/index.ts +1 -0
  49. package/src/tools/chat/types/attachment.ts +26 -2
  50. package/src/tools/chat/types/block.ts +56 -9
  51. package/src/tools/chat/types/blockSchemas.ts +197 -0
  52. package/src/tools/chat/types/config.ts +33 -0
  53. package/src/tools/chat/types/events.ts +14 -7
  54. package/src/tools/chat/types/index.ts +4 -2
  55. package/src/tools/chat/types/message.ts +7 -0
  56. package/src/tools/data/JsonTree/JsonViewer.tsx +7 -7
  57. package/src/tools/dev/Map/README.md +30 -345
  58. package/src/tools/dev/Map/cards/index.ts +6 -0
  59. package/src/tools/dev/Map/cards/types.ts +67 -0
  60. package/src/tools/dev/Map/components/CompassChip.tsx +67 -0
  61. package/src/tools/dev/Map/components/DraggableMarkers.tsx +79 -0
  62. package/src/tools/dev/Map/components/GeocoderControl.tsx +148 -63
  63. package/src/tools/dev/Map/components/GeocoderInput.tsx +181 -0
  64. package/src/tools/dev/Map/components/MapContainer/FullscreenDialog.tsx +140 -0
  65. package/src/tools/dev/Map/components/MapContainer/GeolocateHandle.tsx +70 -0
  66. package/src/tools/dev/Map/components/MapContainer/MapInner.tsx +326 -0
  67. package/src/tools/dev/Map/components/MapContainer/MapToolbar.tsx +286 -0
  68. package/src/tools/dev/Map/components/MapContainer/RotateHint.tsx +25 -0
  69. package/src/tools/dev/Map/components/MapContainer/ScrollLockOverlay.tsx +52 -0
  70. package/src/tools/dev/Map/components/MapContainer/constants.ts +15 -0
  71. package/src/tools/dev/Map/components/MapContainer/hooks/useAutoReset.ts +51 -0
  72. package/src/tools/dev/Map/components/MapContainer/hooks/useBasemapState.ts +48 -0
  73. package/src/tools/dev/Map/components/MapContainer/hooks/useExternalMapsUrl.ts +29 -0
  74. package/src/tools/dev/Map/components/MapContainer/index.tsx +29 -0
  75. package/src/tools/dev/Map/components/MapContainer/types.ts +198 -0
  76. package/src/tools/dev/Map/components/MapControls.tsx +27 -0
  77. package/src/tools/dev/Map/components/MapMarkers.tsx +94 -0
  78. package/src/tools/dev/Map/components/MarkerCard.tsx +102 -0
  79. package/src/tools/dev/Map/components/MeasureControl.tsx +219 -0
  80. package/src/tools/dev/Map/components/index.ts +35 -1
  81. package/src/tools/dev/Map/geocode/index.ts +2 -0
  82. package/src/tools/dev/Map/geocode/photon.ts +265 -0
  83. package/src/tools/dev/Map/hooks/index.ts +37 -0
  84. package/src/tools/dev/Map/hooks/useCompass.ts +44 -0
  85. package/src/tools/dev/Map/hooks/useDraggableMarkers.ts +112 -0
  86. package/src/tools/dev/Map/hooks/useGeocoder.ts +155 -0
  87. package/src/tools/dev/Map/hooks/useGeolocation.ts +174 -0
  88. package/src/tools/dev/Map/hooks/useMapControl.ts +5 -1
  89. package/src/tools/dev/Map/hooks/useMapImages.ts +112 -0
  90. package/src/tools/dev/Map/hooks/useMapScrollProtection.ts +155 -0
  91. package/src/tools/dev/Map/hooks/useMapTouchMode.ts +21 -0
  92. package/src/tools/dev/Map/hooks/useMarkerCard.ts +39 -0
  93. package/src/tools/dev/Map/hooks/useMarkers.ts +9 -3
  94. package/src/tools/dev/Map/hooks/useMeasure.ts +169 -0
  95. package/src/tools/dev/Map/hooks/useTerrain.ts +123 -0
  96. package/src/tools/dev/Map/index.ts +59 -2
  97. package/src/tools/dev/Map/layers/index.ts +1 -0
  98. package/src/tools/dev/Map/layers/symbol.ts +93 -0
  99. package/src/tools/dev/Map/lazy.tsx +120 -0
  100. package/src/tools/dev/Map/protocols/pmtiles.ts +73 -0
  101. package/src/tools/dev/Map/styles/index.ts +62 -0
  102. package/src/tools/dev/Map/styles/terrain.ts +29 -0
  103. package/src/tools/dev/Map/types.ts +31 -3
  104. package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +1 -1
  105. package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +5 -12
  106. package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +1 -1
  107. package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +1 -1
  108. package/src/tools/dev/api/OpenapiViewer/components/shared/SendButton.tsx +1 -1
  109. package/src/tools/dev/api/OpenapiViewer/components/shared/http-colors.ts +61 -0
  110. package/src/tools/dev/api/OpenapiViewer/components/shared/ui.tsx +4 -21
  111. package/src/tools/dev/code/MarkdownMessage/MarkdownMessage.tsx +150 -58
  112. package/src/tools/dev/code/MarkdownMessage/README.md +38 -0
  113. package/src/tools/dev/code/MarkdownMessage/builtinRules.tsx +92 -0
  114. package/src/tools/dev/code/MarkdownMessage/index.ts +3 -0
  115. package/src/tools/dev/code/MarkdownMessage/streamingBlocks.ts +164 -0
  116. package/src/tools/dev/code/MarkdownMessage/types.ts +20 -0
  117. package/src/tools/dev/code/PrettyCode/PrettyCode.client.tsx +27 -30
  118. package/src/tools/dev/code/PrettyCode/index.tsx +4 -1
  119. package/src/tools/dev/code/PrettyCode/lazy.tsx +3 -1
  120. package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +168 -3
  121. package/src/tools/forms/MarkdownEditor/README.md +84 -0
  122. package/src/tools/forms/MarkdownEditor/chip/ChipNode.ts +454 -0
  123. package/src/tools/forms/MarkdownEditor/chip/syncChips.ts +80 -0
  124. package/src/tools/forms/MarkdownEditor/filePath/FilePathChip.tsx +68 -0
  125. package/src/tools/forms/MarkdownEditor/filePath/__tests__/detect.test.ts +161 -0
  126. package/src/tools/forms/MarkdownEditor/filePath/detect.ts +289 -0
  127. package/src/tools/forms/MarkdownEditor/filePath/index.ts +15 -0
  128. package/src/tools/forms/MarkdownEditor/index.ts +19 -0
  129. package/src/tools/forms/MarkdownEditor/lazy.tsx +10 -0
  130. package/src/tools/forms/MarkdownEditor/styles.css +134 -2
  131. package/src/tools/forms/MarkdownEditor/url/__tests__/detect.test.ts +121 -0
  132. package/src/tools/forms/MarkdownEditor/url/detect.ts +227 -0
  133. package/src/tools/forms/NotionEditor/BookmarkNode.ts +158 -0
  134. package/src/tools/forms/NotionEditor/BookmarkView.tsx +45 -0
  135. package/src/tools/forms/NotionEditor/MapEditLayer.tsx +199 -0
  136. package/src/tools/forms/NotionEditor/MapNode.ts +218 -0
  137. package/src/tools/forms/NotionEditor/MapView.tsx +103 -0
  138. package/src/tools/forms/NotionEditor/NotionEditor.tsx +7 -2
  139. package/src/tools/forms/NotionEditor/README.md +109 -188
  140. package/src/tools/forms/NotionEditor/extensions.ts +15 -0
  141. package/src/tools/forms/NotionEditor/slashItems.ts +19 -0
  142. package/src/tools/forms/NotionEditor/styles.css +25 -0
  143. package/src/tools/forms/NotionEditor/types.ts +9 -0
  144. package/src/tools/input/SpeechRecognition/core/engine/external.ts +5 -0
  145. package/src/tools/input/SpeechRecognition/types.ts +10 -0
  146. package/src/tools/media/Gallery/components/lightbox/GalleryLightbox.tsx +39 -22
  147. package/src/tools/media/ImageViewer/README.md +9 -0
  148. package/src/tools/media/ImageViewer/components/ImageViewer.tsx +165 -79
  149. package/src/tools/media/ImageViewer/components/LightboxChrome.tsx +137 -0
  150. package/src/tools/media/ImageViewer/components/MiddleEllipsis.tsx +46 -0
  151. package/src/tools/media/ImageViewer/components/index.ts +2 -0
  152. package/src/tools/media/ImageViewer/hooks/index.ts +3 -0
  153. package/src/tools/media/ImageViewer/hooks/useIdleChrome.ts +73 -0
  154. package/src/tools/media/ImageViewer/types.ts +10 -0
  155. package/src/tools/media/ImageViewer/utils/constants.ts +28 -0
  156. package/src/tools/media/ImageViewer/utils/index.ts +4 -0
  157. package/src/tools/media/VideoPlayer/VideoPlayer.tsx +34 -40
  158. package/src/tools/media/VideoPlayer/canvas/youtube-canvas.tsx +13 -13
  159. package/src/tools/media/VideoPlayer/parts/center-play.tsx +26 -0
  160. package/src/tools/media/VideoPlayer/parts/index.ts +2 -0
  161. package/src/tools/media/VideoPlayer/styles/video-player.css +102 -53
  162. package/src/tools/visual/charts/SmoothLine/README.md +97 -0
  163. package/src/tools/visual/charts/SmoothLine/SmoothLine.tsx +177 -0
  164. package/src/tools/visual/charts/SmoothLine/index.ts +21 -0
  165. package/src/tools/visual/charts/SmoothLine/lazy.tsx +23 -0
  166. package/src/tools/visual/charts/SmoothLine/smoothPath.ts +209 -0
  167. package/src/tools/visual/charts/SmoothLine/types.ts +98 -0
  168. package/src/tools/visual/charts/SmoothLine/useSmoothSeries.ts +429 -0
  169. package/src/tools/visual/indicators/Fps/types.ts +1 -1
  170. package/src/tools/chat/messages/blocks/renderers/LottieBlock.tsx +0 -11
  171. package/src/tools/dev/Map/components/MapContainer.tsx +0 -198
@@ -0,0 +1,93 @@
1
+ import type { LayerProps, SymbolLayerSpecification } from 'react-map-gl/maplibre'
2
+ import type { SymbolLayerOptions } from '../types'
3
+
4
+ /**
5
+ * `layout` / `paint` on `SymbolLayerSpecification` are declared optional
6
+ * (`layout?: {…}`), so `SymbolLayerSpecification['layout']` widens to
7
+ * `{…} | undefined`. Indexing that union per-key (e.g.
8
+ * `SymbolLayerSpecification['layout']['icon-image']`) is what leaks
9
+ * TS2339 into stricter consumer typechecks (cmdop's `tsc --noEmit`).
10
+ * Strip the `| undefined` once here so the rest of the file builds a
11
+ * single, fully-typed object literal instead of per-key indexed casts.
12
+ */
13
+ type SymbolLayout = NonNullable<SymbolLayerSpecification['layout']>
14
+ type SymbolPaint = NonNullable<SymbolLayerSpecification['paint']>
15
+
16
+ /**
17
+ * Create a `symbol` layer that draws a registered icon (and optional text
18
+ * label) per feature — the data-driven path for many icons at scale.
19
+ *
20
+ * Register the icon image first with the `useMapImages` hook, then pass its
21
+ * id (or a data-driven expression like `['get', 'icon']`) as `iconImage`.
22
+ *
23
+ * Two ways to do custom markers:
24
+ * - **For a few rich markers**, use a DOM `MapMarker` with `children` (or its
25
+ * `color`/`size` props) — full React content per marker.
26
+ * - **For many data-driven icons at scale**, use this symbol layer over a
27
+ * GeoJSON source: MapLibre renders the icons on the GPU from the sprite.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * useMapImages([{ id: 'pin', url: '/icons/pin.png' }])
32
+ * const layer = createSymbolLayer({
33
+ * id: 'places',
34
+ * sourceId: 'places-src',
35
+ * iconImage: 'pin',
36
+ * iconSize: 0.5,
37
+ * textField: ['get', 'name'],
38
+ * })
39
+ * ```
40
+ */
41
+ export function createSymbolLayer({
42
+ id,
43
+ sourceId,
44
+ iconImage,
45
+ iconSize,
46
+ iconAllowOverlap = true,
47
+ textField,
48
+ textSize = 12,
49
+ textColor = '#1f2937',
50
+ textOffset = [0, 1.2],
51
+ minZoom,
52
+ maxZoom,
53
+ }: SymbolLayerOptions): LayerProps {
54
+ // Build one fully-typed `layout` literal up front (all keys present-or-
55
+ // omitted), so TS checks it as a unit against `SymbolLayout` rather than
56
+ // assigning per-key into a `… | undefined` indexed type. The option
57
+ // values are the author's loosened `DataDrivenValue<…>` (static value OR
58
+ // expression array); each is asserted to its concrete layout-property
59
+ // type via the non-nullable `SymbolLayout[…]` indexed access.
60
+ const labelKeys: Partial<SymbolLayout> =
61
+ textField !== undefined
62
+ ? {
63
+ 'text-field': textField as SymbolLayout['text-field'],
64
+ 'text-size': textSize as SymbolLayout['text-size'],
65
+ 'text-offset': textOffset,
66
+ 'text-anchor': 'top',
67
+ }
68
+ : {}
69
+
70
+ const layout: SymbolLayout = {
71
+ 'icon-image': iconImage as SymbolLayout['icon-image'],
72
+ 'icon-allow-overlap': iconAllowOverlap,
73
+ ...(iconSize !== undefined
74
+ ? { 'icon-size': iconSize as SymbolLayout['icon-size'] }
75
+ : {}),
76
+ ...labelKeys,
77
+ }
78
+
79
+ const paint: SymbolPaint = textField !== undefined ? { 'text-color': textColor } : {}
80
+
81
+ const layer: LayerProps = {
82
+ id,
83
+ type: 'symbol',
84
+ source: sourceId,
85
+ layout,
86
+ paint,
87
+ }
88
+
89
+ if (minZoom !== undefined) layer.minzoom = minZoom
90
+ if (maxZoom !== undefined) layer.maxzoom = maxZoom
91
+
92
+ return layer
93
+ }
@@ -78,19 +78,50 @@ export {
78
78
  CustomOverlay,
79
79
  MapLegend,
80
80
  LayerSwitcher,
81
+ MeasureControl,
82
+ DraggableMarkers,
83
+ GeocoderInput,
84
+ GeocoderControl,
85
+ CompassChip,
86
+ MarkerCard,
87
+ MapMarkers,
81
88
  } from './components';
82
89
 
90
+ export type { DraggableMarkersProps, DraggablePoint } from './components';
91
+ export type { GeocoderInputProps, GeocoderControlProps } from './components';
92
+ export type { CompassChipProps } from './components';
93
+ // Marker info-cards — light wrappers (no maplibre at module scope), safe on
94
+ // the synchronous surface. `MarkerCard` data type lives in `./cards`.
95
+ export type {
96
+ MarkerCardProps,
97
+ MapMarkersProps,
98
+ MarkerWithCard,
99
+ MarkerCardAction,
100
+ MarkerCardData,
101
+ MarkerCardActionData,
102
+ } from './components';
103
+ // Card-data shape under `MarkerCardInfo` (the `MarkerCard` value is the
104
+ // component re-exported above).
105
+ export type { MarkerCard as MarkerCardInfo } from './cards';
106
+
107
+ // Geocoder resolver — pure, key-free Photon (light; no maplibre runtime).
108
+ export { photonResolve, cameraForResult } from './geocode';
109
+ export type { GeocodeResult, GeocodeKind, ResolveGeocode } from './geocode';
110
+
83
111
  export { MapProvider, useMapContext, MapContext } from './context';
84
112
  export type { MapProviderProps } from './context';
85
113
 
86
114
  export type {
87
115
  MapContainerProps,
116
+ GeolocateOptions,
117
+ GeolocatePosition,
88
118
  MapMarkerProps,
89
119
  MapPopupProps,
90
120
  MapClusterProps,
91
121
  MapSourceProps,
92
122
  MapLayerProps,
93
123
  MapControlsProps,
124
+ MeasureControlProps,
94
125
  } from './components';
95
126
 
96
127
  export type {
@@ -98,3 +129,92 @@ export type {
98
129
  MapStyleKey,
99
130
  MarkerData,
100
131
  } from './types';
132
+
133
+ // Basemap styles (incl. free OpenFreeMap keys) + PMTiles protocol helper —
134
+ // light, safe to load synchronously alongside the lazy container.
135
+ export {
136
+ MAP_STYLES,
137
+ getMapStyle,
138
+ MAP_STYLE_LABELS,
139
+ KEY_FREE_STYLES,
140
+ resolveBasemapOptions,
141
+ TERRAIN_DEM,
142
+ TERRAIN_DEM_URL,
143
+ } from './styles';
144
+ export { addPMTilesProtocol, pmtilesSourceUrl } from './protocols/pmtiles';
145
+
146
+ // Hooks — pure React glue (maplibre is a TYPE-only import here, so these
147
+ // add no runtime weight and are safe on the synchronous lazy surface).
148
+ export {
149
+ useMap,
150
+ useMapControl,
151
+ useMarkers,
152
+ useMapEvents,
153
+ useMapViewport,
154
+ useMapLayers,
155
+ useSpiderfy,
156
+ useMapTouchMode,
157
+ useMapScrollProtection,
158
+ useMapImages,
159
+ useMeasure,
160
+ useTerrain,
161
+ useCompass,
162
+ useGeolocation,
163
+ useControl,
164
+ useDraggableMarkers,
165
+ useGeocoder,
166
+ useMarkerCard,
167
+ type UseMarkerCardResult,
168
+ type UseCompassResult,
169
+ type UseGeocoderOptions,
170
+ type UseGeocoderResult,
171
+ type UseSpiderfyOptions,
172
+ type UseSpiderfyResult,
173
+ type MapImage,
174
+ type UseMapImagesResult,
175
+ type UseMeasureResult,
176
+ type MeasureMode,
177
+ type UseTerrainOptions,
178
+ type GeolocationFix,
179
+ type GeolocationStatus,
180
+ type UseGeolocationOptions,
181
+ type UseGeolocationResult,
182
+ type UseDraggableMarkersOptions,
183
+ type UseDraggableMarkersResult,
184
+ type DraggableMarkerHandlers,
185
+ } from './hooks';
186
+
187
+ // Layer factories — pure functions returning `LayerProps` (type-only deps).
188
+ export {
189
+ createClusterLayers,
190
+ createPointLayer,
191
+ createHeatmapLayer,
192
+ createPolygonLayer,
193
+ createPolygonOutlineLayer,
194
+ createHighlightLayer,
195
+ createLineLayer,
196
+ createRouteLayers,
197
+ createDashedLineLayer,
198
+ createAnimatedLineLayer,
199
+ createSymbolLayer,
200
+ } from './layers';
201
+
202
+ // Geometry + spiderfy utils — pure, dependency-free helpers.
203
+ export {
204
+ calculateBounds,
205
+ calculateCenter,
206
+ calculateDistance,
207
+ isPointInBounds,
208
+ expandBounds,
209
+ toGeoJSON,
210
+ fromGeoJSON,
211
+ createPoint,
212
+ createPolygon,
213
+ createLineString,
214
+ createFeatureCollection,
215
+ offsetOverlappingMarkers,
216
+ getSpiderfyPositions,
217
+ groupOverlappingMarkers,
218
+ hasOverlappingMarkers,
219
+ getOverlapStats,
220
+ } from './utils';
@@ -0,0 +1,73 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * PMTiles protocol registration for MapLibre.
5
+ *
6
+ * PMTiles is a single-file basemap format (Protomaps, BSD + ODbL) that
7
+ * MapLibre reads DIRECTLY in the browser via HTTP range requests — no tile
8
+ * server, no API key. Host one `.pmtiles` file on any static CDN (S3/R2/
9
+ * GitHub Pages) for a self-hosted / offline basemap.
10
+ *
11
+ * Usage (register ONCE per app lifecycle, e.g. in a top-level effect):
12
+ *
13
+ * ```ts
14
+ * import maplibregl from 'maplibre-gl';
15
+ * import { addPMTilesProtocol } from '@djangocfg/ui-tools/map';
16
+ *
17
+ * useEffect(() => addPMTilesProtocol(maplibregl), []); // returns a cleanup fn
18
+ * ```
19
+ *
20
+ * Then reference the file from a style/source with a `pmtiles://` URL:
21
+ *
22
+ * ```ts
23
+ * // source url: pmtiles://https://your-cdn.example/basemap.pmtiles
24
+ * ```
25
+ *
26
+ * The `pmtiles` package is dynamically imported so it costs nothing until a
27
+ * consumer actually opts into PMTiles.
28
+ */
29
+
30
+ /** Minimal shape of the maplibre-gl module we touch (avoids a hard dep). */
31
+ interface MapLibreLike {
32
+ addProtocol: (
33
+ name: string,
34
+ handler: (...args: unknown[]) => unknown,
35
+ ) => void
36
+ removeProtocol: (name: string) => void
37
+ }
38
+
39
+ const PROTOCOL = 'pmtiles'
40
+
41
+ /**
42
+ * Register the `pmtiles://` protocol on the given maplibre-gl module.
43
+ * Returns a cleanup function that removes it — wire it as an effect's
44
+ * teardown. Safe to call once; calling again re-registers (idempotent at
45
+ * the maplibre level, last handler wins).
46
+ */
47
+ export function addPMTilesProtocol(maplibregl: MapLibreLike): () => void {
48
+ let cancelled = false
49
+
50
+ // Dynamic import keeps `pmtiles` out of the bundle until used.
51
+ void import('pmtiles').then(({ Protocol }) => {
52
+ if (cancelled) return
53
+ const protocol = new Protocol()
54
+ maplibregl.addProtocol(
55
+ PROTOCOL,
56
+ protocol.tile as (...args: unknown[]) => unknown,
57
+ )
58
+ })
59
+
60
+ return () => {
61
+ cancelled = true
62
+ try {
63
+ maplibregl.removeProtocol(PROTOCOL)
64
+ } catch {
65
+ // protocol may not have registered yet (import still in flight)
66
+ }
67
+ }
68
+ }
69
+
70
+ /** Build a `pmtiles://` source URL from a plain https URL to a `.pmtiles` file. */
71
+ export function pmtilesSourceUrl(fileUrl: string): string {
72
+ return `${PROTOCOL}://${fileUrl}`
73
+ }
@@ -1,8 +1,28 @@
1
+ /**
2
+ * Built-in basemap styles.
3
+ *
4
+ * `light` / `dark` / `streets` — CARTO, free, no API key.
5
+ * `satellite` — MapTiler, **needs an API key** in the URL (not injected
6
+ * here; pass your own keyed style URL to `mapStyle` to use it).
7
+ * `liberty` / `bright` / `positron` — **OpenFreeMap** public instance:
8
+ * genuinely free, no key, no limits, commercial use allowed (donation-
9
+ * funded, no SLA). See `@plans/plan14-map-capabilities`.
10
+ *
11
+ * For a self-hosted / offline single-file basemap, use the PMTiles helper
12
+ * (`addPMTilesProtocol`) and pass a `pmtiles://…`-backed style instead.
13
+ */
14
+ // Free DEM source for 3D terrain + hillshade (AWS Terrarium, key-free).
15
+ export { TERRAIN_DEM, TERRAIN_DEM_URL } from './terrain'
16
+
1
17
  export const MAP_STYLES = {
2
18
  light: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
3
19
  dark: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
4
20
  streets: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',
5
21
  satellite: 'https://api.maptiler.com/maps/satellite/style.json',
22
+ // OpenFreeMap — key-free, no limits, commercial OK.
23
+ liberty: 'https://tiles.openfreemap.org/styles/liberty',
24
+ bright: 'https://tiles.openfreemap.org/styles/bright',
25
+ positron: 'https://tiles.openfreemap.org/styles/positron',
6
26
  } as const
7
27
 
8
28
  export type MapStyleKey = keyof typeof MAP_STYLES
@@ -13,3 +33,45 @@ export function getMapStyle(key: MapStyleKey | string): string {
13
33
  }
14
34
  return key
15
35
  }
36
+
37
+ /** Human label for the basemap switcher menu. */
38
+ export const MAP_STYLE_LABELS: Record<MapStyleKey, string> = {
39
+ light: 'Light',
40
+ dark: 'Dark',
41
+ streets: 'Streets',
42
+ satellite: 'Satellite',
43
+ liberty: 'Liberty',
44
+ bright: 'Bright',
45
+ positron: 'Positron',
46
+ }
47
+
48
+ /** Basemap keys that work with NO API key (safe to always offer). */
49
+ export const KEY_FREE_STYLES: readonly MapStyleKey[] = [
50
+ 'light',
51
+ 'dark',
52
+ 'streets',
53
+ 'liberty',
54
+ 'bright',
55
+ 'positron',
56
+ ]
57
+
58
+ /**
59
+ * Pick the basemap options to show in the switcher, smartly:
60
+ * - all key-free built-ins, PLUS
61
+ * - `satellite` ONLY when usable — i.e. the host passed its own keyed
62
+ * satellite style (a full URL via `mapStyle`/`current`), since the
63
+ * built-in MapTiler satellite needs a key we don't ship.
64
+ *
65
+ * A host can override entirely by passing an explicit `requested` list.
66
+ */
67
+ export function resolveBasemapOptions(
68
+ requested: readonly MapStyleKey[] | undefined,
69
+ current: MapStyleKey | string | undefined,
70
+ ): MapStyleKey[] {
71
+ if (requested && requested.length) return [...requested]
72
+ const opts: MapStyleKey[] = [...KEY_FREE_STYLES]
73
+ // Offer satellite only if the active style is a host-supplied URL (keyed)
74
+ // OR the current key is already 'satellite' (host wired a key elsewhere).
75
+ if (current === 'satellite') opts.push('satellite')
76
+ return opts
77
+ }
@@ -0,0 +1,29 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Free DEM (digital-elevation-model) source for 3D terrain + hillshade.
5
+ *
6
+ * Uses **AWS Terrain Tiles** — Terrarium-encoded elevation PNGs served as
7
+ * free, open data from the public `elevation-tiles-prod` S3 bucket. No API
8
+ * key, no account, no usage limit. MapLibre renders 3D terrain and hillshade
9
+ * natively from a `raster-dem` source — no external tile service needed.
10
+ *
11
+ * See `@plans/plan14-map-capabilities` / `reports/research.md` §2.
12
+ */
13
+ import type { RasterDEMSourceSpecification } from 'maplibre-gl'
14
+
15
+ /** Terrarium-encoded DEM tile URL template (AWS open data, key-free). */
16
+ export const TERRAIN_DEM_URL =
17
+ 'https://elevation-tiles-prod.s3.amazonaws.com/terrarium/{z}/{x}/{y}.png'
18
+
19
+ /**
20
+ * Ready-to-use `raster-dem` source spec for the free AWS Terrarium DEM.
21
+ * Pass straight to `map.addSource(id, TERRAIN_DEM)`.
22
+ */
23
+ export const TERRAIN_DEM: RasterDEMSourceSpecification = {
24
+ type: 'raster-dem',
25
+ tiles: [TERRAIN_DEM_URL],
26
+ encoding: 'terrarium',
27
+ tileSize: 256,
28
+ maxzoom: 15,
29
+ }
@@ -65,7 +65,7 @@ export interface MapContextValue {
65
65
  export interface MapControlActions {
66
66
  flyTo: (center: [number, number], zoom?: number, options?: { duration?: number }) => void
67
67
  easeTo: (center: [number, number], zoom?: number, options?: { duration?: number }) => void
68
- fitBounds: (bounds: LngLatBoundsLike, options?: { padding?: number; duration?: number }) => void
68
+ fitBounds: (bounds: LngLatBoundsLike, options?: { padding?: number; duration?: number; maxZoom?: number }) => void
69
69
  zoomIn: () => void
70
70
  zoomOut: () => void
71
71
  resetView: (initialViewport: MapViewport) => void
@@ -80,7 +80,7 @@ export interface MarkerActions {
80
80
  removeMarker: (id: string) => void
81
81
  updateMarker: (id: string, updates: Partial<Omit<MarkerData, 'id'>>) => void
82
82
  clearMarkers: () => void
83
- fitToMarkers: (padding?: number) => void
83
+ fitToMarkers: (paddingOrOpts?: number | { padding?: number; maxZoom?: number }) => void
84
84
  }
85
85
 
86
86
  // ============================================================================
@@ -108,7 +108,9 @@ export interface MapMouseEvent {
108
108
  // Style Types
109
109
  // ============================================================================
110
110
 
111
- export type MapStyleKey = 'light' | 'dark' | 'streets' | 'satellite'
111
+ // Single source of truth derived from `MAP_STYLES` so new basemap keys
112
+ // (e.g. OpenFreeMap `liberty`/`bright`/`positron`) flow through automatically.
113
+ export type { MapStyleKey } from './styles'
112
114
 
113
115
  // ============================================================================
114
116
  // Layer Types (from react-map-gl & maplibre-gl)
@@ -167,6 +169,32 @@ export interface RouteLayerOptions extends Omit<LineLayerOptions, 'id'> {
167
169
  sourceId: string
168
170
  }
169
171
 
172
+ // ============================================================================
173
+ // Symbol Layer Types
174
+ // ============================================================================
175
+
176
+ /** A data-driven expression (e.g. `['get', 'icon']`) or a static value. */
177
+ export type DataDrivenValue<T> = T | unknown[]
178
+
179
+ export interface SymbolLayerOptions {
180
+ id: string
181
+ sourceId: string
182
+ /**
183
+ * Registered image id (see `useMapImages`) OR a data-driven expression
184
+ * selecting one per feature, e.g. `['get', 'icon']`.
185
+ */
186
+ iconImage: DataDrivenValue<string>
187
+ iconSize?: DataDrivenValue<number>
188
+ iconAllowOverlap?: boolean
189
+ /** Property name or expression for an optional text label. */
190
+ textField?: DataDrivenValue<string>
191
+ textSize?: DataDrivenValue<number>
192
+ textColor?: string
193
+ textOffset?: [number, number]
194
+ minZoom?: number
195
+ maxZoom?: number
196
+ }
197
+
170
198
  // ============================================================================
171
199
  // Control Types
172
200
  // ============================================================================
@@ -47,7 +47,7 @@ function IconButton({ label, onClick, children, active }: IconButtonProps) {
47
47
  className={cn(
48
48
  'shrink-0 h-6 w-6 inline-flex items-center justify-center rounded',
49
49
  'text-muted-foreground/60 hover:text-foreground hover:bg-muted transition-colors',
50
- active && 'text-emerald-500 hover:text-emerald-500',
50
+ active && 'text-success hover:text-success',
51
51
  )}
52
52
  >
53
53
  {children}
@@ -2,25 +2,18 @@
2
2
 
3
3
  import { cn } from '@djangocfg/ui-core/lib';
4
4
 
5
+ import { getStatusCodeStyle } from '../../../shared/http-colors';
6
+
5
7
  interface StatusTagProps {
6
8
  code: string;
7
9
  }
8
10
 
9
- /** HTTP status pill. Colours map to the usual conventions: 2xx green,
10
- * 3xx blue, 4xx amber, 5xx red, non-numeric (``default``) neutral.
11
+ /** HTTP status pill. Colours map to the usual conventions: 2xx success,
12
+ * 3xx info, 4xx warning, 5xx destructive, non-numeric (``default``) neutral.
11
13
  * Codes that don't parse as integers render in the neutral style so we
12
14
  * never flash a wrong-severity colour at the user. */
13
15
  export function StatusTag({ code }: StatusTagProps) {
14
- const numeric = Number.parseInt(code, 10);
15
- const cls = !Number.isFinite(numeric)
16
- ? 'bg-muted text-muted-foreground border-border'
17
- : numeric >= 500
18
- ? 'bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25'
19
- : numeric >= 400
20
- ? 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25'
21
- : numeric >= 300
22
- ? 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25'
23
- : 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25';
16
+ const cls = getStatusCodeStyle(code);
24
17
  return (
25
18
  <span className={cn(
26
19
  'inline-flex items-center justify-center rounded border px-2 py-0.5 font-mono text-[11px] font-bold leading-none shrink-0 tabular-nums',
@@ -77,7 +77,7 @@ export function SectionHeader({ sectionId, title, badge, open, onToggle }: Secti
77
77
  className={cn(
78
78
  'ml-auto shrink-0 p-1 rounded text-muted-foreground/40 hover:text-foreground hover:bg-muted transition-all',
79
79
  'opacity-0 group-hover/section:opacity-100 focus-visible:opacity-100',
80
- copied && 'opacity-100 text-emerald-500',
80
+ copied && 'opacity-100 text-success',
81
81
  )}
82
82
  >
83
83
  {copied ? <Check className="h-3 w-3" /> : <Link2 className="h-3 w-3" />}
@@ -167,7 +167,7 @@ export function SchemaCopyMenu({ schema, endpoints, baseUrl, variant = 'button',
167
167
  <div className="flex w-full items-center gap-2">
168
168
  <span className="text-xs font-medium flex-1">{label.title}</span>
169
169
  {isDone ? (
170
- <span className="inline-flex items-center gap-1 text-[10px] text-emerald-500">
170
+ <span className="inline-flex items-center gap-1 text-[10px] text-success">
171
171
  <Check className="h-3 w-3" /> Copied
172
172
  </span>
173
173
  ) : size ? (
@@ -62,7 +62,7 @@ export function SendButton({ className }: SendButtonProps) {
62
62
  return (
63
63
  <div className={cn('space-y-2', className)}>
64
64
  {blockers.length > 0 && (
65
- <div className="flex items-start gap-2 rounded-md border border-amber-500/25 bg-amber-500/[0.06] px-3 py-2 text-[11px] text-amber-600 dark:text-amber-400">
65
+ <div className="flex items-start gap-2 rounded-md border border-warning-border bg-warning-background px-3 py-2 text-[11px] text-warning-foreground">
66
66
  <AlertCircle className="h-3.5 w-3.5 shrink-0 mt-px" />
67
67
  <span className="leading-snug">{blockers[0]}</span>
68
68
  </div>
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shared HTTP method + status → semantic status-token mappings.
3
+ *
4
+ * Single source of truth for the badge colours used across the OpenAPI
5
+ * viewer (method pills in the request list, status pills in responses).
6
+ * Uses ui-core's semantic status surface tokens (the tinted banner set:
7
+ * `bg-{status}-background` + `text-{status}-foreground` + `border-{status}-border`)
8
+ * so badges adapt to theme + preset instead of hardcoded colour scales.
9
+ *
10
+ * HTTP → status mapping:
11
+ * 2xx / GET → success · 3xx → info · 4xx / PUT / PATCH → warning ·
12
+ * 5xx / DELETE → destructive · POST → info.
13
+ */
14
+
15
+ type StatusKind = 'success' | 'info' | 'warning' | 'destructive';
16
+
17
+ const BADGE_CLASS: Record<StatusKind, string> = {
18
+ success: 'bg-success-background text-success-foreground border-success-border',
19
+ info: 'bg-info-background text-info-foreground border-info-border',
20
+ warning: 'bg-warning-background text-warning-foreground border-warning-border',
21
+ destructive: 'bg-destructive-background text-destructive-foreground border-destructive-border',
22
+ };
23
+
24
+ const NEUTRAL_BADGE = 'bg-muted text-muted-foreground border-border';
25
+
26
+ const METHOD_KIND: Record<string, StatusKind> = {
27
+ GET: 'success',
28
+ POST: 'info',
29
+ PUT: 'warning',
30
+ PATCH: 'warning',
31
+ DELETE: 'destructive',
32
+ };
33
+
34
+ /** Badge classes for an HTTP method. Unknown methods fall back to neutral. */
35
+ export function getMethodStyle(method: string): string {
36
+ const kind = METHOD_KIND[method.toUpperCase()];
37
+ return kind ? BADGE_CLASS[kind] : NEUTRAL_BADGE;
38
+ }
39
+
40
+ function statusKind(status: number): StatusKind {
41
+ if (status >= 500) return 'destructive';
42
+ if (status >= 400) return 'warning';
43
+ if (status >= 300) return 'info';
44
+ return 'success';
45
+ }
46
+
47
+ /** Badge classes for a numeric HTTP status code. */
48
+ export function getStatusStyle(status: number): string {
49
+ return BADGE_CLASS[statusKind(status)];
50
+ }
51
+
52
+ /**
53
+ * Badge classes for an HTTP status *code string* (e.g. ``"200"``,
54
+ * ``"default"``). Non-numeric codes render neutral so we never flash a
55
+ * wrong-severity colour at the user.
56
+ */
57
+ export function getStatusCodeStyle(code: string): string {
58
+ const numeric = Number.parseInt(code, 10);
59
+ if (!Number.isFinite(numeric)) return NEUTRAL_BADGE;
60
+ return getStatusStyle(numeric);
61
+ }
@@ -12,27 +12,10 @@ import React from 'react';
12
12
  import { cn } from '@djangocfg/ui-core/lib';
13
13
 
14
14
  // ─── Style helpers ────────────────────────────────────────────────────────────
15
-
16
- const METHOD_STYLES: Record<string, string> = {
17
- GET: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25',
18
- POST: 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25',
19
- PUT: 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25',
20
- PATCH: 'bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/25',
21
- DELETE: 'bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25',
22
- } as const;
23
-
24
- const METHOD_FALLBACK = 'bg-muted text-muted-foreground border-border';
25
-
26
- export function getMethodStyle(method: string): string {
27
- return METHOD_STYLES[method.toUpperCase()] ?? METHOD_FALLBACK;
28
- }
29
-
30
- export function getStatusStyle(status: number): string {
31
- if (status >= 500) return 'bg-red-500/10 text-red-500 dark:text-red-400 border-red-500/25';
32
- if (status >= 400) return 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25';
33
- if (status >= 300) return 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25';
34
- return 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25';
35
- }
15
+ // Method/status → semantic status-token mappings live in ./http-colors and are
16
+ // re-exported here so existing imports of `./ui` keep working.
17
+ export { getMethodStyle, getStatusStyle } from './http-colors';
18
+ import { getMethodStyle, getStatusStyle } from './http-colors';
36
19
 
37
20
  // ``relativePath`` lives in utils/url.ts now — re-exported here so every
38
21
  // component that used to import it from ``./ui`` keeps working.