@unleash/toolbar 1.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.es.js","sources":["../src/state.ts","../src/wrapper.ts","../src/index.ts"],"sourcesContent":["import type {\n FlagMetadata,\n FlagOverride,\n FlagValue,\n StorageMode,\n ToolbarEvent,\n ToolbarEventListener,\n ToolbarState,\n UnleashContext,\n UnleashVariant,\n} from './types';\n\n/**\n * Helper to set a cookie with proper encoding\n */\nasync function setCookie(name: string, value: string, maxAgeSeconds: number): Promise<void> {\n if (typeof document === 'undefined') return;\n\n const encodedValue = encodeURIComponent(value);\n\n // Use Cookie Store API if available, fallback to document.cookie\n if ('cookieStore' in window) {\n try {\n await window.cookieStore.set({\n name,\n value: encodedValue,\n path: '/',\n sameSite: 'lax',\n expires: Date.now() + maxAgeSeconds * 1000,\n });\n return;\n } catch {\n // Fallback to document.cookie if Cookie Store API fails\n }\n }\n\n // Fallback for browsers without Cookie Store API\n const cookieParts = [\n `${name}=${encodedValue}`,\n 'path=/',\n `max-age=${maxAgeSeconds}`,\n 'SameSite=Lax',\n ];\n\n // biome-ignore lint/suspicious/noDocumentCookie: Fallback for browsers without Cookie Store API\n document.cookie = cookieParts.join('; ');\n}\n\n/**\n * Helper to delete a cookie\n */\nasync function deleteCookie(name: string): Promise<void> {\n if (typeof document === 'undefined') return;\n\n // Use Cookie Store API if available, fallback to document.cookie\n if ('cookieStore' in window) {\n try {\n await window.cookieStore.delete(name);\n return;\n } catch {\n // Fallback to document.cookie if Cookie Store API fails\n }\n }\n\n // Fallback for browsers without Cookie Store API\n // biome-ignore lint/suspicious/noDocumentCookie: Fallback for browsers without Cookie Store API\n document.cookie = `${name}=; path=/; max-age=0`;\n}\n\n/**\n * Event emitter for toolbar state changes\n */\nclass EventEmitter {\n private listeners: Set<ToolbarEventListener> = new Set();\n\n subscribe(listener: ToolbarEventListener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n emit(event: ToolbarEvent): void {\n this.listeners.forEach((listener) => {\n try {\n listener(event);\n } catch (error) {\n console.error('[Unleash Toolbar] Error in event listener:', error);\n }\n });\n }\n}\n\n/**\n * Storage abstraction for different persistence modes\n */\nclass StorageAdapter {\n private enableCookieSync: boolean = false;\n\n constructor(\n private mode: StorageMode,\n private key: string,\n ) {}\n\n /**\n * Enable or disable cookie synchronization for SSR support\n * Should be enabled by Next.js integration\n */\n setCookieSyncEnabled(enabled: boolean): void {\n this.enableCookieSync = enabled;\n }\n\n private getStorage(): Storage | null {\n if (typeof window === 'undefined') return null;\n\n switch (this.mode) {\n case 'local':\n return window.localStorage;\n case 'session':\n return window.sessionStorage;\n default:\n return null;\n }\n }\n\n /**\n * Syncs state to cookies for server-side access (Next.js SSR)\n * Only runs when explicitly enabled via setCookieSyncEnabled()\n */\n private syncToCookies(state: ToolbarState): void {\n if (!this.enableCookieSync) return;\n if (typeof document === 'undefined') return;\n\n try {\n const value = JSON.stringify(state);\n // Set cookie with 7 day expiration, accessible from same origin\n setCookie('unleash-toolbar-state', value, 7 * 24 * 60 * 60);\n } catch (error) {\n console.error('[Unleash Toolbar] Failed to sync state to cookies:', error);\n }\n }\n\n load(): ToolbarState | null {\n const storage = this.getStorage();\n if (!storage) return null;\n\n try {\n const data = storage.getItem(this.key);\n return data ? JSON.parse(data) : null;\n } catch (error) {\n console.error('[Unleash Toolbar] Failed to load state from storage:', error);\n return null;\n }\n }\n\n save(state: ToolbarState): void {\n const storage = this.getStorage();\n if (!storage) return;\n\n try {\n storage.setItem(this.key, JSON.stringify(state));\n this.syncToCookies(state);\n } catch (error) {\n console.error('[Unleash Toolbar] Failed to save state to storage:', error);\n }\n }\n\n clear(): void {\n const storage = this.getStorage();\n if (!storage) return;\n\n try {\n storage.removeItem(this.key);\n // Also clear the cookie if sync was enabled\n if (this.enableCookieSync) {\n deleteCookie('unleash-toolbar-state');\n }\n } catch (error) {\n console.error('[Unleash Toolbar] Failed to clear storage:', error);\n }\n }\n}\n\n/**\n * Core state manager for the toolbar\n */\nexport class ToolbarStateManager {\n private state: ToolbarState;\n private eventEmitter: EventEmitter;\n private storage: StorageAdapter;\n private sortAlphabetically: boolean;\n\n constructor(\n storageMode: StorageMode = 'local',\n storageKey: string = 'unleash-toolbar-state',\n sortAlphabetically: boolean = false,\n ) {\n this.eventEmitter = new EventEmitter();\n this.storage = new StorageAdapter(storageMode, storageKey);\n this.sortAlphabetically = sortAlphabetically;\n\n // Try to load persisted state\n const persistedState = this.storage.load();\n this.state = persistedState || this.getInitialState();\n }\n\n private getInitialState(): ToolbarState {\n return {\n flags: {},\n contextOverrides: {},\n };\n }\n\n private persist(): void {\n this.storage.save(this.state);\n }\n\n /**\n * Apply override to a default value (same logic as wrapper)\n */\n private applyOverride(\n defaultValue: boolean | UnleashVariant | null,\n override: FlagOverride | null,\n ): boolean | UnleashVariant | null {\n if (!override) return defaultValue;\n\n if (override.type === 'flag') {\n return override.value;\n }\n\n if (override.type === 'variant') {\n // For variant overrides, create a variant object\n if (typeof defaultValue === 'object' && defaultValue !== null && 'name' in defaultValue) {\n return {\n ...defaultValue,\n name: override.variantKey,\n enabled: true,\n };\n }\n // If default was not a variant, create a new one\n return {\n name: override.variantKey,\n enabled: true,\n };\n }\n\n // Exhaustiveness check (for future-proofing)\n const _exhaustive: never = override;\n return _exhaustive;\n }\n\n /**\n * Re-evaluate all known flags (used when SDK configuration updates)\n */\n reEvaluateAllFlags(\n evaluator: (flagName: string) => { defaultValue: FlagValue; effectiveValue: FlagValue },\n ): void {\n const flagNames = Object.keys(this.state.flags);\n\n flagNames.forEach((flagName) => {\n const existing = this.state.flags[flagName];\n if (!existing) return;\n\n try {\n const { defaultValue, effectiveValue } = evaluator(flagName);\n\n // Update flag metadata with new default value\n this.state.flags[flagName] = {\n ...existing,\n lastDefaultValue: defaultValue,\n lastEffectiveValue: effectiveValue,\n };\n } catch (error) {\n // If evaluation fails, keep existing data\n console.error(`[Unleash Toolbar] Failed to re-evaluate flag ${flagName}:`, error);\n }\n });\n\n this.persist();\n\n this.eventEmitter.emit({\n type: 'sdk_updated',\n timestamp: Date.now(),\n });\n }\n\n /**\n * Get the current state (immutable copy)\n */\n getState(): ToolbarState {\n return JSON.parse(JSON.stringify(this.state));\n }\n\n /**\n * Subscribe to state change events\n */\n subscribe(listener: ToolbarEventListener): () => void {\n return this.eventEmitter.subscribe(listener);\n }\n\n /**\n * Record a flag evaluation (updates state without emitting events)\n */\n recordEvaluation(\n name: string,\n flagType: 'flag' | 'variant',\n defaultValue: boolean | UnleashVariant | null,\n effectiveValue: boolean | UnleashVariant | null,\n context: UnleashContext,\n ): void {\n const existing = this.state.flags[name];\n const isNewFlag = !existing;\n\n this.state.flags[name] = {\n flagType,\n lastDefaultValue: defaultValue,\n lastEffectiveValue: effectiveValue,\n lastContext: context,\n override: existing?.override || null,\n };\n\n this.persist();\n\n // Emit event only for new flags to trigger UI update\n if (isNewFlag) {\n this.eventEmitter.emit({\n type: 'sdk_updated',\n timestamp: Date.now(),\n });\n }\n }\n\n /**\n * Set or clear a flag override\n */\n setFlagOverride(name: string, override: FlagOverride | null): void {\n if (!this.state.flags[name]) {\n // Infer flag type from override type (shouldn't happen in normal flow)\n const flagType = override?.type === 'variant' ? 'variant' : 'flag';\n this.state.flags[name] = {\n flagType,\n lastDefaultValue: null,\n lastEffectiveValue: this.applyOverride(null, override),\n lastContext: null,\n override,\n };\n } else {\n this.state.flags[name].override = override;\n\n // Recalculate effective value based on new override\n const defaultValue = this.state.flags[name].lastDefaultValue;\n this.state.flags[name].lastEffectiveValue = this.applyOverride(defaultValue, override);\n }\n\n this.persist();\n\n this.eventEmitter.emit({\n type: 'flag_override_changed',\n name,\n override,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Get flag override\n */\n getFlagOverride(name: string): FlagOverride | null {\n return this.state.flags[name]?.override || null;\n }\n\n /**\n * Set context overrides\n */\n setContextOverride(context: Partial<UnleashContext>): void {\n this.state.contextOverrides = {\n ...this.state.contextOverrides,\n ...context,\n };\n\n this.persist();\n\n this.eventEmitter.emit({\n type: 'context_override_changed',\n contextOverrides: this.state.contextOverrides,\n });\n }\n\n /**\n * Remove a specific context override field\n */\n removeContextOverride(fieldName: keyof UnleashContext): void {\n const newOverrides = { ...this.state.contextOverrides };\n delete newOverrides[fieldName];\n\n // Replace entire context overrides object\n this.state.contextOverrides = newOverrides;\n this.persist();\n\n this.eventEmitter.emit({\n type: 'context_override_changed',\n contextOverrides: this.state.contextOverrides,\n });\n }\n\n /**\n * Get merged context (original + overrides)\n */\n getMergedContext(baseContext: UnleashContext = {}): UnleashContext {\n return {\n ...baseContext,\n ...this.state.contextOverrides,\n properties: {\n ...(baseContext.properties || {}),\n ...(this.state.contextOverrides.properties || {}),\n },\n };\n }\n\n /**\n * Reset all flag overrides\n */\n resetOverrides(): void {\n Object.keys(this.state.flags).forEach((name) => {\n this.state.flags[name].override = null;\n\n // Recalculate effective value (will match default)\n const defaultValue = this.state.flags[name].lastDefaultValue;\n this.state.flags[name].lastEffectiveValue = defaultValue;\n\n // Emit event for each flag override removal\n this.eventEmitter.emit({\n type: 'flag_override_changed',\n name,\n override: null,\n timestamp: Date.now(),\n });\n });\n\n this.persist();\n }\n\n /**\n * Reset context overrides\n */\n resetContextOverrides(): void {\n this.state.contextOverrides = {};\n\n this.persist();\n\n this.eventEmitter.emit({\n type: 'context_override_changed',\n contextOverrides: {},\n });\n }\n\n /**\n * Set toolbar visibility state\n */\n setVisibility(isVisible: boolean): void {\n this.state.isVisible = isVisible;\n this.persist();\n }\n\n /**\n * Get toolbar visibility state\n */\n getVisibility(): boolean | undefined {\n return this.state.isVisible;\n }\n\n /**\n * Clear all persisted data\n */\n clearPersistence(): void {\n this.storage.clear();\n }\n\n /**\n * Get all flag names in evaluation order (insertion order) or alphabetically\n */\n getFlagNames(): string[] {\n const names = Object.keys(this.state.flags);\n return this.sortAlphabetically ? names.sort() : names;\n }\n\n /**\n * Get metadata for a specific flag\n */\n getFlagMetadata(name: string): FlagMetadata | null {\n return this.state.flags[name] || null;\n }\n\n /**\n * Enable cookie synchronization for server-side rendering support\n * Should be called by Next.js integration when SSR is needed\n */\n enableCookieSync(): void {\n this.storage.setCookieSyncEnabled(true);\n }\n}\n","import type { UnleashClient } from 'unleash-proxy-client';\nimport type { ToolbarStateManager } from './state';\nimport type { FlagOverride, UnleashVariant, WrappedUnleashClient } from './types';\n\n/**\n * Wrap an Unleash client to intercept evaluations and apply overrides\n */\nexport function wrapUnleashClient(\n baseClient: UnleashClient,\n stateManager: ToolbarStateManager,\n): WrappedUnleashClient {\n // If already wrapped, return as-is\n if (isWrappedClient(baseClient)) {\n return baseClient as WrappedUnleashClient;\n }\n\n // Capture original base context before any updates\n const originalBaseContext = baseClient.getContext();\n\n // Track user-registered 'update' listeners so we can trigger them on toolbar changes\n const updateListeners = new Set<() => void>();\n\n // Reference to the final proxy (will be assigned below)\n let proxyClient: WrappedUnleashClient;\n\n // Create a partial object with only the methods we're intercepting.\n // The Proxy will handle all other methods by forwarding to baseClient.\n const wrappedClient: Partial<WrappedUnleashClient> = {\n __original: baseClient,\n isEnabled,\n getVariant,\n getContext,\n on: onHandler,\n };\n\n function isEnabled(toggleName: string): boolean {\n // Get merged context (toolbar overrides are applied via updateContext)\n const currentContext = baseClient.getContext();\n const mergedContext = stateManager.getMergedContext(currentContext);\n\n // Get default evaluation from base client (uses client's global context)\n const defaultValue = baseClient.isEnabled(toggleName);\n\n // Apply override if exists\n const override = stateManager.getFlagOverride(toggleName);\n const effectiveValue = applyFlagOverride(defaultValue, override);\n\n // Record evaluation with explicit flag type\n stateManager.recordEvaluation(toggleName, 'flag', defaultValue, effectiveValue, mergedContext);\n\n return effectiveValue as boolean;\n }\n\n function getVariant(toggleName: string): UnleashVariant {\n // Get merged context (toolbar overrides are applied via updateContext)\n const currentContext = baseClient.getContext();\n const mergedContext = stateManager.getMergedContext(currentContext);\n\n // Get default evaluation from base client (uses client's global context)\n const defaultValue = baseClient.getVariant(toggleName);\n\n // Apply override if exists\n const override = stateManager.getFlagOverride(toggleName);\n const effectiveValue = applyFlagOverride(defaultValue, override);\n\n // Record evaluation with explicit flag type\n stateManager.recordEvaluation(\n toggleName,\n 'variant',\n defaultValue,\n effectiveValue,\n mergedContext,\n );\n\n return effectiveValue as UnleashVariant;\n }\n\n function getContext() {\n const baseContext = baseClient.getContext();\n const merged = stateManager.getMergedContext(baseContext);\n // Ensure appName exists (required by SDK type)\n return {\n appName: '',\n ...merged,\n };\n }\n\n // Intercept on() method to capture 'update' listeners\n function onHandler(event: string, callback: () => void): WrappedUnleashClient {\n if (event === 'update') {\n updateListeners.add(callback);\n }\n // Forward to base client\n baseClient.on(event, callback);\n // Return the proxy for method chaining\n return proxyClient;\n }\n\n // Helper to trigger all registered 'update' listeners\n function triggerUpdateListeners() {\n updateListeners.forEach((callback) => {\n try {\n callback();\n } catch (error) {\n console.error('[Unleash Toolbar] Error in update listener:', error);\n }\n });\n }\n\n // Listen to SDK 'update' event to re-evaluate flags when config changes\n baseClient.on('update', () => {\n // Re-evaluate all known flags with the new SDK configuration\n stateManager.reEvaluateAllFlags((flagName) => {\n // Get flag metadata to determine type\n const metadata = stateManager.getFlagMetadata(flagName);\n const isVariant = metadata?.flagType === 'variant';\n\n // Get new default value from SDK using correct method\n const defaultValue = isVariant\n ? baseClient.getVariant(flagName)\n : baseClient.isEnabled(flagName);\n\n // Apply override if exists\n const override = stateManager.getFlagOverride(flagName);\n const effectiveValue = applyFlagOverride(defaultValue, override);\n\n return { defaultValue, effectiveValue };\n });\n // Trigger user's 'update' listeners (already called by SDK, but for consistency)\n });\n\n // Listen to toolbar state changes to update client context and trigger re-renders\n stateManager.subscribe((event) => {\n if (event.type === 'context_override_changed') {\n // Update the base client's context with merged context\n // Use original base context (not current client context which includes previous overrides)\n const mergedContext = stateManager.getMergedContext(originalBaseContext);\n\n // Remove appName and environment - they are static and can't be updated\n const { appName: _appName, environment: _environment, ...updatableContext } = mergedContext;\n\n // Update context on the client (this triggers SDK re-evaluation)\n baseClient\n .updateContext(updatableContext)\n .then(() => {\n // Re-evaluate all known flags after context update\n stateManager.reEvaluateAllFlags((flagName) => {\n // Get flag metadata to determine type\n const metadata = stateManager.getFlagMetadata(flagName);\n const isVariant = metadata?.flagType === 'variant';\n\n // Get new default value from SDK with updated context using correct method\n const defaultValue = isVariant\n ? baseClient.getVariant(flagName)\n : baseClient.isEnabled(flagName);\n\n // Apply override if exists\n const override = stateManager.getFlagOverride(flagName);\n const effectiveValue = applyFlagOverride(defaultValue, override);\n\n return { defaultValue, effectiveValue };\n });\n // Trigger user's 'update' listeners after context change\n triggerUpdateListeners();\n })\n .catch((err) => {\n console.error('[Unleash Toolbar] Failed to update context:', err);\n });\n }\n\n // Trigger user's 'update' listeners for flag override changes\n // (flag_override_changed is emitted when override is set or removed, including bulk resets)\n if (event.type === 'flag_override_changed') {\n triggerUpdateListeners();\n }\n });\n\n // Proxy all other methods and properties to the base client\n proxyClient = new Proxy(wrappedClient, {\n get(target, prop) {\n if (prop in target) {\n return Reflect.get(target, prop);\n }\n const value = Reflect.get(baseClient, prop);\n return typeof value === 'function' ? value.bind(baseClient) : value;\n },\n set(target, prop, value) {\n if (prop in target) {\n Reflect.set(target, prop, value);\n return true;\n }\n Reflect.set(baseClient, prop, value);\n return true;\n },\n }) as WrappedUnleashClient;\n\n return proxyClient;\n}\n\n/**\n * Apply flag override to evaluation result\n */\nfunction applyFlagOverride(\n defaultValue: boolean | UnleashVariant | null,\n override: FlagOverride | null,\n): boolean | UnleashVariant | null {\n if (!override) return defaultValue;\n\n if (override.type === 'flag') {\n return override.value;\n }\n\n if (override.type === 'variant') {\n // For variant overrides, create a variant object\n if (typeof defaultValue === 'object' && defaultValue !== null && 'name' in defaultValue) {\n return {\n ...defaultValue,\n name: override.variantKey,\n enabled: true,\n };\n }\n // If default was not a variant, create a new one\n return {\n name: override.variantKey,\n enabled: true,\n };\n }\n\n return defaultValue;\n}\n\n/**\n * Unwrap a client to get the original\n */\nexport function unwrapUnleashClient(client: UnleashClient): UnleashClient {\n if (isWrappedClient(client)) {\n return (client as WrappedUnleashClient).__original;\n }\n return client;\n}\n\n/**\n * Check if a client is wrapped\n */\nexport function isWrappedClient(client: UnleashClient): client is WrappedUnleashClient {\n return '__original' in client;\n}\n","import type { UnleashClient } from 'unleash-proxy-client';\nimport { ToolbarStateManager } from './state';\nimport type {\n FlagOverride,\n InitToolbarOptions,\n IToolbarUI,\n ToolbarState,\n UnleashContext,\n UnleashToolbarInstance,\n WrappedUnleashClient,\n} from './types';\nimport { wrapUnleashClient } from './wrapper';\n\n/**\n * Main toolbar instance implementation\n */\nexport class UnleashToolbar implements UnleashToolbarInstance {\n private stateManager: ToolbarStateManager;\n private ui: IToolbarUI | null;\n public readonly client: WrappedUnleashClient;\n\n constructor(\n stateManager: ToolbarStateManager,\n wrappedClient: WrappedUnleashClient,\n options: InitToolbarOptions,\n ) {\n this.stateManager = stateManager;\n this.client = wrappedClient;\n this.ui = null;\n\n // Initialize UI asynchronously to avoid SSR issues\n this.initUI(stateManager, wrappedClient, options);\n }\n\n private async initUI(\n stateManager: ToolbarStateManager,\n wrappedClient: WrappedUnleashClient,\n options: InitToolbarOptions,\n ) {\n const { ToolbarUI } = await import('./ui');\n this.ui = new ToolbarUI(stateManager, wrappedClient, options);\n }\n\n show(): void {\n if (this.ui) this.ui.show();\n }\n\n hide(): void {\n if (this.ui) this.ui.hide();\n }\n\n destroy(): void {\n if (this.ui) this.ui.destroy();\n this.stateManager.clearPersistence();\n }\n\n getState(): ToolbarState {\n return this.stateManager.getState();\n }\n\n getFlagNames(): string[] {\n return this.stateManager.getFlagNames();\n }\n\n setFlagOverride(name: string, override: FlagOverride | null): void {\n this.stateManager.setFlagOverride(name, override);\n }\n\n setContextOverride(context: Partial<UnleashContext>): void {\n this.stateManager.setContextOverride(context);\n }\n\n removeContextOverride(fieldName: keyof UnleashContext): void {\n this.stateManager.removeContextOverride(fieldName);\n }\n\n resetOverrides(): void {\n this.stateManager.resetOverrides();\n }\n\n resetContextOverrides(): void {\n this.stateManager.resetContextOverrides();\n }\n}\n\n/**\n * Initialize the Unleash Toolbar with a client\n * This is the main entry point - handles both toolbar creation and client wrapping\n * Returns the wrapped client directly for immediate use\n *\n * @param client - The Unleash client to wrap\n * @param options - Toolbar configuration options\n */\nexport function initUnleashToolbar(\n client: UnleashClient,\n options: InitToolbarOptions = {},\n): WrappedUnleashClient {\n const storageMode = options.storageMode || 'local';\n const storageKey = options.storageKey || 'unleash-toolbar-state';\n const sortAlphabetically = options.sortAlphabetically || false;\n const enableCookieSync = options.enableCookieSync || false;\n\n const stateManager = new ToolbarStateManager(storageMode, storageKey, sortAlphabetically);\n\n // Enable cookie sync if requested (for Next.js SSR)\n if (enableCookieSync) {\n stateManager.enableCookieSync();\n }\n\n const wrappedClient = wrapUnleashClient(client, stateManager);\n const toolbar = new UnleashToolbar(stateManager, wrappedClient, options);\n\n // Expose toolbar instance globally for debugging/advanced use\n if (typeof window !== 'undefined') {\n // biome-ignore lint/suspicious/noExplicitAny: no type for window\n (window as any).unleashToolbar = toolbar;\n }\n\n return wrappedClient;\n}\n\n// UMD global export\nif (typeof window !== 'undefined') {\n // biome-ignore lint/suspicious/noExplicitAny: no type for window\n (window as any).UnleashToolbar = {\n init: initUnleashToolbar,\n };\n}\n\nexport { ToolbarStateManager } from './state';\n// Export types\nexport * from './types';\nexport { wrapUnleashClient } from './wrapper';\n"],"names":["EventEmitter","listeners","Set","subscribe","listener","this","add","delete","emit","event","forEach","error","console","StorageAdapter","constructor","mode","key","enableCookieSync","setCookieSyncEnabled","enabled","getStorage","window","localStorage","sessionStorage","syncToCookies","state","document","async","name","value","encodedValue","encodeURIComponent","cookieStore","set","path","sameSite","expires","Date","now","maxAgeSeconds","cookie","join","setCookie","JSON","stringify","load","storage","data","getItem","parse","save","setItem","clear","removeItem","deleteCookie","ToolbarStateManager","eventEmitter","sortAlphabetically","storageMode","storageKey","persistedState","getInitialState","flags","contextOverrides","persist","applyOverride","defaultValue","override","type","variantKey","reEvaluateAllFlags","evaluator","Object","keys","flagName","existing","effectiveValue","lastDefaultValue","lastEffectiveValue","timestamp","getState","recordEvaluation","flagType","context","isNewFlag","lastContext","setFlagOverride","getFlagOverride","setContextOverride","removeContextOverride","fieldName","newOverrides","getMergedContext","baseContext","properties","resetOverrides","resetContextOverrides","setVisibility","isVisible","getVisibility","clearPersistence","getFlagNames","names","sort","getFlagMetadata","wrapUnleashClient","baseClient","stateManager","originalBaseContext","getContext","updateListeners","proxyClient","wrappedClient","__original","isEnabled","toggleName","currentContext","mergedContext","applyFlagOverride","getVariant","appName","on","callback","triggerUpdateListeners","metadata","_appName","environment","_environment","updatableContext","updateContext","then","catch","err","Proxy","get","target","prop","Reflect","bind","UnleashToolbar","ui","client","options","initUI","ToolbarUI","import","show","hide","destroy","initUnleashToolbar","toolbar","unleashToolbar","init"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { UnleashToolbarProvider as BaseUnleashToolbarProvider } from '../react/index';
2
+ import { default as React } from 'react';
3
+ /**
4
+ * Next.js-specific provider that wraps the official Unleash React SDK's FlagProvider
5
+ * and automatically enables cookie synchronization for server-side rendering support.
6
+ *
7
+ * This ensures toolbar state is available in server components via getToolbarStateFromCookies().
8
+ */
9
+ export declare function UnleashToolbarProvider(props: React.ComponentProps<typeof BaseUnleashToolbarProvider>): import("react/jsx-runtime").JSX.Element;
10
+ export * from '../react/hooks';
11
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/next/client.tsx"],"names":[],"mappings":"AASA,OAAO,EAAE,sBAAsB,IAAI,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAEtF,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,0BAA0B,CAAC,2CAQpG;AAED,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Next.js integration for Unleash Toolbar
3
+ *
4
+ * This package provides both client-side and server-side integration for Next.js:
5
+ *
6
+ * - Client-side: `@unleash/toolbar/next` (same as React integration)
7
+ * - Server-side: `@unleash/toolbar/next/server` (for App Router server components)
8
+ *
9
+ * @example Client Component
10
+ * ```tsx
11
+ * 'use client';
12
+ * import { UnleashToolbarProvider, useFlag } from '@unleash/toolbar/next';
13
+ *
14
+ * export function MyComponent() {
15
+ * return (
16
+ * <UnleashToolbarProvider config={{ ... }}>
17
+ * <App />
18
+ * </UnleashToolbarProvider>
19
+ * );
20
+ * }
21
+ * ```
22
+ *
23
+ * @example Server Component
24
+ * ```tsx
25
+ * import { cookies } from 'next/headers';
26
+ * import { getDefinitions, evaluateFlags, flagsClient } from '@unleash/nextjs';
27
+ * import { applyToolbarOverrides } from '@unleash/toolbar/next/server';
28
+ *
29
+ * export default async function Page() {
30
+ * const definitions = await getDefinitions();
31
+ * const modified = applyToolbarOverrides(definitions, await cookies());
32
+ * const { toggles } = evaluateFlags(modified, { sessionId: '123' });
33
+ * const flags = flagsClient(toggles);
34
+ *
35
+ * return <div>{flags.isEnabled('my-flag') ? 'ON' : 'OFF'}</div>;
36
+ * }
37
+ * ```
38
+ */
39
+ export * from '../react/hooks';
40
+ export { UnleashToolbarProvider } from './client';
41
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/next/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,cAAc,gBAAgB,CAAC;AAE/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { IToggle } from '@unleash/nextjs';
2
+ import { ClientFeaturesResponse } from 'unleash-client';
3
+ import { ToolbarState } from '../types';
4
+ export declare const UNLEASH_TOOLBAR_COOKIE = "unleash-toolbar-state";
5
+ /**
6
+ * Cookie store interface compatible with Next.js cookies()
7
+ */
8
+ interface CookieStore {
9
+ get(name: string): {
10
+ value: string;
11
+ } | undefined;
12
+ }
13
+ /**
14
+ * Parses toolbar state from cookie value
15
+ */
16
+ export declare function parseToolbarState(cookieValue: string | undefined): ToolbarState | null;
17
+ /**
18
+ * Reads toolbar state from Next.js cookie store
19
+ */
20
+ export declare function getToolbarStateFromCookies(cookieStore: CookieStore): ToolbarState | null;
21
+ /**
22
+ * Applies toolbar overrides to Unleash feature flag definitions.
23
+ *
24
+ * Use this with getDefinitions() from @unleash/nextjs before evaluating flags
25
+ * to ensure server-side evaluations respect toolbar overrides.
26
+ *
27
+ * @param definitions - Feature flag definitions from getDefinitions()
28
+ * @param cookieStore - Next.js cookie store from cookies()
29
+ * @returns Modified definitions with overrides applied
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * import { getDefinitions, evaluateFlags } from '@unleash/nextjs';
34
+ * import { cookies } from 'next/headers';
35
+ * import { applyToolbarOverrides } from '@unleash/toolbar/next/server';
36
+ *
37
+ * const cookieStore = await cookies();
38
+ * const definitions = await getDefinitions();
39
+ * const modified = applyToolbarOverrides(definitions, cookieStore);
40
+ * const { toggles } = evaluateFlags(modified, context);
41
+ * ```
42
+ */
43
+ export declare function applyToolbarOverrides(definitions: ClientFeaturesResponse, cookieStore: CookieStore): ClientFeaturesResponse;
44
+ /**
45
+ * Applies toolbar overrides to evaluated toggles.
46
+ *
47
+ * Alternative to applyToolbarOverrides() - use this if you already have
48
+ * evaluated toggles and want to apply overrides post-evaluation.
49
+ *
50
+ * @param toggles - Evaluated toggles array from evaluateFlags()
51
+ * @param cookieStore - Next.js cookie store from cookies()
52
+ * @returns Modified toggles with overrides applied
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * import { getDefinitions, evaluateFlags, flagsClient } from '@unleash/nextjs';
57
+ * import { cookies } from 'next/headers';
58
+ * import { applyToolbarOverridesToToggles } from '@unleash/toolbar/next/server';
59
+ *
60
+ * const definitions = await getDefinitions();
61
+ * const { toggles } = evaluateFlags(definitions, context);
62
+ * const modified = applyToolbarOverridesToToggles(toggles, await cookies());
63
+ * const flags = flagsClient(modified);
64
+ * ```
65
+ */
66
+ export declare function applyToolbarOverridesToToggles(toggles: IToggle[], cookieStore: CookieStore): IToggle[];
67
+ export {};
68
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/next/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAE7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,eAAO,MAAM,sBAAsB,0BAA0B,CAAC;AAE9D;;GAEG;AACH,UAAU,WAAW;IACnB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CAClD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,IAAI,CAQtF;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,WAAW,GAAG,YAAY,GAAG,IAAI,CAGxF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,sBAAsB,EACnC,WAAW,EAAE,WAAW,GACvB,sBAAsB,CAqFxB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,OAAO,EAAE,EAClB,WAAW,EAAE,WAAW,GACvB,OAAO,EAAE,CAgCX"}
@@ -0,0 +1,2 @@
1
+ const e="unleash-toolbar-state";function a(e){if(!e)return null;try{return JSON.parse(decodeURIComponent(e))}catch{return null}}function t(t){const n=t.get(e);return a(n?.value)}function n(e,a){const n=t(a);if(!n||!e)return e;const r=JSON.parse(JSON.stringify(e));r.features||(r.features=[]);const s=new Set(r.features.map(e=>e.name));r.features=r.features.map(e=>{const a=n.flags[e.name]?.override;return a?"flag"===a.type?{...e,enabled:a.value,strategies:a.value?e.strategies&&e.strategies.length>0?e.strategies:[{name:"default",parameters:{},constraints:[]}]:[]}:"variant"===a.type&&e.variants?{...e,enabled:!0,strategies:e.strategies&&e.strategies.length>0?e.strategies:[{name:"default",parameters:{},constraints:[]}],variants:e.variants.map(e=>({...e,weight:e.name===a.variantKey?1e3:0}))}:e:e});for(const[t,i]of Object.entries(n.flags))if(!s.has(t)&&i.override){const e=i.override;"flag"===e.type?r.features.push({name:t,enabled:e.value,strategies:e.value?[{name:"default",parameters:{},constraints:[]}]:[]}):"variant"===e.type&&r.features.push({name:t,enabled:!0,strategies:[{name:"default",parameters:{},constraints:[]}],variants:[{name:e.variantKey,weight:1e3}]})}return r}function r(e,a){const n=t(a);return n&&e?e.map(e=>{const a=n.flags[e.name]?.override;return a?"flag"===a.type?{...e,enabled:a.value,variant:a.value?e.variant:{name:"disabled",enabled:!1}}:"variant"===a.type?{...e,enabled:!0,variant:{name:a.variantKey,enabled:!0,payload:e.variant?.payload}}:e:e}):e}export{e as UNLEASH_TOOLBAR_COOKIE,n as applyToolbarOverrides,r as applyToolbarOverridesToToggles,t as getToolbarStateFromCookies,a as parseToolbarState};
2
+ //# sourceMappingURL=next-server.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next-server.es.js","sources":["../src/next/server.ts"],"sourcesContent":["/**\n * Server-side Next.js integration for Unleash Toolbar\n *\n * This module provides utilities for applying toolbar overrides in server components,\n * SSR, SSG, and App Router scenarios. It reads overrides from cookies to ensure\n * server-side flag evaluations respect client-side toolbar changes.\n *\n * @example App Router Server Component\n * ```tsx\n * import { cookies } from 'next/headers';\n * import { getDefinitions, evaluateFlags, flagsClient } from '@unleash/nextjs';\n * import { applyToolbarOverrides } from '@unleash/toolbar/next/server';\n *\n * export default async function ServerPage() {\n * const cookieStore = await cookies();\n * const definitions = await getDefinitions();\n * const modifiedDefinitions = applyToolbarOverrides(definitions, cookieStore);\n *\n * const { toggles } = evaluateFlags(modifiedDefinitions, {\n * sessionId: 'session-123'\n * });\n *\n * const flags = flagsClient(toggles);\n * const isEnabled = flags.isEnabled('my-flag');\n *\n * return <div>{isEnabled ? 'ON' : 'OFF'}</div>;\n * }\n * ```\n */\n\nimport type { IToggle } from '@unleash/nextjs';\nimport type { ClientFeaturesResponse } from 'unleash-client';\nimport type { VariantDefinition } from 'unleash-client/lib/variant';\nimport type { ToolbarState } from '../types';\n\nexport const UNLEASH_TOOLBAR_COOKIE = 'unleash-toolbar-state';\n\n/**\n * Cookie store interface compatible with Next.js cookies()\n */\ninterface CookieStore {\n get(name: string): { value: string } | undefined;\n}\n\n/**\n * Parses toolbar state from cookie value\n */\nexport function parseToolbarState(cookieValue: string | undefined): ToolbarState | null {\n if (!cookieValue) return null;\n\n try {\n return JSON.parse(decodeURIComponent(cookieValue));\n } catch {\n return null;\n }\n}\n\n/**\n * Reads toolbar state from Next.js cookie store\n */\nexport function getToolbarStateFromCookies(cookieStore: CookieStore): ToolbarState | null {\n const cookie = cookieStore.get(UNLEASH_TOOLBAR_COOKIE);\n return parseToolbarState(cookie?.value);\n}\n\n/**\n * Applies toolbar overrides to Unleash feature flag definitions.\n *\n * Use this with getDefinitions() from @unleash/nextjs before evaluating flags\n * to ensure server-side evaluations respect toolbar overrides.\n *\n * @param definitions - Feature flag definitions from getDefinitions()\n * @param cookieStore - Next.js cookie store from cookies()\n * @returns Modified definitions with overrides applied\n *\n * @example\n * ```tsx\n * import { getDefinitions, evaluateFlags } from '@unleash/nextjs';\n * import { cookies } from 'next/headers';\n * import { applyToolbarOverrides } from '@unleash/toolbar/next/server';\n *\n * const cookieStore = await cookies();\n * const definitions = await getDefinitions();\n * const modified = applyToolbarOverrides(definitions, cookieStore);\n * const { toggles } = evaluateFlags(modified, context);\n * ```\n */\nexport function applyToolbarOverrides(\n definitions: ClientFeaturesResponse,\n cookieStore: CookieStore,\n): ClientFeaturesResponse {\n const state = getToolbarStateFromCookies(cookieStore);\n\n if (!state || !definitions) return definitions;\n\n // Clone definitions to avoid mutation\n const modified = JSON.parse(JSON.stringify(definitions)) as ClientFeaturesResponse;\n\n if (!modified.features) {\n modified.features = [];\n }\n\n // Track which features we've seen\n const existingFeatureNames = new Set(modified.features.map((f) => f.name));\n\n // Apply overrides to existing features\n modified.features = modified.features.map((feature) => {\n const override = state.flags[feature.name]?.override;\n\n if (!override) return feature;\n\n if (override.type === 'flag') {\n // Force flag to enabled/disabled\n return {\n ...feature,\n enabled: override.value,\n // Override strategies to force the value\n // When enabling, ensure we have at least a default strategy\n strategies: override.value\n ? feature.strategies && feature.strategies.length > 0\n ? feature.strategies\n : [{ name: 'default', parameters: {}, constraints: [] }]\n : [],\n };\n }\n\n if (override.type === 'variant' && feature.variants) {\n // Force specific variant\n return {\n ...feature,\n enabled: true,\n strategies:\n feature.strategies && feature.strategies.length > 0\n ? feature.strategies\n : [{ name: 'default', parameters: {}, constraints: [] }],\n variants: feature.variants.map((v: VariantDefinition) => ({\n ...v,\n weight: v.name === override.variantKey ? 1000 : 0,\n })),\n };\n }\n\n return feature;\n });\n\n // Add features that don't exist in the API but have overrides\n for (const [flagName, metadata] of Object.entries(state.flags)) {\n if (!existingFeatureNames.has(flagName) && metadata.override) {\n const override = metadata.override;\n\n if (override.type === 'flag') {\n // Add a new feature for the override\n modified.features.push({\n name: flagName,\n enabled: override.value,\n strategies: override.value ? [{ name: 'default', parameters: {}, constraints: [] }] : [],\n });\n } else if (override.type === 'variant') {\n // Add a new feature with a variant\n modified.features.push({\n name: flagName,\n enabled: true,\n strategies: [{ name: 'default', parameters: {}, constraints: [] }],\n variants: [\n {\n name: override.variantKey,\n weight: 1000,\n },\n ],\n });\n }\n }\n }\n\n return modified;\n}\n\n/**\n * Applies toolbar overrides to evaluated toggles.\n *\n * Alternative to applyToolbarOverrides() - use this if you already have\n * evaluated toggles and want to apply overrides post-evaluation.\n *\n * @param toggles - Evaluated toggles array from evaluateFlags()\n * @param cookieStore - Next.js cookie store from cookies()\n * @returns Modified toggles with overrides applied\n *\n * @example\n * ```tsx\n * import { getDefinitions, evaluateFlags, flagsClient } from '@unleash/nextjs';\n * import { cookies } from 'next/headers';\n * import { applyToolbarOverridesToToggles } from '@unleash/toolbar/next/server';\n *\n * const definitions = await getDefinitions();\n * const { toggles } = evaluateFlags(definitions, context);\n * const modified = applyToolbarOverridesToToggles(toggles, await cookies());\n * const flags = flagsClient(modified);\n * ```\n */\nexport function applyToolbarOverridesToToggles(\n toggles: IToggle[],\n cookieStore: CookieStore,\n): IToggle[] {\n const state = getToolbarStateFromCookies(cookieStore);\n\n if (!state || !toggles) return toggles;\n\n return toggles.map((toggle) => {\n const override = state.flags[toggle.name]?.override;\n\n if (!override) return toggle;\n\n if (override.type === 'flag') {\n return {\n ...toggle,\n enabled: override.value,\n variant: override.value ? toggle.variant : { name: 'disabled', enabled: false },\n };\n }\n\n if (override.type === 'variant') {\n return {\n ...toggle,\n enabled: true,\n variant: {\n name: override.variantKey,\n enabled: true,\n payload: toggle.variant?.payload,\n },\n };\n }\n\n return toggle;\n });\n}\n"],"names":["UNLEASH_TOOLBAR_COOKIE","parseToolbarState","cookieValue","JSON","parse","decodeURIComponent","getToolbarStateFromCookies","cookieStore","cookie","get","value","applyToolbarOverrides","definitions","state","modified","stringify","features","existingFeatureNames","Set","map","f","name","feature","override","flags","type","enabled","strategies","length","parameters","constraints","variants","v","weight","variantKey","flagName","metadata","Object","entries","has","push","applyToolbarOverridesToToggles","toggles","toggle","variant","payload"],"mappings":"AAmCO,MAAMA,EAAyB,wBAY/B,SAASC,EAAkBC,GAChC,IAAKA,EAAa,OAAO,KAEzB,IACE,OAAOC,KAAKC,MAAMC,mBAAmBH,GACvC,CAAA,MACE,OAAO,IACT,CACF,CAKO,SAASI,EAA2BC,GACzC,MAAMC,EAASD,EAAYE,IAAIT,GAC/B,OAAOC,EAAkBO,GAAQE,MACnC,CAwBO,SAASC,EACdC,EACAL,GAEA,MAAMM,EAAQP,EAA2BC,GAEzC,IAAKM,IAAUD,EAAa,OAAOA,EAGnC,MAAME,EAAWX,KAAKC,MAAMD,KAAKY,UAAUH,IAEtCE,EAASE,WACZF,EAASE,SAAW,IAItB,MAAMC,EAAuB,IAAIC,IAAIJ,EAASE,SAASG,IAAKC,GAAMA,EAAEC,OAGpEP,EAASE,SAAWF,EAASE,SAASG,IAAKG,IACzC,MAAMC,EAAWV,EAAMW,MAAMF,EAAQD,OAAOE,SAE5C,OAAKA,EAEiB,SAAlBA,EAASE,KAEJ,IACFH,EACHI,QAASH,EAASb,MAGlBiB,WAAYJ,EAASb,MACjBY,EAAQK,YAAcL,EAAQK,WAAWC,OAAS,EAChDN,EAAQK,WACR,CAAC,CAAEN,KAAM,UAAWQ,WAAY,GAAIC,YAAa,KACnD,IAIc,YAAlBP,EAASE,MAAsBH,EAAQS,SAElC,IACFT,EACHI,SAAS,EACTC,WACEL,EAAQK,YAAcL,EAAQK,WAAWC,OAAS,EAC9CN,EAAQK,WACR,CAAC,CAAEN,KAAM,UAAWQ,WAAY,CAAA,EAAIC,YAAa,KACvDC,SAAUT,EAAQS,SAASZ,IAAKa,IAAA,IAC3BA,EACHC,OAAQD,EAAEX,OAASE,EAASW,WAAa,IAAO,MAK/CZ,EAjCeA,IAqCxB,IAAA,MAAYa,EAAUC,KAAaC,OAAOC,QAAQzB,EAAMW,OACtD,IAAKP,EAAqBsB,IAAIJ,IAAaC,EAASb,SAAU,CAC5D,MAAMA,EAAWa,EAASb,SAEJ,SAAlBA,EAASE,KAEXX,EAASE,SAASwB,KAAK,CACrBnB,KAAMc,EACNT,QAASH,EAASb,MAClBiB,WAAYJ,EAASb,MAAQ,CAAC,CAAEW,KAAM,UAAWQ,WAAY,CAAA,EAAIC,YAAa,KAAQ,KAE7D,YAAlBP,EAASE,MAElBX,EAASE,SAASwB,KAAK,CACrBnB,KAAMc,EACNT,SAAS,EACTC,WAAY,CAAC,CAAEN,KAAM,UAAWQ,WAAY,GAAIC,YAAa,KAC7DC,SAAU,CACR,CACEV,KAAME,EAASW,WACfD,OAAQ,OAKlB,CAGF,OAAOnB,CACT,CAwBO,SAAS2B,EACdC,EACAnC,GAEA,MAAMM,EAAQP,EAA2BC,GAEzC,OAAKM,GAAU6B,EAERA,EAAQvB,IAAKwB,IAClB,MAAMpB,EAAWV,EAAMW,MAAMmB,EAAOtB,OAAOE,SAE3C,OAAKA,EAEiB,SAAlBA,EAASE,KACJ,IACFkB,EACHjB,QAASH,EAASb,MAClBkC,QAASrB,EAASb,MAAQiC,EAAOC,QAAU,CAAEvB,KAAM,WAAYK,SAAS,IAItD,YAAlBH,EAASE,KACJ,IACFkB,EACHjB,SAAS,EACTkB,QAAS,CACPvB,KAAME,EAASW,WACfR,SAAS,EACTmB,QAASF,EAAOC,SAASC,UAKxBF,EAtBeA,IALOD,CA6BjC"}
@@ -0,0 +1,4 @@
1
+ "use client";
2
+ export{useFlag,useFlags,useFlagsStatus,useUnleashClient,useUnleashContext,useVariant}from"@unleash/proxy-client-react";import{jsx as e}from"react/jsx-runtime";import{UnleashToolbarProvider as o}from"./react.es.js";function t(t){const r={...t.toolbarOptions,enableCookieSync:!0};/* @__PURE__ */
3
+ return e(o,{...t,toolbarOptions:r})}export{t as UnleashToolbarProvider};
4
+ //# sourceMappingURL=next.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.es.js","sources":["../src/next/client.tsx"],"sourcesContent":["/**\n * Client-side Next.js integration for Unleash Toolbar\n * \n * This module provides the client-side provider for Next.js applications.\n * Use this in client components marked with \"use client\" directive.\n * Automatically enables cookie synchronization for server-side rendering support.\n */\n'use client';\n\nimport { UnleashToolbarProvider as BaseUnleashToolbarProvider } from '../react/index';\nimport type { InitToolbarOptions } from '../types';\nimport React from 'react';\n\n/**\n * Next.js-specific provider that wraps the official Unleash React SDK's FlagProvider\n * and automatically enables cookie synchronization for server-side rendering support.\n * \n * This ensures toolbar state is available in server components via getToolbarStateFromCookies().\n */\nexport function UnleashToolbarProvider(props: React.ComponentProps<typeof BaseUnleashToolbarProvider>) {\n // Automatically enable cookie sync for Next.js SSR support\n const toolbarOptions: InitToolbarOptions = {\n ...props.toolbarOptions,\n enableCookieSync: true,\n };\n\n return <BaseUnleashToolbarProvider {...props} toolbarOptions={toolbarOptions} />;\n}\n\nexport * from '../react/hooks';\n\n"],"names":["UnleashToolbarProvider","props","toolbarOptions","enableCookieSync","jsx","BaseUnleashToolbarProvider"],"mappings":"sNAmBO,SAASA,EAAuBC,GAErC,MAAMC,EAAqC,IACtCD,EAAMC,eACTC,kBAAkB;AAGpB,OAAOC,EAACC,EAAA,IAA+BJ,EAAOC,kBAChD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../src/react/__tests__/index.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Re-export hooks from the official Unleash React SDK
3
+ * This allows users to import all React hooks from the toolbar package:
4
+ *
5
+ * import { useFlag, useVariant } from '@unleash/toolbar/react';
6
+ */
7
+ export { useFlag, useFlags, useFlagsStatus, useUnleashClient, useUnleashContext, useVariant, } from '@unleash/proxy-client-react';
8
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/react/hooks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,OAAO,EACP,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,GACX,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,94 @@
1
+ import { UnleashClient, IConfig } from 'unleash-proxy-client';
2
+ import { InitToolbarOptions } from '../types';
3
+ import { default as React } from 'react';
4
+ /**
5
+ * Base props shared by both config and client variants
6
+ */
7
+ interface BaseToolbarProviderProps {
8
+ /**
9
+ * The official FlagProvider component from @unleash/proxy-client-react
10
+ * Optional - defaults to the standard FlagProvider
11
+ */
12
+ FlagProvider?: React.ComponentType<any>;
13
+ /**
14
+ * Optional toolbar configuration
15
+ * Set to undefined to disable toolbar in production
16
+ */
17
+ toolbarOptions?: InitToolbarOptions;
18
+ /**
19
+ * Whether to automatically start the client
20
+ * Defaults to true
21
+ */
22
+ startClient?: boolean;
23
+ /**
24
+ * Children components
25
+ */
26
+ children: React.ReactNode;
27
+ /**
28
+ * Any additional props to pass to the official FlagProvider
29
+ */
30
+ [key: string]: any;
31
+ }
32
+ /**
33
+ * Props when using config-based initialization
34
+ */
35
+ interface ConfigBasedProps extends BaseToolbarProviderProps {
36
+ /**
37
+ * Unleash SDK configuration object (same as official React SDK)
38
+ */
39
+ config: IConfig;
40
+ client?: never;
41
+ }
42
+ /**
43
+ * Props when using pre-instantiated client
44
+ */
45
+ interface ClientBasedProps extends BaseToolbarProviderProps {
46
+ /**
47
+ * Pre-instantiated Unleash client (will be wrapped with toolbar)
48
+ */
49
+ client: UnleashClient;
50
+ config?: never;
51
+ }
52
+ /**
53
+ * Props for the UnleashToolbarProvider - either config OR client must be provided
54
+ */
55
+ type UnleashToolbarProviderProps = ConfigBasedProps | ClientBasedProps;
56
+ /**
57
+ * Higher-order provider that wraps the official Unleash React SDK's FlagProvider
58
+ * and initializes the toolbar with a wrapped client.
59
+ *
60
+ * Simple usage (no imports needed):
61
+ * ```tsx
62
+ * import { UnleashToolbarProvider } from '@unleash/toolbar/react';
63
+ *
64
+ * function App() {
65
+ * return (
66
+ * <UnleashToolbarProvider
67
+ * config={{
68
+ * url: 'https://your-unleash-instance.com/api/frontend',
69
+ * clientKey: 'your-client-key',
70
+ * appName: 'my-app'
71
+ * }}
72
+ * toolbarOptions={{ themePreset: 'dark' }}
73
+ * >
74
+ * <YourApp />
75
+ * </UnleashToolbarProvider>
76
+ * );
77
+ * }
78
+ * ```
79
+ *
80
+ * Advanced usage with custom FlagProvider:
81
+ * ```tsx
82
+ * import { FlagProvider } from '@unleash/proxy-client-react';
83
+ *
84
+ * <UnleashToolbarProvider
85
+ * FlagProvider={FlagProvider}
86
+ * config={{ ... }}
87
+ * >
88
+ * <YourApp />
89
+ * </UnleashToolbarProvider>
90
+ * ```
91
+ */
92
+ export declare function UnleashToolbarProvider({ FlagProvider, config, client, toolbarOptions, startClient, children, ...flagProviderProps }: UnleashToolbarProviderProps): import("react/jsx-runtime").JSX.Element;
93
+ export {};
94
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAGnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,KAA4B,MAAM,OAAO,CAAC;AAEjD;;GAEG;AACH,UAAU,wBAAwB;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IAExC;;;OAGG;IACH,cAAc,CAAC,EAAE,kBAAkB,CAAC;IAEpC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAE1B;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,UAAU,gBAAiB,SAAQ,wBAAwB;IACzD;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB;AAED;;GAEG;AACH,UAAU,gBAAiB,SAAQ,wBAAwB;IACzD;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB;AAED;;GAEG;AACH,KAAK,2BAA2B,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,YAAkC,EAClC,MAAM,EACN,MAAM,EACN,cAAmB,EACnB,WAAkB,EAClB,QAAQ,EACR,GAAG,iBAAiB,EACrB,EAAE,2BAA2B,2CAmF7B"}
@@ -0,0 +1,2 @@
1
+ import{jsx as r}from"react/jsx-runtime";import{UnleashClient as e}from"unleash-proxy-client";import{FlagProvider as o}from"@unleash/proxy-client-react";import{ToolbarStateManager as n,wrapUnleashClient as t,UnleashToolbar as i}from"./index.es.js";import{useRef as l,useEffect as c}from"react";function u({FlagProvider:u=o,config:a,client:s,toolbarOptions:d={},startClient:f=!0,children:p,...h}){if(!a&&!s)throw new Error('UnleashToolbarProvider: Either "config" or "client" prop must be provided');if(a&&s)throw new Error('UnleashToolbarProvider: Provide either "config" or "client" prop, not both');const b=l(null),m=l(null),w=l(null);if(null===w.current){const r=s||new e(a);if(void 0!==d){const e=new n(d.storageMode||"local",d.storageKey||"unleash-toolbar-state",d.sortAlphabetically||!1);m.current=e,d.enableCookieSync&&e.enableCookieSync(),w.current=t(r,e)}else w.current=r}return c(()=>{if(void 0!==d&&!b.current&&m.current&&!w.current?.__toolbar){const r=new i(m.current,w.current,d);w.current.__toolbar=r,b.current=r,"undefined"!=typeof window&&(window.unleashToolbar=r)}return()=>{b.current&&"function"==typeof b.current.destroy&&(b.current.destroy(),b.current=null)}},[]),/* @__PURE__ */r(u,{unleashClient:w.current,startClient:f,...h,children:p})}export{u as UnleashToolbarProvider};
2
+ //# sourceMappingURL=react.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.es.js","sources":["../src/react/index.tsx"],"sourcesContent":["import { UnleashClient, type IConfig } from 'unleash-proxy-client';\nimport { FlagProvider as DefaultFlagProvider } from '@unleash/proxy-client-react';\nimport { wrapUnleashClient, ToolbarStateManager, UnleashToolbar } from '../index';\nimport type { InitToolbarOptions } from '../types';\nimport React, { useRef, useEffect } from 'react';\n\n/**\n * Base props shared by both config and client variants\n */\ninterface BaseToolbarProviderProps {\n /**\n * The official FlagProvider component from @unleash/proxy-client-react\n * Optional - defaults to the standard FlagProvider\n */\n FlagProvider?: React.ComponentType<any>;\n \n /**\n * Optional toolbar configuration\n * Set to undefined to disable toolbar in production\n */\n toolbarOptions?: InitToolbarOptions;\n \n /**\n * Whether to automatically start the client\n * Defaults to true\n */\n startClient?: boolean;\n \n /**\n * Children components\n */\n children: React.ReactNode;\n \n /**\n * Any additional props to pass to the official FlagProvider\n */\n [key: string]: any;\n}\n\n/**\n * Props when using config-based initialization\n */\ninterface ConfigBasedProps extends BaseToolbarProviderProps {\n /**\n * Unleash SDK configuration object (same as official React SDK)\n */\n config: IConfig;\n client?: never;\n}\n\n/**\n * Props when using pre-instantiated client\n */\ninterface ClientBasedProps extends BaseToolbarProviderProps {\n /**\n * Pre-instantiated Unleash client (will be wrapped with toolbar)\n */\n client: UnleashClient;\n config?: never;\n}\n\n/**\n * Props for the UnleashToolbarProvider - either config OR client must be provided\n */\ntype UnleashToolbarProviderProps = ConfigBasedProps | ClientBasedProps;\n\n/**\n * Higher-order provider that wraps the official Unleash React SDK's FlagProvider\n * and initializes the toolbar with a wrapped client.\n * \n * Simple usage (no imports needed):\n * ```tsx\n * import { UnleashToolbarProvider } from '@unleash/toolbar/react';\n * \n * function App() {\n * return (\n * <UnleashToolbarProvider\n * config={{\n * url: 'https://your-unleash-instance.com/api/frontend',\n * clientKey: 'your-client-key',\n * appName: 'my-app'\n * }}\n * toolbarOptions={{ themePreset: 'dark' }}\n * >\n * <YourApp />\n * </UnleashToolbarProvider>\n * );\n * }\n * ```\n * \n * Advanced usage with custom FlagProvider:\n * ```tsx\n * import { FlagProvider } from '@unleash/proxy-client-react';\n * \n * <UnleashToolbarProvider\n * FlagProvider={FlagProvider}\n * config={{ ... }}\n * >\n * <YourApp />\n * </UnleashToolbarProvider>\n * ```\n */\nexport function UnleashToolbarProvider({\n FlagProvider = DefaultFlagProvider,\n config,\n client,\n toolbarOptions = {},\n startClient = true,\n children,\n ...flagProviderProps\n}: UnleashToolbarProviderProps) {\n // Validate that either config or client is provided, not both\n if (!config && !client) {\n throw new Error(\n 'UnleashToolbarProvider: Either \"config\" or \"client\" prop must be provided'\n );\n }\n if (config && client) {\n throw new Error(\n 'UnleashToolbarProvider: Provide either \"config\" or \"client\" prop, not both'\n );\n }\n\n const toolbarRef = useRef<any>(null);\n const stateManagerRef = useRef<any>(null);\n \n // Wrap client synchronously (idempotent, captures flag evaluations)\n // This ensures the wrapper intercepts all flag evaluations from the first render\n const wrappedClientRef = useRef<UnleashClient | null>(null);\n \n if (wrappedClientRef.current === null) {\n const unleashClient = client || new UnleashClient(config!);\n \n if (toolbarOptions !== undefined) {\n // Create state manager that will be shared between wrapper and toolbar\n const storageMode = toolbarOptions.storageMode || 'local';\n const storageKey = toolbarOptions.storageKey || 'unleash-toolbar-state';\n const sortAlphabetically = toolbarOptions.sortAlphabetically || false;\n const stateManager = new ToolbarStateManager(storageMode, storageKey, sortAlphabetically);\n stateManagerRef.current = stateManager;\n\n if (toolbarOptions.enableCookieSync) {\n stateManager.enableCookieSync();\n }\n \n // Wrap client synchronously with shared state manager\n wrappedClientRef.current = wrapUnleashClient(unleashClient, stateManager);\n } else {\n // no toolbar - just use original client\n wrappedClientRef.current = unleashClient;\n }\n }\n \n // Create toolbar UI in useEffect (StrictMode-safe)\n useEffect(() => {\n // Only create toolbar if:\n // 1. toolbarOptions is defined\n // 2. No toolbar ref yet\n // 3. State manager exists\n // 4. Wrapped client doesn't already have a toolbar (prevents StrictMode duplicates)\n if (toolbarOptions !== undefined && \n !toolbarRef.current && \n stateManagerRef.current &&\n !(wrappedClientRef.current as any)?.__toolbar) {\n // Create toolbar with the same state manager used for wrapping\n const toolbar = new UnleashToolbar(stateManagerRef.current, wrappedClientRef.current as any, toolbarOptions);\n (wrappedClientRef.current as any).__toolbar = toolbar;\n toolbarRef.current = toolbar;\n \n // Expose globally\n if (typeof window !== 'undefined') {\n (window as any).unleashToolbar = toolbar;\n }\n }\n \n // Cleanup toolbar on unmount\n return () => {\n if (toolbarRef.current && typeof toolbarRef.current.destroy === 'function') {\n toolbarRef.current.destroy();\n toolbarRef.current = null;\n }\n };\n }, []);\n\n return (\n <FlagProvider\n unleashClient={wrappedClientRef.current}\n startClient={startClient}\n {...flagProviderProps}\n >\n {children}\n </FlagProvider>\n );\n}\n"],"names":["UnleashToolbarProvider","FlagProvider","DefaultFlagProvider","config","client","toolbarOptions","startClient","children","flagProviderProps","Error","toolbarRef","useRef","stateManagerRef","wrappedClientRef","current","unleashClient","UnleashClient","stateManager","ToolbarStateManager","storageMode","storageKey","sortAlphabetically","enableCookieSync","wrapUnleashClient","useEffect","__toolbar","toolbar","UnleashToolbar","window","unleashToolbar","destroy","jsx"],"mappings":"qSAsGO,SAASA,GAAuBC,aACrCA,EAAeC,EAAAC,OACfA,EAAAC,OACAA,EAAAC,eACAA,EAAiB,CAAA,EAACC,YAClBA,GAAc,EAAAC,SACdA,KACGC,IAGH,IAAKL,IAAWC,EACd,MAAM,IAAIK,MACR,6EAGJ,GAAIN,GAAUC,EACZ,MAAM,IAAIK,MACR,8EAIJ,MAAMC,EAAaC,EAAY,MACzBC,EAAkBD,EAAY,MAI9BE,EAAmBF,EAA6B,MAEtD,GAAiC,OAA7BE,EAAiBC,QAAkB,CACrC,MAAMC,EAAgBX,GAAU,IAAIY,EAAcb,GAElD,QAAuB,IAAnBE,EAA8B,CAEhC,MAGMY,EAAe,IAAIC,EAHLb,EAAec,aAAe,QAC/Bd,EAAee,YAAc,wBACrBf,EAAegB,qBAAsB,GAEhET,EAAgBE,QAAUG,EAEtBZ,EAAeiB,kBACjBL,EAAaK,mBAIfT,EAAiBC,QAAUS,EAAkBR,EAAeE,EAC9D,MAEEJ,EAAiBC,QAAUC,CAE/B,CAiCA,OA9BAS,EAAU,KAMR,QAAuB,IAAnBnB,IACCK,EAAWI,SACZF,EAAgBE,UACdD,EAAiBC,SAAiBW,UAAW,CAEjD,MAAMC,EAAU,IAAIC,EAAef,EAAgBE,QAASD,EAAiBC,QAAgBT,GAC5FQ,EAAiBC,QAAgBW,UAAYC,EAC9ChB,EAAWI,QAAUY,EAGC,oBAAXE,SACRA,OAAeC,eAAiBH,EAErC,CAGA,MAAO,KACDhB,EAAWI,SAAiD,mBAA/BJ,EAAWI,QAAQgB,UAClDpB,EAAWI,QAAQgB,UACnBpB,EAAWI,QAAU,QAGxB,mBAGDiB,EAAC9B,EAAA,CACCc,cAAeF,EAAiBC,QAChCR,iBACIE,EAEHD,YAGP"}
@@ -0,0 +1,90 @@
1
+ import { FlagMetadata, FlagOverride, FlagValue, StorageMode, ToolbarEventListener, ToolbarState, UnleashContext, UnleashVariant } from './types';
2
+ /**
3
+ * Core state manager for the toolbar
4
+ */
5
+ export declare class ToolbarStateManager {
6
+ private state;
7
+ private eventEmitter;
8
+ private storage;
9
+ private sortAlphabetically;
10
+ constructor(storageMode?: StorageMode, storageKey?: string, sortAlphabetically?: boolean);
11
+ private getInitialState;
12
+ private persist;
13
+ /**
14
+ * Apply override to a default value (same logic as wrapper)
15
+ */
16
+ private applyOverride;
17
+ /**
18
+ * Re-evaluate all known flags (used when SDK configuration updates)
19
+ */
20
+ reEvaluateAllFlags(evaluator: (flagName: string) => {
21
+ defaultValue: FlagValue;
22
+ effectiveValue: FlagValue;
23
+ }): void;
24
+ /**
25
+ * Get the current state (immutable copy)
26
+ */
27
+ getState(): ToolbarState;
28
+ /**
29
+ * Subscribe to state change events
30
+ */
31
+ subscribe(listener: ToolbarEventListener): () => void;
32
+ /**
33
+ * Record a flag evaluation (updates state without emitting events)
34
+ */
35
+ recordEvaluation(name: string, flagType: 'flag' | 'variant', defaultValue: boolean | UnleashVariant | null, effectiveValue: boolean | UnleashVariant | null, context: UnleashContext): void;
36
+ /**
37
+ * Set or clear a flag override
38
+ */
39
+ setFlagOverride(name: string, override: FlagOverride | null): void;
40
+ /**
41
+ * Get flag override
42
+ */
43
+ getFlagOverride(name: string): FlagOverride | null;
44
+ /**
45
+ * Set context overrides
46
+ */
47
+ setContextOverride(context: Partial<UnleashContext>): void;
48
+ /**
49
+ * Remove a specific context override field
50
+ */
51
+ removeContextOverride(fieldName: keyof UnleashContext): void;
52
+ /**
53
+ * Get merged context (original + overrides)
54
+ */
55
+ getMergedContext(baseContext?: UnleashContext): UnleashContext;
56
+ /**
57
+ * Reset all flag overrides
58
+ */
59
+ resetOverrides(): void;
60
+ /**
61
+ * Reset context overrides
62
+ */
63
+ resetContextOverrides(): void;
64
+ /**
65
+ * Set toolbar visibility state
66
+ */
67
+ setVisibility(isVisible: boolean): void;
68
+ /**
69
+ * Get toolbar visibility state
70
+ */
71
+ getVisibility(): boolean | undefined;
72
+ /**
73
+ * Clear all persisted data
74
+ */
75
+ clearPersistence(): void;
76
+ /**
77
+ * Get all flag names in evaluation order (insertion order) or alphabetically
78
+ */
79
+ getFlagNames(): string[];
80
+ /**
81
+ * Get metadata for a specific flag
82
+ */
83
+ getFlagMetadata(name: string): FlagMetadata | null;
84
+ /**
85
+ * Enable cookie synchronization for server-side rendering support
86
+ * Should be called by Next.js integration when SSR is needed
87
+ */
88
+ enableCookieSync(): void;
89
+ }
90
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,WAAW,EAEX,oBAAoB,EACpB,YAAY,EACZ,cAAc,EACd,cAAc,EACf,MAAM,SAAS,CAAC;AA2KjB;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,kBAAkB,CAAU;gBAGlC,WAAW,GAAE,WAAqB,EAClC,UAAU,GAAE,MAAgC,EAC5C,kBAAkB,GAAE,OAAe;IAWrC,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,OAAO;IAIf;;OAEG;IACH,OAAO,CAAC,aAAa;IA+BrB;;OAEG;IACH,kBAAkB,CAChB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK;QAAE,YAAY,EAAE,SAAS,CAAC;QAAC,cAAc,EAAE,SAAS,CAAA;KAAE,GACtF,IAAI;IA8BP;;OAEG;IACH,QAAQ,IAAI,YAAY;IAIxB;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI;IAIrD;;OAEG;IACH,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,YAAY,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,EAC7C,cAAc,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,EAC/C,OAAO,EAAE,cAAc,GACtB,IAAI;IAuBP;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI;IA6BlE;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAIlD;;OAEG;IACH,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI;IAc1D;;OAEG;IACH,qBAAqB,CAAC,SAAS,EAAE,MAAM,cAAc,GAAG,IAAI;IAc5D;;OAEG;IACH,gBAAgB,CAAC,WAAW,GAAE,cAAmB,GAAG,cAAc;IAWlE;;OAEG;IACH,cAAc,IAAI,IAAI;IAoBtB;;OAEG;IACH,qBAAqB,IAAI,IAAI;IAW7B;;OAEG;IACH,aAAa,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI;IAKvC;;OAEG;IACH,aAAa,IAAI,OAAO,GAAG,SAAS;IAIpC;;OAEG;IACH,gBAAgB,IAAI,IAAI;IAIxB;;OAEG;IACH,YAAY,IAAI,MAAM,EAAE;IAKxB;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IAIlD;;;OAGG;IACH,gBAAgB,IAAI,IAAI;CAGzB"}
@@ -0,0 +1 @@
1
+ .unleash-toolbar-container{--ut-primary:#6c65e5;--ut-bg:#fff;--ut-text:#1f2021;--ut-border:#e1e1e3;--ut-hover:#f5f6fa;--ut-success:#00b894;--ut-danger:#d63031;--ut-warning:#fdcb6e;--ut-shadow:rgba(0,0,0,.1);--ut-font:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu",sans-serif;all:initial;position:fixed;z-index:999998}.unleash-toolbar-container.ut-theme-dark{--ut-primary:#4c4992;--ut-bg:#1a1923;--ut-text:#eeeefc;--ut-border:#39384c;--ut-hover:#3d4446;--ut-success:#00b894;--ut-danger:#ff7675;--ut-warning:#fdcb6e;--ut-shadow:rgba(0,0,0,.3)}.unleash-toolbar button,.unleash-toolbar input,.unleash-toolbar-container{box-sizing:border-box;font-family:inherit;letter-spacing:normal;line-height:inherit;margin:0;text-transform:none;word-spacing:normal}.unleash-toolbar input{background:var(--ut-bg);color:var(--ut-text);margin:0}.unleash-toolbar img{border:none;max-width:none;vertical-align:middle}.unleash-toolbar img,.unleash-toolbar svg{display:inline-block;height:auto;max-width:none}.unleash-toolbar-container.position-top-left{left:20px;top:20px}.unleash-toolbar-container.position-top-right{right:20px;top:20px}.unleash-toolbar-container.position-bottom-left{bottom:20px;left:20px}.unleash-toolbar-container.position-bottom-right{bottom:20px;right:20px}.unleash-toolbar-container.position-left{left:20px;top:50%;transform:translateY(-50%)}.unleash-toolbar-container.position-right{right:20px;top:50%;transform:translateY(-50%)}.unleash-toolbar-container .ut-toggle{all:initial;align-items:center;background:var(--ut-primary);border:none;border-radius:50%;box-shadow:0 4px 12px var(--ut-shadow);cursor:pointer;display:flex;height:48px;justify-content:center;padding:0;position:relative;transition:all .3s ease;width:48px}.unleash-toolbar-container .ut-toggle:hover{box-shadow:0 6px 16px rgba(0,0,0,.2);transform:scale(1.1)}.unleash-toolbar-container .ut-toggle img{height:28px;object-fit:contain;width:28px}.unleash-toolbar-container .unleash-toolbar{all:initial;background:var(--ut-bg);border:1px solid var(--ut-border);border-radius:8px;box-shadow:0 4px 12px var(--ut-shadow);box-sizing:border-box;color:var(--ut-text);display:flex;flex-direction:column;font-family:var(--ut-font);font-size:14px;height:700px;line-height:1.5;max-height:85vh;min-height:400px;overflow:hidden;position:relative;width:400px}.unleash-toolbar-container .unleash-toolbar *{box-sizing:border-box}.unleash-toolbar .ut-header{align-items:center;background:var(--ut-primary);border-bottom:1px solid var(--ut-border);color:#fff;display:flex;flex-shrink:0;gap:12px;justify-content:space-between;padding:12px 20px 12px 12px;position:relative}.unleash-toolbar .ut-title{align-items:center;display:flex;flex:1;gap:10px;min-width:0}.unleash-toolbar .ut-title img{flex-shrink:0;height:28px;object-fit:contain;width:28px}.unleash-toolbar .ut-title-main{font-size:14px;font-weight:600;line-height:1.3}.unleash-toolbar .ut-title-sub{font-size:11px;margin-top:1px;opacity:.9}.unleash-toolbar .ut-actions{align-items:center;display:flex;gap:4px}.unleash-toolbar .ut-btn-icon{align-items:center;background:hsla(0,0%,100%,.1);border:1px solid hsla(0,0%,100%,.15);border-radius:4px;color:#fff;cursor:pointer;display:flex;flex-shrink:0;font-size:16px;height:24px;justify-content:center;line-height:1;padding:0;transition:all .2s;width:24px}.unleash-toolbar .ut-btn-icon:hover{background:hsla(0,0%,100%,.2);border-color:hsla(0,0%,100%,.25)}.unleash-toolbar .ut-btn-small{background:hsla(0,0%,100%,.15);border:1px solid hsla(0,0%,100%,.2);color:#fff;white-space:nowrap}.unleash-toolbar .ut-btn-small:hover{background:hsla(0,0%,100%,.25);border-color:hsla(0,0%,100%,.3)}.unleash-toolbar .ut-btn{background:hsla(0,0%,100%,.2);border:none;border-radius:4px;color:#fff;cursor:pointer;font-family:inherit;font-size:12px;padding:6px 12px;transition:background .2s}.unleash-toolbar .ut-btn:hover{background:hsla(0,0%,100%,.3)}.unleash-toolbar .ut-btn-close{align-items:center;background-color:var(--ut-primary);border:none;color:#fff;cursor:pointer;display:flex;flex-shrink:0;font-size:18px;height:24px;justify-content:center;line-height:1;padding:0;transition:all .2s;width:24px}.unleash-toolbar .ut-btn-close:hover{background:rgba(0,0,0,.3);border-color:hsla(0,0%,100%,.2)}.unleash-toolbar .ut-tabs{background:var(--ut-bg);display:flex;flex-shrink:0}.unleash-toolbar .ut-tab{background:none;border:none;border-bottom:2px solid transparent;color:var(--ut-text);cursor:pointer;flex:1;font-family:inherit;font-size:13px;padding:12px;transition:all .2s}.unleash-toolbar .ut-tab:hover{background:var(--ut-hover)}.unleash-toolbar .ut-tab.active{border-bottom-color:var(--ut-primary);font-weight:600}.unleash-toolbar .ut-content{display:flex;flex:1;flex-direction:column;overflow-y:auto}.unleash-toolbar .ut-tab-header{align-items:center;background:var(--ut-bg);display:flex;flex-shrink:0;gap:12px;padding:12px 16px}.unleash-toolbar .ut-search-container{flex:1;min-width:0}.unleash-toolbar .ut-search-input{border:1px solid var(--ut-border);border-radius:6px;font-family:inherit;font-size:13px;padding:8px 12px;transition:border-color .2s;width:100%}.unleash-toolbar .ut-search-input:focus{border-color:var(--ut-primary);outline:none}.unleash-toolbar .ut-search-input::placeholder{color:var(--ut-text);opacity:.5}.unleash-toolbar .ut-tab-header .ut-btn{background:var(--ut-primary);border:none;border-radius:6px;color:#fff;cursor:pointer;flex-shrink:0;font-family:inherit;font-size:13px;font-weight:500;padding:8px 16px;transition:all .2s;white-space:nowrap}.unleash-toolbar .ut-tab-header .ut-btn:hover{box-shadow:0 2px 4px rgba(0,0,0,.1);opacity:.9;transform:translateY(-1px)}.unleash-toolbar .ut-flag-list{display:flex;flex:1;flex-direction:column;gap:8px;overflow-y:auto;padding:16px}.unleash-toolbar .ut-flag-item{align-items:center;background:var(--ut-bg);border:1px solid var(--ut-border);border-radius:6px;display:flex;gap:12px;min-height:48px;padding:10px 12px}.unleash-toolbar .ut-flag-main{flex:1;min-width:0}.unleash-toolbar .ut-flag-header{align-items:flex-start;display:flex;flex-direction:column;gap:6px}.unleash-toolbar .ut-flag-title-row{align-items:center;display:flex;flex-wrap:wrap;gap:6px}.unleash-toolbar .ut-flag-name{flex-shrink:0;font-size:13px;font-weight:600;word-break:break-word}.unleash-toolbar .ut-flag-meta{align-items:center;display:flex;flex-wrap:wrap;font-size:11px;gap:8px}.unleash-toolbar .ut-flag-default-value{align-items:center;color:#636e72;display:flex;font-size:11px;gap:4px}.unleash-toolbar .ut-override-indicator{align-items:center;color:var(--ut-primary);display:flex;font-weight:500;gap:4px}.unleash-toolbar .ut-eval-count{font-size:10px;opacity:.6}.unleash-toolbar .ut-flag-control{flex-shrink:0}.unleash-toolbar .ut-badge{border-radius:3px;font-size:10px;font-weight:600;padding:2px 6px}.unleash-toolbar .ut-badge-success{background:#d4edda;color:#155724}.unleash-toolbar .ut-badge-danger{background:#f8d7da;color:#721c24}.unleash-toolbar .ut-badge-default{background:#e2e3e5;color:#383d41}.unleash-toolbar .ut-toggle-group{background:var(--ut-hover);border:1px solid var(--ut-border);border-radius:5px;display:flex;gap:3px;padding:2px}.unleash-toolbar .ut-toggle-btn{background:transparent;border:none;border-radius:4px;color:var(--ut-text);cursor:pointer;font-family:inherit;font-size:11px;font-weight:500;min-width:36px;padding:4px 10px;transition:all .15s ease;white-space:nowrap}.unleash-toolbar .ut-toggle-btn:hover:not(.active){background:hsla(0,0%,100%,.5)}.unleash-toolbar .ut-toggle-btn.active{background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.1);color:var(--ut-primary);font-weight:600}.unleash-toolbar .ut-variant-control{display:flex;flex-direction:column;gap:6px;min-width:120px}.unleash-toolbar .ut-variant-override-toggle{align-items:center;border-radius:4px;cursor:pointer;display:flex;font-size:12px;gap:6px;padding:6px 10px;transition:background .15s}.unleash-toolbar .ut-variant-override-toggle:hover{background:var(--ut-hover)}.unleash-toolbar .ut-variant-override-toggle input[type=checkbox]{cursor:pointer}.unleash-toolbar .ut-input-small{border:1px solid var(--ut-border);border-radius:4px;font-family:inherit;font-size:11px;padding:4px 8px;width:100%}.unleash-toolbar .ut-input-small:focus{border-color:var(--ut-primary);outline:none}.unleash-toolbar .ut-btn-small{font-weight:500;padding:4px 10px;transition:all .15s}.unleash-toolbar .ut-btn-small.active{background:var(--ut-primary);border-color:var(--ut-primary);color:#fff}.unleash-toolbar .ut-override-control{display:flex;gap:8px;margin-top:8px}.unleash-toolbar .ut-btn-small{background:var(--ut-hover);border:1px solid var(--ut-border);border-radius:4px;color:var(--ut-text);cursor:pointer;font-family:inherit;font-size:11px;padding:6px 10px;transition:all .2s}.unleash-toolbar .ut-btn-small:hover{background:var(--ut-border)}.unleash-toolbar .ut-context-form{display:flex;flex:1;flex-direction:column;gap:12px;overflow-y:auto;padding:16px}.unleash-toolbar .ut-form-group{display:flex;flex-direction:column;gap:4px;position:relative}.unleash-toolbar .ut-label{align-items:center;color:var(--ut-text);display:flex;font-size:12px;font-weight:600;gap:6px}.unleash-toolbar .ut-readonly-label{font-size:11px;opacity:.6}.unleash-toolbar .ut-input-with-reset{display:block;position:relative;width:100%}.unleash-toolbar .ut-input-with-reset .ut-input{padding-right:32px;width:100%}.unleash-toolbar .ut-reset-field{align-items:center;background:var(--ut-hover);border:1px solid var(--ut-border);border-radius:4px;bottom:4px;color:var(--ut-text);cursor:pointer;display:flex;font-size:14px;justify-content:center;line-height:1;margin:auto 0;padding:0;position:absolute;right:4px;top:4px;transition:all .15s;width:24px}.unleash-toolbar .ut-reset-field:hover{background:var(--ut-border);border-color:var(--ut-primary);color:var(--ut-primary)}.unleash-toolbar .ut-property-actions{display:flex;flex-shrink:0;gap:4px}.unleash-toolbar .ut-btn-remove{background:var(--ut-hover);border:1px solid var(--ut-border);border-radius:4px;color:#d63031;cursor:pointer;flex-shrink:0;font-size:16px;font-weight:700;height:32px;line-height:1;padding:0;transition:all .15s;width:24px}.unleash-toolbar .ut-btn-remove:hover{background:#fee;border-color:#d63031;color:#d63031}.unleash-toolbar .ut-input{border:1px solid var(--ut-border);border-radius:4px;font-family:inherit;font-size:13px;padding:8px 12px}.unleash-toolbar .ut-input:focus{border-color:var(--ut-primary);outline:none}.unleash-toolbar .ut-input-readonly{cursor:not-allowed;opacity:.7}.unleash-toolbar .ut-properties{display:flex;flex-direction:column;gap:8px}.unleash-toolbar .ut-property-row{align-items:center;display:flex;gap:8px;min-width:0}.unleash-toolbar .ut-property-key{background:var(--ut-bg-secondary);border-radius:4px;color:var(--ut-text);flex:0 0 auto;font-size:13px;max-width:200px;min-width:120px;opacity:.8;overflow:hidden;padding:8px 12px;text-overflow:ellipsis;white-space:nowrap}.unleash-toolbar .ut-property-row .ut-input-with-reset{flex:1;min-width:0}.unleash-toolbar .ut-empty{color:#636e72;font-size:13px;padding:40px 20px;text-align:center}.unleash-toolbar .ut-empty-properties{color:var(--ut-text);font-size:12px;margin-bottom:8px}.unleash-toolbar .ut-stats{display:grid;gap:8px;grid-template-columns:repeat(2,1fr);margin-bottom:16px}.unleash-toolbar .ut-stat-card{background:var(--ut-hover);border-radius:6px;padding:12px;text-align:center}.unleash-toolbar .ut-stat-value{color:var(--ut-primary);font-size:24px;font-weight:700}.unleash-toolbar .ut-stat-label{color:#636e72;font-size:11px;margin-top:4px}.unleash-toolbar .ut-content::-webkit-scrollbar{width:10px}.unleash-toolbar .ut-content::-webkit-scrollbar-track{background:var(--ut-hover);border-radius:5px}.unleash-toolbar .ut-content::-webkit-scrollbar-thumb{background:var(--ut-primary);border:2px solid var(--ut-bg);border-radius:5px}.unleash-toolbar .ut-content::-webkit-scrollbar-thumb:hover{background:var(--ut-primary);opacity:.8}.unleash-toolbar .ut-flag-list::-webkit-scrollbar{width:10px}.unleash-toolbar .ut-flag-list::-webkit-scrollbar-track{background:var(--ut-hover);border-radius:5px}.unleash-toolbar .ut-flag-list::-webkit-scrollbar-thumb{background:var(--ut-primary);border:2px solid var(--ut-bg);border-radius:5px}.unleash-toolbar .ut-flag-list::-webkit-scrollbar-thumb:hover{background:var(--ut-primary);opacity:.8}