@demokit-ai/react 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,480 +1,43 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import * as _demokit_ai_core from '@demokit-ai/core';
3
- import { FixtureMap, CloudFixtureResponse, SessionState } from '@demokit-ai/core';
4
- export { FixtureHandler, FixtureMap, RequestContext, SessionState } from '@demokit-ai/core';
5
- import * as react from 'react';
6
- import { ReactNode, CSSProperties } from 'react';
7
-
8
1
  /**
9
- * Props for the DemoKitProvider component
2
+ * @demokit-ai/react
10
3
  *
11
- * The provider supports two modes:
12
- * 1. **Local mode**: Provide `fixtures` prop with pattern handlers
13
- * 2. **Remote mode**: Provide `apiKey` to fetch from DemoKit Cloud
4
+ * React bindings for DemoKit - provider, hooks, and components.
14
5
  *
15
- * @example Local mode
16
- * ```tsx
17
- * <DemoKitProvider fixtures={{ 'GET /api/users': () => [] }}>
18
- * <App />
19
- * </DemoKitProvider>
20
- * ```
21
- *
22
- * @example Remote mode (zero-config)
23
- * ```tsx
24
- * <DemoKitProvider apiKey="dk_live_xxx">
25
- * <App />
26
- * </DemoKitProvider>
27
- * ```
28
- *
29
- * @example Remote mode with local overrides
30
- * ```tsx
31
- * <DemoKitProvider
32
- * apiKey="dk_live_xxx"
33
- * fixtures={{ 'POST /api/users': ({ body }) => ({ id: 'custom', ...body }) }}
34
- * >
35
- * <App />
36
- * </DemoKitProvider>
37
- * ```
38
- */
39
- interface DemoKitProviderProps {
40
- /**
41
- * Child components to render
42
- */
43
- children: ReactNode;
44
- /**
45
- * Map of URL patterns to fixture handlers (local mode)
46
- * In remote mode, these act as overrides for cloud fixtures
47
- */
48
- fixtures?: FixtureMap;
49
- /**
50
- * DemoKit Cloud API key for remote mode
51
- * Format: dk_live_xxxx
52
- *
53
- * When provided, fixtures are fetched from DemoKit Cloud.
54
- * Any `fixtures` prop values will override the cloud fixtures.
55
- */
56
- apiKey?: string;
57
- /**
58
- * DemoKit Cloud API URL
59
- * @default 'https://api.demokit.cloud'
60
- */
61
- cloudUrl?: string;
62
- /**
63
- * Timeout for cloud API requests in milliseconds
64
- * @default 10000
65
- */
66
- timeout?: number;
67
- /**
68
- * Whether to retry on fetch failure
69
- * @default true
70
- */
71
- retry?: boolean;
72
- /**
73
- * Maximum number of retries for cloud fetch
74
- * @default 3
75
- */
76
- maxRetries?: number;
77
- /**
78
- * Callback when remote fixtures are successfully loaded
79
- */
80
- onRemoteLoad?: (response: CloudFixtureResponse) => void;
81
- /**
82
- * Callback when remote fetch fails
83
- */
84
- onRemoteError?: (error: Error) => void;
85
- /**
86
- * Content to render while loading remote fixtures
87
- * @default null (renders nothing while loading)
88
- */
89
- loadingFallback?: ReactNode;
90
- /**
91
- * Content to render when remote fetch fails
92
- * If not provided, children are rendered (with local fixtures only if provided)
93
- */
94
- errorFallback?: ReactNode | ((error: Error) => ReactNode);
95
- /**
96
- * localStorage key for persisting demo mode state
97
- * @default 'demokit-mode'
98
- */
99
- storageKey?: string;
100
- /**
101
- * Whether demo mode should be initially enabled
102
- * If not provided, will read from localStorage
103
- * @default false
104
- */
105
- initialEnabled?: boolean;
106
- /**
107
- * Callback invoked when demo mode state changes
108
- */
109
- onDemoModeChange?: (enabled: boolean) => void;
110
- /**
111
- * Base URL to use for relative URL parsing
112
- * @default 'http://localhost'
113
- */
114
- baseUrl?: string;
115
- }
116
- /**
117
- * Value provided by the DemoMode context
118
- */
119
- interface DemoModeContextValue {
120
- /**
121
- * Whether demo mode is currently enabled
122
- */
123
- isDemoMode: boolean;
124
- /**
125
- * Whether the component has hydrated (for SSR safety)
126
- * Always check this before rendering demo-dependent UI
127
- */
128
- isHydrated: boolean;
129
- /**
130
- * Whether remote fixtures are currently being loaded
131
- * Only relevant when apiKey is provided
132
- */
133
- isLoading: boolean;
134
- /**
135
- * Error that occurred during remote fetch
136
- * Only set when apiKey is provided and fetch fails
137
- */
138
- remoteError: Error | null;
139
- /**
140
- * Version identifier from the loaded cloud fixtures
141
- * Useful for cache invalidation and debugging
142
- */
143
- remoteVersion: string | null;
144
- /**
145
- * Enable demo mode
146
- */
147
- enable(): void;
148
- /**
149
- * Disable demo mode
150
- */
151
- disable(): void;
152
- /**
153
- * Toggle demo mode and return the new state
154
- */
155
- toggle(): void;
156
- /**
157
- * Set demo mode to a specific state
158
- */
159
- setDemoMode(enabled: boolean): void;
160
- /**
161
- * Reset the session state, clearing all stored data
162
- * Call this to manually reset the demo session without page refresh
163
- */
164
- resetSession(): void;
165
- /**
166
- * Get the current session state instance
167
- * Useful for inspecting or manipulating session state directly
168
- * Returns null if the interceptor hasn't been initialized yet
169
- */
170
- getSession(): SessionState | null;
171
- /**
172
- * Refetch fixtures from DemoKit Cloud
173
- * Only works when apiKey is provided
174
- * Returns a promise that resolves when the fetch completes
175
- */
176
- refetch(): Promise<void>;
177
- }
178
- /**
179
- * Props for the DemoModeBanner component
180
- */
181
- interface DemoModeBannerProps {
182
- /**
183
- * Additional CSS class name
184
- */
185
- className?: string;
186
- /**
187
- * Label for the exit button
188
- * @default 'Exit Demo Mode'
189
- */
190
- exitLabel?: string;
191
- /**
192
- * Label shown when demo mode is active
193
- * @default 'Demo Mode Active'
194
- */
195
- demoLabel?: string;
196
- /**
197
- * Description shown in the banner
198
- * @default 'Changes are simulated and not saved'
199
- */
200
- description?: string;
201
- /**
202
- * Whether to show the eye icon
203
- * @default true
204
- */
205
- showIcon?: boolean;
206
- /**
207
- * Show "Powered by DemoKit" branding
208
- * Note: For OSS users, this is always true regardless of the prop value.
209
- * Only paid DemoKit Cloud users can hide the branding.
210
- * @default true
211
- */
212
- showPoweredBy?: boolean;
213
- /**
214
- * URL for the "Powered by" link
215
- * @default 'https://demokit.ai'
216
- */
217
- poweredByUrl?: string;
218
- /**
219
- * Custom styles for the banner container
220
- */
221
- style?: React.CSSProperties;
222
- /**
223
- * Callback when exit button is clicked
224
- * If not provided, will call disable() from context
225
- */
226
- onExit?: () => void;
227
- }
228
-
229
- /**
230
- * Provider component that enables demo mode functionality
231
- *
232
- * Wraps your app to provide demo mode state and controls.
233
- * Handles SSR hydration safely and persists state to localStorage.
234
- *
235
- * Supports two modes:
236
- * 1. **Local mode**: Pass `fixtures` prop with pattern handlers
237
- * 2. **Remote mode**: Pass `apiKey` to fetch from DemoKit Cloud
6
+ * @example
7
+ * import { DemoKitProvider, useDemoMode, DemoModeBanner } from '@demokit-ai/react'
238
8
  *
239
- * @example Local mode
240
- * ```tsx
241
9
  * const fixtures = {
242
10
  * 'GET /api/users': () => [{ id: '1', name: 'Demo User' }],
243
- * 'GET /api/users/:id': ({ params }) => ({ id: params.id, name: 'Demo User' }),
244
11
  * }
245
12
  *
246
13
  * function App() {
247
14
  * return (
248
15
  * <DemoKitProvider fixtures={fixtures}>
16
+ * <DemoModeBanner />
249
17
  * <YourApp />
250
18
  * </DemoKitProvider>
251
19
  * )
252
20
  * }
253
- * ```
254
- *
255
- * @example Remote mode (zero-config)
256
- * ```tsx
257
- * function App() {
258
- * return (
259
- * <DemoKitProvider
260
- * apiKey="dk_live_xxx"
261
- * loadingFallback={<LoadingSpinner />}
262
- * >
263
- * <YourApp />
264
- * </DemoKitProvider>
265
- * )
266
- * }
267
- * ```
268
- */
269
- declare function DemoKitProvider({ children, fixtures, apiKey, cloudUrl, timeout, retry, maxRetries, onRemoteLoad, onRemoteError, loadingFallback, errorFallback, storageKey, initialEnabled, onDemoModeChange, baseUrl, }: DemoKitProviderProps): react_jsx_runtime.JSX.Element;
270
-
271
- /**
272
- * Hook to access demo mode state and controls
273
- *
274
- * @returns Demo mode context value with state and control methods
275
- * @throws Error if used outside of DemoKitProvider
276
21
  *
277
- * @example
22
+ * // In any component
278
23
  * function MyComponent() {
279
24
  * const { isDemoMode, isHydrated, toggle } = useDemoMode()
280
- *
281
- * // Wait for hydration before rendering demo-dependent UI
282
- * if (!isHydrated) {
283
- * return <Loading />
284
- * }
285
- *
286
- * return (
287
- * <div>
288
- * <p>Demo mode: {isDemoMode ? 'ON' : 'OFF'}</p>
289
- * <button onClick={toggle}>Toggle</button>
290
- * </div>
291
- * )
292
- * }
293
- */
294
- declare function useDemoMode(): DemoModeContextValue;
295
- /**
296
- * Hook to check if demo mode is enabled
297
- * Shorthand for useDemoMode().isDemoMode
298
- *
299
- * @returns Whether demo mode is enabled
300
- */
301
- declare function useIsDemoMode(): boolean;
302
- /**
303
- * Hook to check if the component has hydrated
304
- * Shorthand for useDemoMode().isHydrated
305
- *
306
- * @returns Whether the component has hydrated
307
- */
308
- declare function useIsHydrated(): boolean;
309
- /**
310
- * Hook to access the session state
311
- * Shorthand for useDemoMode().getSession()
312
- *
313
- * @returns The session state, or null if not yet initialized
314
- *
315
- * @example
316
- * function MyComponent() {
317
- * const session = useDemoSession()
318
- *
319
- * const cart = session?.get<CartItem[]>('cart') || []
320
- * const addToCart = (item: CartItem) => {
321
- * session?.set('cart', [...cart, item])
322
- * }
323
- *
324
- * return <CartView items={cart} onAdd={addToCart} />
25
+ * // ...
325
26
  * }
326
- */
327
- declare function useDemoSession(): _demokit_ai_core.SessionState | null;
328
-
329
- /**
330
- * A ready-to-use banner component that shows when demo mode is active
331
- *
332
- * Displays a prominent amber banner with a label, description, and exit button.
333
- * Automatically hides when demo mode is disabled or before hydration.
334
- *
335
- * @example
336
- * function App() {
337
- * return (
338
- * <DemoKitProvider fixtures={fixtures}>
339
- * <DemoModeBanner />
340
- * <YourApp />
341
- * </DemoKitProvider>
342
- * )
343
- * }
344
- *
345
- * @example Custom labels
346
- * <DemoModeBanner
347
- * demoLabel="Preview Mode"
348
- * description="You're viewing sample data"
349
- * exitLabel="Exit Preview"
350
- * />
351
- */
352
- declare function DemoModeBanner({ className, exitLabel, demoLabel, description, showIcon, showPoweredBy, poweredByUrl, style, onExit, }: DemoModeBannerProps): react_jsx_runtime.JSX.Element | null;
353
-
354
- /**
355
- * Props for the DemoModeToggle component
356
- */
357
- interface DemoModeToggleProps {
358
- /**
359
- * Show "Demo Mode" label next to the toggle
360
- * @default true
361
- */
362
- showLabel?: boolean;
363
- /**
364
- * Label text to display
365
- * @default 'Demo Mode'
366
- */
367
- label?: string;
368
- /**
369
- * Show "Powered by DemoKit" branding
370
- * Note: For OSS users, this is always true regardless of the prop value.
371
- * Only paid DemoKit Cloud users can hide the branding.
372
- * @default true
373
- */
374
- showPoweredBy?: boolean;
375
- /**
376
- * URL for the "Powered by" link
377
- * @default 'https://demokit.ai'
378
- */
379
- poweredByUrl?: string;
380
- /**
381
- * Position of the toggle
382
- * - 'inline': Renders where placed in the component tree
383
- * - 'floating': Fixed position, can be moved around
384
- * - 'corner': Fixed to bottom-right corner
385
- * @default 'inline'
386
- */
387
- position?: 'inline' | 'floating' | 'corner';
388
- /**
389
- * Size of the toggle
390
- * @default 'md'
391
- */
392
- size?: 'sm' | 'md' | 'lg';
393
- /**
394
- * Additional CSS class name
395
- */
396
- className?: string;
397
- /**
398
- * Custom styles
399
- */
400
- style?: CSSProperties;
401
- /**
402
- * Callback when toggle state changes
403
- */
404
- onChange?: (enabled: boolean) => void;
405
- }
406
- /**
407
- * A toggle switch component for enabling/disabling demo mode
408
- *
409
- * Supports inline placement or fixed positioning (floating/corner).
410
- * Includes optional "Powered by DemoKit" branding that is always shown for OSS users.
411
- *
412
- * @example Basic inline usage
413
- * <DemoModeToggle />
414
27
  *
415
- * @example Corner position (floating)
416
- * <DemoModeToggle position="corner" />
417
- *
418
- * @example Without label
419
- * <DemoModeToggle showLabel={false} />
420
- *
421
- * @example With custom label and callback
422
- * <DemoModeToggle
423
- * label="Preview Mode"
424
- * onChange={(enabled) => console.log('Demo mode:', enabled)}
425
- * />
426
- */
427
- declare function DemoModeToggle({ showLabel, label, showPoweredBy, poweredByUrl, position, size, className, style, onChange, }: DemoModeToggleProps): react_jsx_runtime.JSX.Element | null;
428
-
429
- /**
430
- * Props for the PoweredByBadge component
431
- */
432
- interface PoweredByBadgeProps {
433
- /**
434
- * URL to link to when clicked
435
- * @default 'https://demokit.ai'
436
- */
437
- url?: string;
438
- /**
439
- * Visual variant for light/dark backgrounds
440
- * @default 'auto'
441
- */
442
- variant?: 'light' | 'dark' | 'auto';
443
- /**
444
- * Size of the badge
445
- * @default 'sm'
446
- */
447
- size?: 'xs' | 'sm' | 'md';
448
- /**
449
- * Additional CSS class name
450
- */
451
- className?: string;
452
- /**
453
- * Custom styles
454
- */
455
- style?: CSSProperties;
456
- }
457
- /**
458
- * A "Powered by DemoKit" badge that links to demokit.ai
459
- *
460
- * This badge is shown by default for OSS users and cannot be hidden
461
- * without a valid DemoKit Cloud paid plan.
462
- *
463
- * @example
464
- * <PoweredByBadge />
465
- *
466
- * @example With dark theme
467
- * <PoweredByBadge variant="dark" />
468
- *
469
- * @example Small size
470
- * <PoweredByBadge size="xs" />
471
- */
472
- declare function PoweredByBadge({ url, variant, size, className, style, }: PoweredByBadgeProps): react_jsx_runtime.JSX.Element;
473
-
474
- /**
475
- * React context for demo mode state
476
- * @internal
477
- */
478
- declare const DemoModeContext: react.Context<DemoModeContextValue | undefined>;
479
-
480
- export { DemoKitProvider, type DemoKitProviderProps, DemoModeBanner, type DemoModeBannerProps, DemoModeContext, type DemoModeContextValue, DemoModeToggle, type DemoModeToggleProps, PoweredByBadge, type PoweredByBadgeProps, useDemoMode, useDemoSession, useIsDemoMode, useIsHydrated };
28
+ * @packageDocumentation
29
+ */
30
+ export { DemoKitProvider } from './provider';
31
+ export { createRemoteSource } from './config';
32
+ export { useDemoMode, useIsDemoMode, useIsHydrated, useDemoSession } from './hooks';
33
+ export { useDemoGuard } from './guard';
34
+ export { DemoModeBanner } from './banner';
35
+ export { DemoModeToggle } from './toggle';
36
+ export { PoweredByBadge } from './powered-by';
37
+ export { DemoModeContext } from './context';
38
+ export type { DemoKitProviderProps, DemoModeContextValue, DemoModeBannerProps, } from './types';
39
+ export type { UseDemoGuardOptions, DemoGuardReturn } from './guard';
40
+ export type { DemoModeToggleProps } from './toggle';
41
+ export type { PoweredByBadgeProps } from './powered-by';
42
+ export type { FixtureMap, FixtureHandler, RequestContext, SessionState, RemoteConfig, DetectionConfig, MutationInterceptedContext, } from '@demokit-ai/core';
43
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAG5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAG7C,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACnF,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAGtC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAG7C,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAG3C,YAAY,EACV,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,SAAS,CAAA;AAGhB,YAAY,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAGnE,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AACnD,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAGvD,YAAY,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,0BAA0B,GAC3B,MAAM,kBAAkB,CAAA"}
package/dist/index.js CHANGED
@@ -17,11 +17,7 @@ function DemoKitProvider({
17
17
  children,
18
18
  fixtures,
19
19
  // Remote config
20
- apiKey,
21
- cloudUrl,
22
- timeout,
23
- retry,
24
- maxRetries,
20
+ source,
25
21
  onRemoteLoad,
26
22
  onRemoteError,
27
23
  loadingFallback = null,
@@ -30,11 +26,16 @@ function DemoKitProvider({
30
26
  storageKey = "demokit-mode",
31
27
  initialEnabled = false,
32
28
  onDemoModeChange,
33
- baseUrl
29
+ baseUrl,
30
+ // Detection & guards
31
+ detection,
32
+ canDisable,
33
+ onMutationIntercepted
34
34
  }) {
35
35
  const [isDemoMode, setIsDemoMode] = useState(initialEnabled);
36
36
  const [isHydrated, setIsHydrated] = useState(false);
37
- const [isLoading, setIsLoading] = useState(!!apiKey);
37
+ const [isPublicDemo, setIsPublicDemo] = useState(false);
38
+ const [isLoading, setIsLoading] = useState(!!source?.apiKey);
38
39
  const [remoteError, setRemoteError] = useState(null);
39
40
  const [remoteVersion, setRemoteVersion] = useState(null);
40
41
  const interceptorRef = useRef(null);
@@ -49,6 +50,9 @@ function DemoKitProvider({
49
50
  storageKey,
50
51
  initialEnabled,
51
52
  baseUrl,
53
+ detection,
54
+ canDisable,
55
+ onMutationIntercepted,
52
56
  onEnable: () => {
53
57
  setIsDemoMode(true);
54
58
  onDemoModeChange?.(true);
@@ -60,21 +64,22 @@ function DemoKitProvider({
60
64
  });
61
65
  const storedState = interceptorRef.current.isEnabled();
62
66
  setIsDemoMode(storedState);
67
+ setIsPublicDemo(interceptorRef.current.isPublicDemo());
63
68
  setIsHydrated(true);
64
69
  },
65
- [storageKey, initialEnabled, baseUrl, onDemoModeChange]
70
+ [storageKey, initialEnabled, baseUrl, onDemoModeChange, detection, canDisable, onMutationIntercepted]
66
71
  );
67
72
  const fetchAndSetup = useCallback(async () => {
68
- if (!apiKey) return;
73
+ if (!source?.apiKey) return;
69
74
  setIsLoading(true);
70
75
  setRemoteError(null);
71
76
  try {
72
77
  const response = await fetchCloudFixtures({
73
- apiKey,
74
- cloudUrl,
75
- timeout,
76
- retry,
77
- maxRetries,
78
+ apiKey: source.apiKey,
79
+ apiUrl: source.apiUrl,
80
+ timeout: source.timeout,
81
+ retry: source.retry,
82
+ maxRetries: source.maxRetries,
78
83
  onLoad: onRemoteLoad,
79
84
  onError: onRemoteError
80
85
  });
@@ -95,11 +100,7 @@ function DemoKitProvider({
95
100
  setIsLoading(false);
96
101
  }
97
102
  }, [
98
- apiKey,
99
- cloudUrl,
100
- timeout,
101
- retry,
102
- maxRetries,
103
+ source,
103
104
  fixtures,
104
105
  onRemoteLoad,
105
106
  onRemoteError,
@@ -111,7 +112,7 @@ function DemoKitProvider({
111
112
  return;
112
113
  }
113
114
  initializedRef.current = true;
114
- if (apiKey) {
115
+ if (source?.apiKey) {
115
116
  fetchAndSetup();
116
117
  } else if (fixtures) {
117
118
  setupInterceptor(fixtures);
@@ -127,18 +128,18 @@ function DemoKitProvider({
127
128
  }, []);
128
129
  useEffect(() => {
129
130
  if (!isHydrated || isLoading) return;
130
- if (apiKey && remoteFixturesRef.current) {
131
+ if (source?.apiKey && remoteFixturesRef.current) {
131
132
  const merged = { ...remoteFixturesRef.current, ...fixtures };
132
133
  interceptorRef.current?.setFixtures(merged);
133
134
  } else if (fixtures) {
134
135
  interceptorRef.current?.setFixtures(fixtures);
135
136
  }
136
- }, [fixtures, isHydrated, isLoading, apiKey]);
137
+ }, [fixtures, isHydrated, isLoading, source]);
137
138
  const enable = useCallback(() => {
138
139
  interceptorRef.current?.enable();
139
140
  }, []);
140
141
  const disable = useCallback(() => {
141
- interceptorRef.current?.disable();
142
+ return interceptorRef.current?.disable() ?? true;
142
143
  }, []);
143
144
  const toggle = useCallback(() => {
144
145
  interceptorRef.current?.toggle();
@@ -157,16 +158,17 @@ function DemoKitProvider({
157
158
  return interceptorRef.current?.getSession() ?? null;
158
159
  }, []);
159
160
  const refetch = useCallback(async () => {
160
- if (!apiKey) {
161
- console.warn("[DemoKit] refetch() called but no apiKey provided");
161
+ if (!source?.apiKey) {
162
+ console.warn("[DemoKit] refetch() called but no source provided");
162
163
  return;
163
164
  }
164
165
  await refetchFnRef.current?.();
165
- }, [apiKey]);
166
+ }, [source]);
166
167
  const value = useMemo(
167
168
  () => ({
168
169
  isDemoMode,
169
170
  isHydrated,
171
+ isPublicDemo,
170
172
  isLoading,
171
173
  remoteError,
172
174
  remoteVersion,
@@ -181,6 +183,7 @@ function DemoKitProvider({
181
183
  [
182
184
  isDemoMode,
183
185
  isHydrated,
186
+ isPublicDemo,
184
187
  isLoading,
185
188
  remoteError,
186
189
  remoteVersion,
@@ -193,7 +196,7 @@ function DemoKitProvider({
193
196
  refetch
194
197
  ]
195
198
  );
196
- if (isLoading && apiKey) {
199
+ if (isLoading && source?.apiKey) {
197
200
  return /* @__PURE__ */ jsx(DemoModeContext.Provider, { value, children: loadingFallback });
198
201
  }
199
202
  if (remoteError && errorFallback) {
@@ -203,6 +206,16 @@ function DemoKitProvider({
203
206
  return /* @__PURE__ */ jsx(DemoModeContext.Provider, { value, children });
204
207
  }
205
208
 
209
+ // src/config.ts
210
+ function createRemoteSource(config) {
211
+ return {
212
+ timeout: 1e4,
213
+ retry: true,
214
+ maxRetries: 3,
215
+ ...config
216
+ };
217
+ }
218
+
206
219
  // src/hooks.ts
207
220
  import { useContext } from "react";
208
221
  function useDemoMode() {
@@ -224,6 +237,29 @@ function useDemoSession() {
224
237
  return useDemoMode().getSession();
225
238
  }
226
239
 
240
+ // src/guard.ts
241
+ import { useCallback as useCallback2 } from "react";
242
+ function useDemoGuard(options = {}) {
243
+ const { isDemoMode } = useDemoMode();
244
+ const { onBlocked } = options;
245
+ const guardMutation = useCallback2(
246
+ (action, actionName) => {
247
+ if (isDemoMode) {
248
+ const message = actionName ? `${actionName} (simulated in demo mode)` : "Action simulated in demo mode";
249
+ onBlocked?.(message);
250
+ return false;
251
+ }
252
+ action();
253
+ return true;
254
+ },
255
+ [isDemoMode, onBlocked]
256
+ );
257
+ return {
258
+ guardMutation,
259
+ isDemoMode
260
+ };
261
+ }
262
+
227
263
  // src/powered-by.tsx
228
264
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
229
265
  function ExternalLinkIcon({ size }) {
@@ -625,6 +661,8 @@ export {
625
661
  DemoModeContext,
626
662
  DemoModeToggle,
627
663
  PoweredByBadge,
664
+ createRemoteSource,
665
+ useDemoGuard,
628
666
  useDemoMode,
629
667
  useDemoSession,
630
668
  useIsDemoMode,