@djangocfg/ui-tools 2.1.384 → 2.1.387

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 (69) hide show
  1. package/README.md +25 -11
  2. package/dist/ChatRoot-4KM2JMGA.mjs +6 -0
  3. package/dist/{ChatRoot-JVR3M3H2.mjs.map → ChatRoot-4KM2JMGA.mjs.map} +1 -1
  4. package/dist/ChatRoot-OILWMMZ6.cjs +15 -0
  5. package/dist/{ChatRoot-LXIUBOXF.cjs.map → ChatRoot-OILWMMZ6.cjs.map} +1 -1
  6. package/dist/DictationField-AS2F33WI.cjs +13 -0
  7. package/dist/{DictationField-U25MEYAL.mjs.map → DictationField-AS2F33WI.cjs.map} +1 -1
  8. package/dist/DictationField-WPONUCYE.mjs +4 -0
  9. package/dist/{DictationField-XWR5VOID.cjs.map → DictationField-WPONUCYE.mjs.map} +1 -1
  10. package/dist/MapContainer-AKIPABJK.mjs +4 -0
  11. package/dist/MapContainer-AKIPABJK.mjs.map +1 -0
  12. package/dist/MapContainer-STVDMC36.cjs +17 -0
  13. package/dist/MapContainer-STVDMC36.cjs.map +1 -0
  14. package/dist/{MapContainer-76YL2JXL.cjs → chunk-5D2OCOPQ.cjs} +3 -2
  15. package/dist/chunk-5D2OCOPQ.cjs.map +1 -0
  16. package/dist/{MapContainer-7HXBI3OH.mjs → chunk-7CWGZPO3.mjs} +3 -3
  17. package/dist/chunk-7CWGZPO3.mjs.map +1 -0
  18. package/dist/{chunk-4PFW7MIJ.cjs → chunk-ADEN3UA4.cjs} +60 -5
  19. package/dist/chunk-ADEN3UA4.cjs.map +1 -0
  20. package/dist/chunk-BVESQTBM.mjs +1439 -0
  21. package/dist/chunk-BVESQTBM.mjs.map +1 -0
  22. package/dist/{chunk-PEKBT75W.mjs → chunk-DMX7W4XZ.mjs} +53 -1387
  23. package/dist/chunk-DMX7W4XZ.mjs.map +1 -0
  24. package/dist/chunk-HNIMIIFR.mjs +1361 -0
  25. package/dist/chunk-HNIMIIFR.mjs.map +1 -0
  26. package/dist/chunk-L25HA3TM.cjs +1478 -0
  27. package/dist/chunk-L25HA3TM.cjs.map +1 -0
  28. package/dist/{chunk-HPK3EWBF.cjs → chunk-TBSHZO5R.cjs} +50 -1409
  29. package/dist/chunk-TBSHZO5R.cjs.map +1 -0
  30. package/dist/chunk-TSNRU3UO.cjs +1387 -0
  31. package/dist/chunk-TSNRU3UO.cjs.map +1 -0
  32. package/dist/{chunk-C2YN6WEO.mjs → chunk-UNCS5V5F.mjs} +61 -7
  33. package/dist/chunk-UNCS5V5F.mjs.map +1 -0
  34. package/dist/index.cjs +1236 -1768
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +780 -780
  37. package/dist/index.d.ts +780 -780
  38. package/dist/index.mjs +853 -1423
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/launcher-5WYPDPEP.mjs +7 -0
  41. package/dist/launcher-5WYPDPEP.mjs.map +1 -0
  42. package/dist/launcher-FCI3LTDY.css +7 -0
  43. package/dist/launcher-FCI3LTDY.css.map +1 -0
  44. package/dist/launcher-QAOG2NUI.cjs +60 -0
  45. package/dist/launcher-QAOG2NUI.cjs.map +1 -0
  46. package/package.json +23 -18
  47. package/src/tools/AudioPlayer/lazy.tsx +100 -0
  48. package/src/tools/Chat/README.md +85 -1
  49. package/src/tools/Chat/context/ChatProvider.tsx +42 -0
  50. package/src/tools/Chat/lazy.tsx +213 -1
  51. package/src/tools/CodeEditor/lazy.tsx +70 -0
  52. package/src/tools/Map/lazy.tsx +38 -1
  53. package/src/tools/MarkdownEditor/lazy.tsx +42 -0
  54. package/src/tools/SpeechRecognition/README.md +48 -0
  55. package/src/tools/SpeechRecognition/core/index.ts +6 -1
  56. package/src/tools/SpeechRecognition/core/logger.ts +107 -1
  57. package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +15 -4
  58. package/src/tools/SpeechRecognition/index.ts +9 -0
  59. package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +37 -2
  60. package/dist/ChatRoot-JVR3M3H2.mjs +0 -5
  61. package/dist/ChatRoot-LXIUBOXF.cjs +0 -14
  62. package/dist/DictationField-U25MEYAL.mjs +0 -4
  63. package/dist/DictationField-XWR5VOID.cjs +0 -13
  64. package/dist/MapContainer-76YL2JXL.cjs.map +0 -1
  65. package/dist/MapContainer-7HXBI3OH.mjs.map +0 -1
  66. package/dist/chunk-4PFW7MIJ.cjs.map +0 -1
  67. package/dist/chunk-C2YN6WEO.mjs.map +0 -1
  68. package/dist/chunk-HPK3EWBF.cjs.map +0 -1
  69. package/dist/chunk-PEKBT75W.mjs.map +0 -1
@@ -0,0 +1,70 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * `@djangocfg/ui-tools/code-editor` subpath entrypoint.
5
+ *
6
+ * Monaco itself weighs ~550 KB minified — we must never pay that cost
7
+ * unless the editor actually mounts. The `Lazy*` wrappers here dynamically
8
+ * import the `Editor` and `DiffEditor` components, which transitively pull
9
+ * in `monaco-editor` and the worker setup.
10
+ *
11
+ * The rest of the surface (types, EditorProvider, useMonaco hook, helpers)
12
+ * is light:
13
+ * - `monaco-editor` is referenced only as a TS type (`type *`) — erased
14
+ * at compile time.
15
+ * - `useMonaco` performs a dynamic `import('monaco-editor')` itself, so
16
+ * importing the hook is free until the caller actually invokes it.
17
+ *
18
+ * That means consumers can wire up keyboard shortcuts, theme toggles, or
19
+ * file-state hooks at the top of their tree without paying Monaco's cost,
20
+ * and only render `<LazyEditor>` lower in the tree where it's needed.
21
+ */
22
+
23
+ import { createLazyComponent, LoadingFallback } from '../../components';
24
+ import type { EditorProps, DiffEditorProps } from './types';
25
+
26
+ // ============================================================================
27
+ // Lazy components
28
+ // ============================================================================
29
+
30
+ export const LazyEditor = createLazyComponent<EditorProps>(
31
+ () => import('./components/Editor').then((m) => ({ default: m.Editor })),
32
+ {
33
+ displayName: 'LazyEditor',
34
+ fallback: <LoadingFallback minHeight={320} text="Loading editor…" />,
35
+ },
36
+ );
37
+
38
+ export const LazyDiffEditor = createLazyComponent<DiffEditorProps>(
39
+ () => import('./components/DiffEditor').then((m) => ({ default: m.DiffEditor })),
40
+ {
41
+ displayName: 'LazyDiffEditor',
42
+ fallback: <LoadingFallback minHeight={320} text="Loading diff editor…" />,
43
+ },
44
+ );
45
+
46
+ // ============================================================================
47
+ // Light surface
48
+ // ============================================================================
49
+
50
+ // Hooks — `useMonaco` does its own dynamic import; the others are pure.
51
+ export {
52
+ useMonaco,
53
+ useEditor,
54
+ useLanguage,
55
+ useEditorTheme,
56
+ } from './hooks';
57
+
58
+ // Provider + context hook
59
+ export { EditorProvider, useEditorContext } from './context';
60
+
61
+ // All types
62
+ export type {
63
+ EditorFile,
64
+ EditorOptions,
65
+ EditorProps,
66
+ EditorContextValue,
67
+ UseEditorReturn,
68
+ UseMonacoReturn,
69
+ DiffEditorProps,
70
+ } from './types';
@@ -42,11 +42,48 @@ export const LazyMapView = createLazyComponent(
42
42
  );
43
43
 
44
44
  // ============================================================================
45
- // Re-export types for convenience
45
+ // Light primitives direct re-exports
46
+ //
47
+ // MapMarker / MapPopup / MapCluster / MapSource / MapLayer / MapControls
48
+ // etc. are thin wrappers around `react-map-gl/maplibre`. They don't import
49
+ // `maplibre-gl` at module scope (only types, which are erased), so exporting
50
+ // them synchronously here costs ~tens of KB at most — not the ~800KB of
51
+ // MapLibre GL itself.
52
+ //
53
+ // The heavy library only loads when `LazyMapContainer` actually mounts,
54
+ // because `MapContainer.tsx` (the only module that imports `maplibre-gl`
55
+ // at runtime) is reached exclusively via the dynamic import above.
56
+ //
57
+ // This means consumers can write:
58
+ //
59
+ // import { LazyMapContainer, MapMarker, MapPopup } from '@djangocfg/ui-tools/map'
60
+ //
61
+ // …and still get correct code-splitting.
46
62
  // ============================================================================
47
63
 
64
+ export {
65
+ MapMarker,
66
+ MapPopup,
67
+ MapCluster,
68
+ MapSource,
69
+ MapLayer,
70
+ MapControls,
71
+ CustomOverlay,
72
+ MapLegend,
73
+ LayerSwitcher,
74
+ } from './components';
75
+
76
+ export { MapProvider, useMapContext, MapContext } from './context';
77
+ export type { MapProviderProps } from './context';
78
+
48
79
  export type {
49
80
  MapContainerProps,
81
+ MapMarkerProps,
82
+ MapPopupProps,
83
+ MapClusterProps,
84
+ MapSourceProps,
85
+ MapLayerProps,
86
+ MapControlsProps,
50
87
  } from './components';
51
88
 
52
89
  export type {
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * `@djangocfg/ui-tools/markdown-editor` subpath entrypoint.
5
+ *
6
+ * `MarkdownEditor` is a TipTap WYSIWYG with the full ProseMirror + TipTap
7
+ * extension stack — `@tiptap/react`, `starter-kit`, `markdown`, `mention`,
8
+ * `placeholder`, plus our `createMentionSuggestion` (floating-ui anchored
9
+ * dropdown). Together that's ~200 KB minified — wrap it in React.lazy so
10
+ * pages that don't render an editor don't pay.
11
+ *
12
+ * Light surface kept here:
13
+ * - All public types (erased at compile time).
14
+ * - `mentionPresets` — pure data describing how to render mentions to
15
+ * markdown. No TipTap imports at module scope.
16
+ */
17
+
18
+ import { createLazyComponent, LoadingFallback } from '../../components';
19
+ import type { MarkdownEditorProps } from './MarkdownEditor';
20
+
21
+ export const LazyMarkdownEditor = createLazyComponent<MarkdownEditorProps>(
22
+ () => import('./MarkdownEditor').then((m) => ({ default: m.MarkdownEditor })),
23
+ {
24
+ displayName: 'LazyMarkdownEditor',
25
+ fallback: <LoadingFallback minHeight={140} text="Loading editor…" />,
26
+ },
27
+ );
28
+
29
+ // Light surface — pure helpers + types.
30
+ export { mentionPresets } from './mentionPresets';
31
+
32
+ export type {
33
+ MarkdownEditorProps,
34
+ MarkdownEditorHandle,
35
+ } from './MarkdownEditor';
36
+
37
+ export type {
38
+ MentionItem,
39
+ MentionConfig,
40
+ MentionAttrs,
41
+ MentionMarkdownRenderer,
42
+ } from './types';
@@ -278,6 +278,54 @@ const unsubscribe = useSpeechPrefs.subscribe((state) => {
278
278
 
279
279
  ---
280
280
 
281
+ ## Debug logger
282
+
283
+ Scoped, namespaced [consola](https://github.com/unjs/consola) wrapper that silences itself in production by default. Mirrors `getChatLogger()` in the Chat tool so both surfaces feel the same in DevTools.
284
+
285
+ ```ts
286
+ import { getSpeechLogger } from '@djangocfg/ui-tools';
287
+
288
+ const log = getSpeechLogger();
289
+ log.dictation.info('final merged', { len: 42 });
290
+ log.engine.debug('state', 'listening');
291
+ log.error.error('engine threw', err);
292
+ ```
293
+
294
+ Sub-loggers: `engine`, `dictation`, `slot`, `composer`, `mic`, `push`, `error`. `error` always emits; everything else is gated.
295
+
296
+ **Opt-in (any one is enough):**
297
+
298
+ 1. **Dev mode** — `NODE_ENV === 'development'` auto-enables everything.
299
+ 2. **Runtime toggle** — paste this in DevTools to enable without a rebuild:
300
+ ```js
301
+ localStorage.setItem('djangocfg:speech-debug', '1');
302
+ location.reload();
303
+ ```
304
+ `'0'` (or `removeItem`) turns it back off.
305
+ 3. **Explicit** — `getSpeechLogger(true)` from a host component (analogous to `<ChatRoot debug />`).
306
+
307
+ **What you'll see when on**, in order of a typical dictation session:
308
+
309
+ ```
310
+ [speech][slot] mount { supported: true, hasComposerHandle: true, … }
311
+ [speech][engine] subscribe { engineId: 'webspeech' }
312
+ [speech][engine] state 'listening'
313
+ [speech][engine] partial { len: 6, segmentId: 's1' }
314
+ [speech][composer] setValue → composer handle { len: 12 }
315
+ [speech][engine] final { len: 42, confidence: 0.91 }
316
+ [speech][dictation] final merged { len: 42, totalLen: 54 }
317
+ [speech][engine] autoStop silence detected
318
+ [speech][engine] state 'closed'
319
+ ```
320
+
321
+ If text never appears in your composer, look for:
322
+
323
+ - `[speech][slot] mount { hasComposerHandle: false, … }` → `<VoiceComposerSlot>` is outside a `<ChatProvider>` and no `value`/`onChange` props were given — text is going nowhere.
324
+ - `[speech][composer] warn setValue called but no composer handle is registered …` → the composer never called `useRegisterComposer(...)`. Built-in `<Composer>` and `MarkdownEditor` do this automatically; custom composers must opt in.
325
+ - `[speech][engine] final` arrives but no `[speech][dictation] final merged` follows → check `normaliseFinal` filtered the text (empty / whitespace only).
326
+
327
+ ---
328
+
281
329
  ## Public surface
282
330
 
283
331
  ### Hooks
@@ -1,5 +1,10 @@
1
1
  export { newSegmentId } from './ids';
2
- export { sttLogger } from './logger';
2
+ export {
3
+ getSpeechLogger,
4
+ sttLogger,
5
+ type SpeechLogger,
6
+ type SpeechLogScope,
7
+ } from './logger';
3
8
  export {
4
9
  EMPTY_TRANSCRIPT,
5
10
  buildTranscript,
@@ -1,3 +1,109 @@
1
- import { consola } from 'consola';
1
+ /**
2
+ * Speech recognition dev logger.
3
+ *
4
+ * Mirrors `getChatLogger()` in the Chat tool so consumers reason about both
5
+ * surfaces the same way. Silent in production by default; emits `error`
6
+ * level always.
7
+ *
8
+ * Opt-in mechanisms (any one is enough):
9
+ * 1. `NODE_ENV === 'development'` (auto, via `isDev` from `@djangocfg/ui-core`).
10
+ * 2. `localStorage.setItem('djangocfg:speech-debug', '1')` — runtime
11
+ * toggle without a rebuild. Useful for diagnosing a prod bug.
12
+ * 3. Explicit `getSpeechLogger(true)` from a host component.
13
+ *
14
+ * Sub-loggers:
15
+ * engine — engine lifecycle (start/stop/abort/result)
16
+ * dictation — `useDictation` state (anchor, partial → final merge)
17
+ * slot — `VoiceComposerSlot` UI (mount, support gating, button)
18
+ * composer — composer handle registry (focus / setValue / pin caret)
19
+ * mic — `useMicLevel` / `useMicDevices` / device picker
20
+ * push — push-to-talk hotkey events
21
+ * error — caught errors (always emitted, even when disabled)
22
+ */
23
+ import { consola, type ConsolaInstance } from 'consola';
2
24
 
25
+ import { isBrowser, isDev } from '@djangocfg/ui-core/lib';
26
+
27
+ export type SpeechLogScope =
28
+ | 'engine'
29
+ | 'dictation'
30
+ | 'slot'
31
+ | 'composer'
32
+ | 'mic'
33
+ | 'push'
34
+ | 'error';
35
+
36
+ export interface SpeechLogger {
37
+ engine: ConsolaInstance;
38
+ dictation: ConsolaInstance;
39
+ slot: ConsolaInstance;
40
+ composer: ConsolaInstance;
41
+ mic: ConsolaInstance;
42
+ push: ConsolaInstance;
43
+ error: ConsolaInstance;
44
+ /** True when this logger is actually emitting. */
45
+ enabled: boolean;
46
+ }
47
+
48
+ const SCOPES: SpeechLogScope[] = [
49
+ 'engine',
50
+ 'dictation',
51
+ 'slot',
52
+ 'composer',
53
+ 'mic',
54
+ 'push',
55
+ 'error',
56
+ ];
57
+
58
+ const STORAGE_KEY = 'djangocfg:speech-debug';
59
+
60
+ function readStorageFlag(): boolean {
61
+ if (!isBrowser) return false;
62
+ try {
63
+ return window.localStorage.getItem(STORAGE_KEY) === '1';
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ const cache = new Map<boolean, SpeechLogger>();
70
+
71
+ function buildLogger(enabled: boolean): SpeechLogger {
72
+ const root = consola.withTag('speech');
73
+ const subs = Object.fromEntries(
74
+ SCOPES.map((scope) => [scope, root.withTag(scope)]),
75
+ ) as Record<SpeechLogScope, ConsolaInstance>;
76
+
77
+ if (!enabled) {
78
+ for (const scope of SCOPES) {
79
+ if (scope === 'error') continue;
80
+ subs[scope].level = -999;
81
+ }
82
+ }
83
+
84
+ return { ...subs, enabled };
85
+ }
86
+
87
+ /**
88
+ * Get the speech logger.
89
+ *
90
+ * @param debug Explicit override. `undefined` falls back to `isDev` OR
91
+ * `localStorage['djangocfg:speech-debug'] === '1'`.
92
+ */
93
+ export function getSpeechLogger(debug?: boolean): SpeechLogger {
94
+ const enabled = debug ?? (isDev || readStorageFlag());
95
+ let logger = cache.get(enabled);
96
+ if (!logger) {
97
+ logger = buildLogger(enabled);
98
+ cache.set(enabled, logger);
99
+ }
100
+ return logger;
101
+ }
102
+
103
+ /**
104
+ * Legacy flat-tag logger. New code should prefer `getSpeechLogger()` for
105
+ * scoped output and the runtime opt-in switch.
106
+ *
107
+ * @deprecated Use `getSpeechLogger()`.
108
+ */
3
109
  export const sttLogger = consola.withTag('ui-tools:speech');
@@ -6,9 +6,11 @@ import {
6
6
  EMPTY_TRANSCRIPT,
7
7
  INITIAL_STATE,
8
8
  buildTranscript,
9
+ getSpeechLogger,
9
10
  reducer,
10
- sttLogger,
11
11
  } from '../core';
12
+
13
+ const log = getSpeechLogger();
12
14
  import { createWebSpeechEngine } from '../core/engine/webspeech';
13
15
  import { useSpeechPrefs } from '../store/prefsStore';
14
16
  import type {
@@ -46,8 +48,10 @@ export function useSpeechRecognition(
46
48
 
47
49
  // Engine subscription lifecycle.
48
50
  useEffect(() => {
51
+ log.engine.debug('subscribe', { engineId: engine.id });
49
52
  const offs = [
50
53
  engine.on('partial', (text, segmentId) => {
54
+ log.engine.debug('partial', { len: text.length, segmentId });
51
55
  dispatch({ type: 'PARTIAL', text, segmentId });
52
56
  const seg: Segment = {
53
57
  id: segmentId,
@@ -58,6 +62,11 @@ export function useSpeechRecognition(
58
62
  cbRef.current.onPartial?.(text, seg);
59
63
  }),
60
64
  engine.on('final', (text, segmentId, confidence) => {
65
+ log.engine.info('final', {
66
+ len: text.length,
67
+ segmentId,
68
+ confidence,
69
+ });
61
70
  dispatch({ type: 'FINAL', text, segmentId, confidence });
62
71
  const seg: Segment = {
63
72
  id: segmentId,
@@ -70,10 +79,12 @@ export function useSpeechRecognition(
70
79
  cbRef.current.onFinal?.(text, seg);
71
80
  }),
72
81
  engine.on('error', (err) => {
82
+ log.error.error('engine error', err);
73
83
  dispatch({ type: 'ERROR', error: err });
74
84
  cbRef.current.onError?.(err);
75
85
  }),
76
86
  engine.on('state', (s) => {
87
+ log.engine.debug('state', s);
77
88
  if (s === 'listening') {
78
89
  dispatch({ type: 'STARTED' });
79
90
  cbRef.current.onStart?.();
@@ -98,7 +109,7 @@ export function useSpeechRecognition(
98
109
  const { silenceMs, maxMs, silenceThreshold = 0.02 } = config.autoStop ?? {};
99
110
  if (maxMs) {
100
111
  maxTimer.current = window.setTimeout(() => {
101
- sttLogger.debug('[autoStop] max duration hit');
112
+ log.engine.debug('autoStop max duration hit');
102
113
  void engine.stop();
103
114
  }, maxMs);
104
115
  }
@@ -107,7 +118,7 @@ export function useSpeechRecognition(
107
118
  if (level < silenceThreshold) {
108
119
  if (silenceTimer.current == null) {
109
120
  silenceTimer.current = window.setTimeout(() => {
110
- sttLogger.debug('[autoStop] silence detected');
121
+ log.engine.debug('autoStop silence detected');
111
122
  void engine.stop();
112
123
  }, silenceMs);
113
124
  }
@@ -141,7 +152,7 @@ export function useSpeechRecognition(
141
152
  });
142
153
  } catch (cause) {
143
154
  // engine already emitted 'error'; reducer caught it via subscription
144
- sttLogger.debug('[start] engine threw', cause);
155
+ log.engine.warn('start threw', cause);
145
156
  }
146
157
  }, [engine, language, config.interim, config.deviceId, prefs.deviceId, state.status]);
147
158
 
@@ -60,6 +60,15 @@ export * from './hooks';
60
60
  export * from './components';
61
61
  export * from './widgets';
62
62
  export * from './context';
63
+ // Dev logger — namespaced, runtime opt-in via localStorage.
64
+ // Enable from devtools: `localStorage['djangocfg:speech-debug'] = '1'`.
65
+ export {
66
+ getSpeechLogger,
67
+ sttLogger,
68
+ type SpeechLogger,
69
+ type SpeechLogScope,
70
+ } from './core/logger';
71
+
63
72
  export { LazyDictationField } from './lazy';
64
73
  export { useSpeechPrefs } from './store';
65
74
  export type { SpeechPrefs } from './store';
@@ -10,10 +10,13 @@ import { cn } from '@djangocfg/ui-core/lib';
10
10
  import { useChatContextOptional } from '../../Chat/context';
11
11
  import { useSpeechRecognition } from '../hooks/useSpeechRecognition';
12
12
  import { useVoiceSupport } from '../hooks/useVoiceSupport';
13
+ import { getSpeechLogger } from '../core/logger';
13
14
  import { normaliseFinal } from '../core/transcript';
14
15
  import { DEFAULT_VOICE_SOUNDS, type VoiceSoundEvent } from '../core/audio/defaults';
15
16
  import type { RecognitionEngine } from '../types';
16
17
 
18
+ const log = getSpeechLogger();
19
+
17
20
  export interface VoiceComposerSlotProps {
18
21
  /**
19
22
  * Controlled composer value. Optional — when omitted, the slot
@@ -98,6 +101,16 @@ export function VoiceComposerSlot({
98
101
  const composerHandleRef = useRef(chatCtx?.composer ?? null);
99
102
  composerHandleRef.current = chatCtx?.composer ?? null;
100
103
 
104
+ useEffect(() => {
105
+ log.slot.debug('mount', {
106
+ supported: support.supported,
107
+ reason: support.reason,
108
+ hasComposerHandle: !!chatCtx?.composer,
109
+ hasExplicitValue: value !== undefined,
110
+ hasOnChange: !!onChange,
111
+ });
112
+ }, [support.supported, support.reason, chatCtx?.composer, value, onChange]);
113
+
101
114
  // Resolve value/onChange: prop wins; otherwise pull from the
102
115
  // registered composer handle. The slot can therefore be dropped into
103
116
  // `composerToolbarEnd` of `ChatRoot` with zero props.
@@ -108,10 +121,21 @@ export function VoiceComposerSlot({
108
121
  const resolvedSetValue = useCallback(
109
122
  (next: string): void => {
110
123
  if (onChange) {
124
+ log.composer.debug('setValue → onChange prop', { len: next.length });
111
125
  onChange(next);
112
126
  return;
113
127
  }
114
- composerHandleRef.current?.setValue?.(next);
128
+ const handle = composerHandleRef.current;
129
+ if (!handle?.setValue) {
130
+ log.composer.warn(
131
+ 'setValue called but no composer handle is registered — text will be lost. ' +
132
+ 'Make sure <VoiceComposerSlot> lives inside a <ChatProvider> (or pass `value`/`onChange` props for standalone use).',
133
+ { len: next.length },
134
+ );
135
+ return;
136
+ }
137
+ log.composer.debug('setValue → composer handle', { len: next.length });
138
+ handle.setValue(next);
115
139
  },
116
140
  [onChange],
117
141
  );
@@ -157,6 +181,10 @@ export function VoiceComposerSlot({
157
181
 
158
182
  const handlePartial = useCallback(
159
183
  (text: string) => {
184
+ log.dictation.debug('partial', {
185
+ len: text.length,
186
+ anchorLen: anchorRef.current.length,
187
+ });
160
188
  const next = anchorRef.current
161
189
  ? `${anchorRef.current} ${text}`
162
190
  : text;
@@ -169,8 +197,15 @@ export function VoiceComposerSlot({
169
197
  const handleFinal = useCallback(
170
198
  (text: string) => {
171
199
  const clean = normaliseFinal(text);
172
- if (!clean) return;
200
+ if (!clean) {
201
+ log.dictation.debug('final dropped — empty after normalise', { raw: text });
202
+ return;
203
+ }
173
204
  const merged = anchorRef.current ? `${anchorRef.current} ${clean}` : clean;
205
+ log.dictation.info('final merged', {
206
+ len: clean.length,
207
+ totalLen: merged.length,
208
+ });
174
209
  anchorRef.current = merged;
175
210
  resolvedSetValue(merged);
176
211
  pinCaretToEnd();
@@ -1,5 +0,0 @@
1
- export { ChatRoot } from './chunk-PEKBT75W.mjs';
2
- import './chunk-HIK6BPL7.mjs';
3
- import './chunk-N2XQF2OL.mjs';
4
- //# sourceMappingURL=ChatRoot-JVR3M3H2.mjs.map
5
- //# sourceMappingURL=ChatRoot-JVR3M3H2.mjs.map
@@ -1,14 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkHPK3EWBF_cjs = require('./chunk-HPK3EWBF.cjs');
4
- require('./chunk-FIRK5CEH.cjs');
5
- require('./chunk-OLISEQHS.cjs');
6
-
7
-
8
-
9
- Object.defineProperty(exports, "ChatRoot", {
10
- enumerable: true,
11
- get: function () { return chunkHPK3EWBF_cjs.ChatRoot; }
12
- });
13
- //# sourceMappingURL=ChatRoot-LXIUBOXF.cjs.map
14
- //# sourceMappingURL=ChatRoot-LXIUBOXF.cjs.map
@@ -1,4 +0,0 @@
1
- export { DictationField } from './chunk-C2YN6WEO.mjs';
2
- import './chunk-N2XQF2OL.mjs';
3
- //# sourceMappingURL=DictationField-U25MEYAL.mjs.map
4
- //# sourceMappingURL=DictationField-U25MEYAL.mjs.map
@@ -1,13 +0,0 @@
1
- 'use strict';
2
-
3
- var chunk4PFW7MIJ_cjs = require('./chunk-4PFW7MIJ.cjs');
4
- require('./chunk-OLISEQHS.cjs');
5
-
6
-
7
-
8
- Object.defineProperty(exports, "DictationField", {
9
- enumerable: true,
10
- get: function () { return chunk4PFW7MIJ_cjs.DictationField; }
11
- });
12
- //# sourceMappingURL=DictationField-XWR5VOID.cjs.map
13
- //# sourceMappingURL=DictationField-XWR5VOID.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tools/Map/context/MapContext.tsx","../src/tools/Map/styles/index.ts","../src/tools/Map/components/MapContainer.tsx"],"names":["createContext","useRef","useState","useCallback","useMemo","jsx","__name","useContext","useEffect","jsxs","Fragment","Map","cn","RotateCcw","ExternalLink"],"mappings":"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","file":"MapContainer-76YL2JXL.cjs","sourcesContent":["'use client'\n\nimport {\n createContext,\n useContext,\n useState,\n useRef,\n useMemo,\n useCallback,\n type ReactNode,\n} from 'react'\nimport type { MapRef } from 'react-map-gl/maplibre'\nimport type { MapContextValue, MapViewport, MarkerData } from '../types'\n\nconst MapContext = createContext<MapContextValue | null>(null)\n\nexport interface MapProviderProps {\n children: ReactNode\n initialViewport?: Partial<MapViewport>\n}\n\nconst DEFAULT_VIEWPORT: MapViewport = {\n longitude: 115.1889,\n latitude: -8.4095,\n zoom: 10,\n bearing: 0,\n pitch: 0,\n}\n\nexport function MapProvider({ children, initialViewport }: MapProviderProps) {\n const mapRef = useRef<MapRef | null>(null)\n const initialViewportRef = useRef<MapViewport>({\n ...DEFAULT_VIEWPORT,\n ...initialViewport,\n })\n const [viewport, setViewportState] = useState<MapViewport>(initialViewportRef.current)\n const [markers, setMarkers] = useState<MarkerData[]>([])\n const [selectedMarker, setSelectedMarker] = useState<MarkerData | null>(null)\n const [hoveredFeature, setHoveredFeature] = useState<GeoJSON.Feature | null>(null)\n const [isLoaded, setIsLoaded] = useState(false)\n\n const setViewport = useCallback((newViewport: Partial<MapViewport>) => {\n setViewportState((prev) => ({ ...prev, ...newViewport }))\n }, [])\n\n const resetToInitial = useCallback(() => {\n const map = mapRef.current\n if (map) {\n map.flyTo({\n center: [initialViewportRef.current.longitude, initialViewportRef.current.latitude],\n zoom: initialViewportRef.current.zoom,\n bearing: initialViewportRef.current.bearing ?? 0,\n pitch: initialViewportRef.current.pitch ?? 0,\n duration: 1000,\n })\n }\n }, [])\n\n const value = useMemo<MapContextValue>(\n () => ({\n mapRef,\n viewport,\n setViewport,\n initialViewport: initialViewportRef.current,\n resetToInitial,\n markers,\n setMarkers,\n selectedMarker,\n setSelectedMarker,\n hoveredFeature,\n setHoveredFeature,\n isLoaded,\n setIsLoaded,\n }),\n [viewport, setViewport, resetToInitial, markers, selectedMarker, hoveredFeature, isLoaded]\n )\n\n return <MapContext.Provider value={value}>{children}</MapContext.Provider>\n}\n\nexport function useMapContext(): MapContextValue {\n const context = useContext(MapContext)\n if (!context) {\n throw new Error('useMapContext must be used within a MapProvider')\n }\n return context\n}\n\nexport { MapContext }\n","export const MAP_STYLES = {\n light: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',\n dark: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',\n streets: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',\n satellite: 'https://api.maptiler.com/maps/satellite/style.json',\n} as const\n\nexport type MapStyleKey = keyof typeof MAP_STYLES\n\nexport function getMapStyle(key: MapStyleKey | string): string {\n if (key in MAP_STYLES) {\n return MAP_STYLES[key as MapStyleKey]\n }\n return key\n}\n","'use client'\n\nimport { useCallback, useEffect, useRef, type ReactNode } from 'react'\nimport Map, { type ViewStateChangeEvent } from 'react-map-gl/maplibre'\nimport { ExternalLink, RotateCcw } from 'lucide-react'\nimport { cn } from '@djangocfg/ui-core/lib'\nimport 'maplibre-gl/dist/maplibre-gl.css'\n\nimport { MapProvider, useMapContext } from '../context'\nimport { MAP_STYLES } from '../styles'\nimport type { MapViewport, MapStyleKey } from '../types'\n\nexport interface MapContainerProps {\n children?: ReactNode\n initialViewport?: Partial<MapViewport>\n mapStyle?: MapStyleKey | string\n interactiveLayerIds?: string[]\n className?: string\n style?: React.CSSProperties\n cursor?: string\n attributionControl?: boolean\n reuseMaps?: boolean\n /** URL to open in external maps app (shows \"Open in Maps\" button if provided) */\n openInMapsUrl?: string\n /** Label for the open in maps button */\n openInMapsLabel?: string\n /** Auto-reset to initial viewport after N ms of inactivity (0 = disabled) */\n autoResetDelay?: number\n /** Show reset button */\n showResetButton?: boolean\n}\n\ninterface MapInnerProps extends Omit<MapContainerProps, 'initialViewport'> {}\n\nfunction MapInner({\n children,\n mapStyle = 'light',\n interactiveLayerIds,\n style,\n cursor,\n attributionControl = true,\n reuseMaps = true,\n openInMapsUrl,\n openInMapsLabel = 'Open in Maps',\n autoResetDelay = 0,\n showResetButton = false,\n}: MapInnerProps) {\n const { mapRef, viewport, setViewport, setIsLoaded, resetToInitial, initialViewport } = useMapContext()\n const resetTimerRef = useRef<NodeJS.Timeout | null>(null)\n const isInteractingRef = useRef(false)\n\n // Check if viewport has changed from initial\n const hasViewportChanged =\n Math.abs(viewport.longitude - initialViewport.longitude) > 0.0001 ||\n Math.abs(viewport.latitude - initialViewport.latitude) > 0.0001 ||\n Math.abs(viewport.zoom - initialViewport.zoom) > 0.1\n\n const handleMove = useCallback(\n (evt: ViewStateChangeEvent) => {\n setViewport({\n longitude: evt.viewState.longitude,\n latitude: evt.viewState.latitude,\n zoom: evt.viewState.zoom,\n bearing: evt.viewState.bearing,\n pitch: evt.viewState.pitch,\n })\n },\n [setViewport]\n )\n\n const handleMoveStart = useCallback(() => {\n isInteractingRef.current = true\n // Clear any pending reset timer\n if (resetTimerRef.current) {\n clearTimeout(resetTimerRef.current)\n resetTimerRef.current = null\n }\n }, [])\n\n const handleMoveEnd = useCallback(() => {\n isInteractingRef.current = false\n // Start auto-reset timer if enabled\n if (autoResetDelay > 0) {\n resetTimerRef.current = setTimeout(() => {\n if (!isInteractingRef.current) {\n resetToInitial()\n }\n }, autoResetDelay)\n }\n }, [autoResetDelay, resetToInitial])\n\n const handleLoad = useCallback(() => {\n setIsLoaded(true)\n }, [setIsLoaded])\n\n // Cleanup timer on unmount\n useEffect(() => {\n return () => {\n if (resetTimerRef.current) {\n clearTimeout(resetTimerRef.current)\n }\n }\n }, [])\n\n const resolvedStyle = mapStyle in MAP_STYLES\n ? MAP_STYLES[mapStyle as MapStyleKey]\n : mapStyle\n\n return (\n <>\n <Map\n ref={mapRef}\n {...viewport}\n onMove={handleMove}\n onMoveStart={handleMoveStart}\n onMoveEnd={handleMoveEnd}\n onLoad={handleLoad}\n mapStyle={resolvedStyle}\n interactiveLayerIds={interactiveLayerIds}\n attributionControl={attributionControl ? {} : false}\n reuseMaps={reuseMaps}\n style={{\n width: '100%',\n height: '100%',\n ...style,\n }}\n cursor={cursor}\n >\n {children}\n </Map>\n\n {/* Map overlay buttons */}\n <div className=\"absolute bottom-3 right-3 flex items-center gap-2\">\n {/* Reset button */}\n {showResetButton && hasViewportChanged && (\n <button\n type=\"button\"\n onClick={resetToInitial}\n className={cn(\n 'inline-flex items-center gap-2 px-3 py-2 rounded-lg',\n 'bg-background/95 backdrop-blur-sm text-foreground text-sm font-medium',\n 'hover:bg-background transition-colors shadow-sm border border-border'\n )}\n >\n <RotateCcw className=\"w-4 h-4\" />\n Reset\n </button>\n )}\n\n {/* Open in Maps button */}\n {openInMapsUrl && (\n <a\n href={openInMapsUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={cn(\n 'inline-flex items-center gap-2 px-4 py-2 rounded-lg',\n 'bg-background/95 backdrop-blur-sm text-foreground text-sm font-medium',\n 'hover:bg-background transition-colors shadow-sm border border-border'\n )}\n >\n <ExternalLink className=\"w-4 h-4\" />\n {openInMapsLabel}\n </a>\n )}\n </div>\n </>\n )\n}\n\nexport function MapContainer({\n children,\n initialViewport,\n className,\n ...props\n}: MapContainerProps) {\n return (\n <div className={className} style={{ width: '100%', height: '100%', position: 'relative' }}>\n <MapProvider initialViewport={initialViewport}>\n <MapInner {...props}>{children}</MapInner>\n </MapProvider>\n </div>\n )\n}\n\n/**\n * Use this when you need the map inside an existing MapProvider\n */\nexport function MapView(props: Omit<MapContainerProps, 'initialViewport'>) {\n return <MapInner {...props} />\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tools/Map/context/MapContext.tsx","../src/tools/Map/styles/index.ts","../src/tools/Map/components/MapContainer.tsx"],"names":["useRef","useCallback","jsx"],"mappings":";;;;;;;;AAcA,IAAM,UAAA,GAAa,cAAsC,IAAI,CAAA;AAO7D,IAAM,gBAAA,GAAgC;AAAA,EACpC,SAAA,EAAW,QAAA;AAAA,EACX,QAAA,EAAU,OAAA;AAAA,EACV,IAAA,EAAM,EAAA;AAAA,EACN,OAAA,EAAS,CAAA;AAAA,EACT,KAAA,EAAO;AACT,CAAA;AAEO,SAAS,WAAA,CAAY,EAAE,QAAA,EAAU,eAAA,EAAgB,EAAqB;AAC3E,EAAA,MAAM,MAAA,GAAS,OAAsB,IAAI,CAAA;AACzC,EAAA,MAAM,qBAAqB,MAAA,CAAoB;AAAA,IAC7C,GAAG,gBAAA;AAAA,IACH,GAAG;AAAA,GACJ,CAAA;AACD,EAAA,MAAM,CAAC,QAAA,EAAU,gBAAgB,CAAA,GAAI,QAAA,CAAsB,mBAAmB,OAAO,CAAA;AACrF,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,QAAA,CAAuB,EAAE,CAAA;AACvD,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,SAA4B,IAAI,CAAA;AAC5E,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,SAAiC,IAAI,CAAA;AACjF,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,KAAK,CAAA;AAE9C,EAAA,MAAM,WAAA,GAAc,WAAA,CAAY,CAAC,WAAA,KAAsC;AACrE,IAAA,gBAAA,CAAiB,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,GAAG,aAAY,CAAE,CAAA;AAAA,EAC1D,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,cAAA,GAAiB,YAAY,MAAM;AACvC,IAAA,MAAM,MAAM,MAAA,CAAO,OAAA;AACnB,IAAA,IAAI,GAAA,EAAK;AACP,MAAA,GAAA,CAAI,KAAA,CAAM;AAAA,QACR,QAAQ,CAAC,kBAAA,CAAmB,QAAQ,SAAA,EAAW,kBAAA,CAAmB,QAAQ,QAAQ,CAAA;AAAA,QAClF,IAAA,EAAM,mBAAmB,OAAA,CAAQ,IAAA;AAAA,QACjC,OAAA,EAAS,kBAAA,CAAmB,OAAA,CAAQ,OAAA,IAAW,CAAA;AAAA,QAC/C,KAAA,EAAO,kBAAA,CAAmB,OAAA,CAAQ,KAAA,IAAS,CAAA;AAAA,QAC3C,QAAA,EAAU;AAAA,OACX,CAAA;AAAA,IACH;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACZ,OAAO;AAAA,MACL,MAAA;AAAA,MACA,QAAA;AAAA,MACA,WAAA;AAAA,MACA,iBAAiB,kBAAA,CAAmB,OAAA;AAAA,MACpC,cAAA;AAAA,MACA,OAAA;AAAA,MACA,UAAA;AAAA,MACA,cAAA;AAAA,MACA,iBAAA;AAAA,MACA,cAAA;AAAA,MACA,iBAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACF,CAAA;AAAA,IACA,CAAC,QAAA,EAAU,WAAA,EAAa,gBAAgB,OAAA,EAAS,cAAA,EAAgB,gBAAgB,QAAQ;AAAA,GAC3F;AAEA,EAAA,uBAAO,GAAA,CAAC,UAAA,CAAW,QAAA,EAAX,EAAoB,OAAe,QAAA,EAAS,CAAA;AACtD;AAjDgB,MAAA,CAAA,WAAA,EAAA,aAAA,CAAA;AAmDT,SAAS,aAAA,GAAiC;AAC/C,EAAA,MAAM,OAAA,GAAU,WAAW,UAAU,CAAA;AACrC,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,MAAM,iDAAiD,CAAA;AAAA,EACnE;AACA,EAAA,OAAO,OAAA;AACT;AANgB,MAAA,CAAA,aAAA,EAAA,eAAA,CAAA;;;AChFT,IAAM,UAAA,GAAa;AAAA,EACxB,KAAA,EAAO,+DAAA;AAAA,EACP,IAAA,EAAM,kEAAA;AAAA,EACN,OAAA,EAAS,8DAAA;AAAA,EACT,SAAA,EAAW;AACb,CAAA;AC6BA,SAAS,QAAA,CAAS;AAAA,EAChB,QAAA;AAAA,EACA,QAAA,GAAW,OAAA;AAAA,EACX,mBAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,kBAAA,GAAqB,IAAA;AAAA,EACrB,SAAA,GAAY,IAAA;AAAA,EACZ,aAAA;AAAA,EACA,eAAA,GAAkB,cAAA;AAAA,EAClB,cAAA,GAAiB,CAAA;AAAA,EACjB,eAAA,GAAkB;AACpB,CAAA,EAAkB;AAChB,EAAA,MAAM,EAAE,QAAQ,QAAA,EAAU,WAAA,EAAa,aAAa,cAAA,EAAgB,eAAA,KAAoB,aAAA,EAAc;AACtG,EAAA,MAAM,aAAA,GAAgBA,OAA8B,IAAI,CAAA;AACxD,EAAA,MAAM,gBAAA,GAAmBA,OAAO,KAAK,CAAA;AAGrC,EAAA,MAAM,kBAAA,GACJ,KAAK,GAAA,CAAI,QAAA,CAAS,YAAY,eAAA,CAAgB,SAAS,CAAA,GAAI,IAAA,IAC3D,IAAA,CAAK,GAAA,CAAI,SAAS,QAAA,GAAW,eAAA,CAAgB,QAAQ,CAAA,GAAI,IAAA,IACzD,IAAA,CAAK,IAAI,QAAA,CAAS,IAAA,GAAO,eAAA,CAAgB,IAAI,CAAA,GAAI,GAAA;AAEnD,EAAA,MAAM,UAAA,GAAaC,WAAAA;AAAA,IACjB,CAAC,GAAA,KAA8B;AAC7B,MAAA,WAAA,CAAY;AAAA,QACV,SAAA,EAAW,IAAI,SAAA,CAAU,SAAA;AAAA,QACzB,QAAA,EAAU,IAAI,SAAA,CAAU,QAAA;AAAA,QACxB,IAAA,EAAM,IAAI,SAAA,CAAU,IAAA;AAAA,QACpB,OAAA,EAAS,IAAI,SAAA,CAAU,OAAA;AAAA,QACvB,KAAA,EAAO,IAAI,SAAA,CAAU;AAAA,OACtB,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,WAAW;AAAA,GACd;AAEA,EAAA,MAAM,eAAA,GAAkBA,YAAY,MAAM;AACxC,IAAA,gBAAA,CAAiB,OAAA,GAAU,IAAA;AAE3B,IAAA,IAAI,cAAc,OAAA,EAAS;AACzB,MAAA,YAAA,CAAa,cAAc,OAAO,CAAA;AAClC,MAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AAAA,IAC1B;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,aAAA,GAAgBA,YAAY,MAAM;AACtC,IAAA,gBAAA,CAAiB,OAAA,GAAU,KAAA;AAE3B,IAAA,IAAI,iBAAiB,CAAA,EAAG;AACtB,MAAA,aAAA,CAAc,OAAA,GAAU,WAAW,MAAM;AACvC,QAAA,IAAI,CAAC,iBAAiB,OAAA,EAAS;AAC7B,UAAA,cAAA,EAAe;AAAA,QACjB;AAAA,MACF,GAAG,cAAc,CAAA;AAAA,IACnB;AAAA,EACF,CAAA,EAAG,CAAC,cAAA,EAAgB,cAAc,CAAC,CAAA;AAEnC,EAAA,MAAM,UAAA,GAAaA,YAAY,MAAM;AACnC,IAAA,WAAA,CAAY,IAAI,CAAA;AAAA,EAClB,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAGhB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,cAAc,OAAA,EAAS;AACzB,QAAA,YAAA,CAAa,cAAc,OAAO,CAAA;AAAA,MACpC;AAAA,IACF,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,aAAA,GAAgB,QAAA,IAAY,UAAA,GAC9B,UAAA,CAAW,QAAuB,CAAA,GAClC,QAAA;AAEJ,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAAC,GAAAA;AAAA,MAAC,GAAA;AAAA,MAAA;AAAA,QACC,GAAA,EAAK,MAAA;AAAA,QACJ,GAAG,QAAA;AAAA,QACJ,MAAA,EAAQ,UAAA;AAAA,QACR,WAAA,EAAa,eAAA;AAAA,QACb,SAAA,EAAW,aAAA;AAAA,QACX,MAAA,EAAQ,UAAA;AAAA,QACR,QAAA,EAAU,aAAA;AAAA,QACV,mBAAA;AAAA,QACA,kBAAA,EAAoB,kBAAA,GAAqB,EAAC,GAAI,KAAA;AAAA,QAC9C,SAAA;AAAA,QACA,KAAA,EAAO;AAAA,UACL,KAAA,EAAO,MAAA;AAAA,UACP,MAAA,EAAQ,MAAA;AAAA,UACR,GAAG;AAAA,SACL;AAAA,QACA,MAAA;AAAA,QAEC;AAAA;AAAA,KACH;AAAA,oBAGA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mDAAA,EAEZ,QAAA,EAAA;AAAA,MAAA,eAAA,IAAmB,kBAAA,oBAClB,IAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAK,QAAA;AAAA,UACL,OAAA,EAAS,cAAA;AAAA,UACT,SAAA,EAAW,EAAA;AAAA,YACT,qDAAA;AAAA,YACA,uEAAA;AAAA,YACA;AAAA,WACF;AAAA,UAEA,QAAA,EAAA;AAAA,4BAAAA,GAAAA,CAAC,SAAA,EAAA,EAAU,SAAA,EAAU,SAAA,EAAU,CAAA;AAAA,YAAE;AAAA;AAAA;AAAA,OAEnC;AAAA,MAID,aAAA,oBACC,IAAA;AAAA,QAAC,GAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAM,aAAA;AAAA,UACN,MAAA,EAAO,QAAA;AAAA,UACP,GAAA,EAAI,qBAAA;AAAA,UACJ,SAAA,EAAW,EAAA;AAAA,YACT,qDAAA;AAAA,YACA,uEAAA;AAAA,YACA;AAAA,WACF;AAAA,UAEA,QAAA,EAAA;AAAA,4BAAAA,GAAAA,CAAC,YAAA,EAAA,EAAa,SAAA,EAAU,SAAA,EAAU,CAAA;AAAA,YACjC;AAAA;AAAA;AAAA;AACH,KAAA,EAEJ;AAAA,GAAA,EACF,CAAA;AAEJ;AAtIS,MAAA,CAAA,QAAA,EAAA,UAAA,CAAA;AAwIF,SAAS,YAAA,CAAa;AAAA,EAC3B,QAAA;AAAA,EACA,eAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAG;AACL,CAAA,EAAsB;AACpB,EAAA,uBACEA,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAsB,KAAA,EAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,MAAA,EAAQ,MAAA,EAAQ,QAAA,EAAU,UAAA,IAC3E,QAAA,kBAAAA,GAAAA,CAAC,WAAA,EAAA,EAAY,eAAA,EACX,QAAA,kBAAAA,GAAAA,CAAC,YAAU,GAAG,KAAA,EAAQ,QAAA,EAAS,CAAA,EACjC,CAAA,EACF,CAAA;AAEJ;AAbgB,MAAA,CAAA,YAAA,EAAA,cAAA,CAAA;AAkBT,SAAS,QAAQ,KAAA,EAAmD;AACzE,EAAA,uBAAOA,GAAAA,CAAC,QAAA,EAAA,EAAU,GAAG,KAAA,EAAO,CAAA;AAC9B;AAFgB,MAAA,CAAA,OAAA,EAAA,SAAA,CAAA","file":"MapContainer-7HXBI3OH.mjs","sourcesContent":["'use client'\n\nimport {\n createContext,\n useContext,\n useState,\n useRef,\n useMemo,\n useCallback,\n type ReactNode,\n} from 'react'\nimport type { MapRef } from 'react-map-gl/maplibre'\nimport type { MapContextValue, MapViewport, MarkerData } from '../types'\n\nconst MapContext = createContext<MapContextValue | null>(null)\n\nexport interface MapProviderProps {\n children: ReactNode\n initialViewport?: Partial<MapViewport>\n}\n\nconst DEFAULT_VIEWPORT: MapViewport = {\n longitude: 115.1889,\n latitude: -8.4095,\n zoom: 10,\n bearing: 0,\n pitch: 0,\n}\n\nexport function MapProvider({ children, initialViewport }: MapProviderProps) {\n const mapRef = useRef<MapRef | null>(null)\n const initialViewportRef = useRef<MapViewport>({\n ...DEFAULT_VIEWPORT,\n ...initialViewport,\n })\n const [viewport, setViewportState] = useState<MapViewport>(initialViewportRef.current)\n const [markers, setMarkers] = useState<MarkerData[]>([])\n const [selectedMarker, setSelectedMarker] = useState<MarkerData | null>(null)\n const [hoveredFeature, setHoveredFeature] = useState<GeoJSON.Feature | null>(null)\n const [isLoaded, setIsLoaded] = useState(false)\n\n const setViewport = useCallback((newViewport: Partial<MapViewport>) => {\n setViewportState((prev) => ({ ...prev, ...newViewport }))\n }, [])\n\n const resetToInitial = useCallback(() => {\n const map = mapRef.current\n if (map) {\n map.flyTo({\n center: [initialViewportRef.current.longitude, initialViewportRef.current.latitude],\n zoom: initialViewportRef.current.zoom,\n bearing: initialViewportRef.current.bearing ?? 0,\n pitch: initialViewportRef.current.pitch ?? 0,\n duration: 1000,\n })\n }\n }, [])\n\n const value = useMemo<MapContextValue>(\n () => ({\n mapRef,\n viewport,\n setViewport,\n initialViewport: initialViewportRef.current,\n resetToInitial,\n markers,\n setMarkers,\n selectedMarker,\n setSelectedMarker,\n hoveredFeature,\n setHoveredFeature,\n isLoaded,\n setIsLoaded,\n }),\n [viewport, setViewport, resetToInitial, markers, selectedMarker, hoveredFeature, isLoaded]\n )\n\n return <MapContext.Provider value={value}>{children}</MapContext.Provider>\n}\n\nexport function useMapContext(): MapContextValue {\n const context = useContext(MapContext)\n if (!context) {\n throw new Error('useMapContext must be used within a MapProvider')\n }\n return context\n}\n\nexport { MapContext }\n","export const MAP_STYLES = {\n light: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',\n dark: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',\n streets: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',\n satellite: 'https://api.maptiler.com/maps/satellite/style.json',\n} as const\n\nexport type MapStyleKey = keyof typeof MAP_STYLES\n\nexport function getMapStyle(key: MapStyleKey | string): string {\n if (key in MAP_STYLES) {\n return MAP_STYLES[key as MapStyleKey]\n }\n return key\n}\n","'use client'\n\nimport { useCallback, useEffect, useRef, type ReactNode } from 'react'\nimport Map, { type ViewStateChangeEvent } from 'react-map-gl/maplibre'\nimport { ExternalLink, RotateCcw } from 'lucide-react'\nimport { cn } from '@djangocfg/ui-core/lib'\nimport 'maplibre-gl/dist/maplibre-gl.css'\n\nimport { MapProvider, useMapContext } from '../context'\nimport { MAP_STYLES } from '../styles'\nimport type { MapViewport, MapStyleKey } from '../types'\n\nexport interface MapContainerProps {\n children?: ReactNode\n initialViewport?: Partial<MapViewport>\n mapStyle?: MapStyleKey | string\n interactiveLayerIds?: string[]\n className?: string\n style?: React.CSSProperties\n cursor?: string\n attributionControl?: boolean\n reuseMaps?: boolean\n /** URL to open in external maps app (shows \"Open in Maps\" button if provided) */\n openInMapsUrl?: string\n /** Label for the open in maps button */\n openInMapsLabel?: string\n /** Auto-reset to initial viewport after N ms of inactivity (0 = disabled) */\n autoResetDelay?: number\n /** Show reset button */\n showResetButton?: boolean\n}\n\ninterface MapInnerProps extends Omit<MapContainerProps, 'initialViewport'> {}\n\nfunction MapInner({\n children,\n mapStyle = 'light',\n interactiveLayerIds,\n style,\n cursor,\n attributionControl = true,\n reuseMaps = true,\n openInMapsUrl,\n openInMapsLabel = 'Open in Maps',\n autoResetDelay = 0,\n showResetButton = false,\n}: MapInnerProps) {\n const { mapRef, viewport, setViewport, setIsLoaded, resetToInitial, initialViewport } = useMapContext()\n const resetTimerRef = useRef<NodeJS.Timeout | null>(null)\n const isInteractingRef = useRef(false)\n\n // Check if viewport has changed from initial\n const hasViewportChanged =\n Math.abs(viewport.longitude - initialViewport.longitude) > 0.0001 ||\n Math.abs(viewport.latitude - initialViewport.latitude) > 0.0001 ||\n Math.abs(viewport.zoom - initialViewport.zoom) > 0.1\n\n const handleMove = useCallback(\n (evt: ViewStateChangeEvent) => {\n setViewport({\n longitude: evt.viewState.longitude,\n latitude: evt.viewState.latitude,\n zoom: evt.viewState.zoom,\n bearing: evt.viewState.bearing,\n pitch: evt.viewState.pitch,\n })\n },\n [setViewport]\n )\n\n const handleMoveStart = useCallback(() => {\n isInteractingRef.current = true\n // Clear any pending reset timer\n if (resetTimerRef.current) {\n clearTimeout(resetTimerRef.current)\n resetTimerRef.current = null\n }\n }, [])\n\n const handleMoveEnd = useCallback(() => {\n isInteractingRef.current = false\n // Start auto-reset timer if enabled\n if (autoResetDelay > 0) {\n resetTimerRef.current = setTimeout(() => {\n if (!isInteractingRef.current) {\n resetToInitial()\n }\n }, autoResetDelay)\n }\n }, [autoResetDelay, resetToInitial])\n\n const handleLoad = useCallback(() => {\n setIsLoaded(true)\n }, [setIsLoaded])\n\n // Cleanup timer on unmount\n useEffect(() => {\n return () => {\n if (resetTimerRef.current) {\n clearTimeout(resetTimerRef.current)\n }\n }\n }, [])\n\n const resolvedStyle = mapStyle in MAP_STYLES\n ? MAP_STYLES[mapStyle as MapStyleKey]\n : mapStyle\n\n return (\n <>\n <Map\n ref={mapRef}\n {...viewport}\n onMove={handleMove}\n onMoveStart={handleMoveStart}\n onMoveEnd={handleMoveEnd}\n onLoad={handleLoad}\n mapStyle={resolvedStyle}\n interactiveLayerIds={interactiveLayerIds}\n attributionControl={attributionControl ? {} : false}\n reuseMaps={reuseMaps}\n style={{\n width: '100%',\n height: '100%',\n ...style,\n }}\n cursor={cursor}\n >\n {children}\n </Map>\n\n {/* Map overlay buttons */}\n <div className=\"absolute bottom-3 right-3 flex items-center gap-2\">\n {/* Reset button */}\n {showResetButton && hasViewportChanged && (\n <button\n type=\"button\"\n onClick={resetToInitial}\n className={cn(\n 'inline-flex items-center gap-2 px-3 py-2 rounded-lg',\n 'bg-background/95 backdrop-blur-sm text-foreground text-sm font-medium',\n 'hover:bg-background transition-colors shadow-sm border border-border'\n )}\n >\n <RotateCcw className=\"w-4 h-4\" />\n Reset\n </button>\n )}\n\n {/* Open in Maps button */}\n {openInMapsUrl && (\n <a\n href={openInMapsUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={cn(\n 'inline-flex items-center gap-2 px-4 py-2 rounded-lg',\n 'bg-background/95 backdrop-blur-sm text-foreground text-sm font-medium',\n 'hover:bg-background transition-colors shadow-sm border border-border'\n )}\n >\n <ExternalLink className=\"w-4 h-4\" />\n {openInMapsLabel}\n </a>\n )}\n </div>\n </>\n )\n}\n\nexport function MapContainer({\n children,\n initialViewport,\n className,\n ...props\n}: MapContainerProps) {\n return (\n <div className={className} style={{ width: '100%', height: '100%', position: 'relative' }}>\n <MapProvider initialViewport={initialViewport}>\n <MapInner {...props}>{children}</MapInner>\n </MapProvider>\n </div>\n )\n}\n\n/**\n * Use this when you need the map inside an existing MapProvider\n */\nexport function MapView(props: Omit<MapContainerProps, 'initialViewport'>) {\n return <MapInner {...props} />\n}\n"]}