UNPKG

@andreaswissel/uiflow

Version:

Adaptive UI density management library with progressive disclosure, element dependencies, A/B testing, and intelligent behavior-based adaptation

2 lines (1 loc) โ€ข 31.3 kB
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).UIFlow={})}(this,(function(e){"use strict";class t{constructor(e={}){this.config=e,this.ready=!1}async initialize(){this.ready=!0}isReady(){return this.ready}async pushData(e,t){throw new Error("pushData must be implemented by subclasses")}async pullData(e){throw new Error("pullData must be implemented by subclasses")}async trackEvent(e,t){console.log(`Tracking event for ${e}:`,t)}destroy(){this.ready=!1}}class i extends t{constructor(e={}){if(super(e),this.endpoint=e.endpoint,!this.endpoint)throw new Error("API endpoint is required")}async initialize(){try{(await fetch(`${this.endpoint}/health`,{method:"GET",headers:{"Content-Type":"application/json"}})).ok||console.warn("UIFlow API health check failed, but continuing...")}catch(e){console.warn("UIFlow API not accessible:",e.message)}this.ready=!0}async pushData(e,t){if(!this.isReady())throw new Error("API data source not ready");try{const i=await fetch(`${this.endpoint}/users/${e}/ui-density`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({...t,source:"uiflow-api",timestamp:Date.now()})});if(!i.ok)throw new Error(`API push failed: ${i.status}`);return await i.json()}catch(e){throw console.error("UIFlow API push error:",e),e}}async pullData(e){if(!this.isReady())throw new Error("API data source not ready");try{const t=await fetch(`${this.endpoint}/users/${e}/ui-density`,{method:"GET",headers:{"Content-Type":"application/json"}});if(!t.ok){if(404===t.status)return{areas:{},overrides:{},usageHistory:[]};throw new Error(`API pull failed: ${t.status}`)}return await t.json()}catch(e){throw console.error("UIFlow API pull error:",e),e}}async trackEvent(e,t){if(this.isReady())try{await fetch(`${this.endpoint}/events`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userId:e,event:"ui_interaction",properties:{...t,source:"uiflow"},timestamp:Date.now()})})}catch(e){console.warn("UIFlow API event tracking failed:",e)}}}class s extends t{constructor(e={}){if(super(e),this.writeKey=e.writeKey,this.trackingPlan=e.trackingPlan||"uiflow",this.useUserProperties=!1!==e.useUserProperties,this.analytics=null,!this.writeKey)throw new Error("Segment write key is required")}async initialize(){window.analytics||await this.loadSegmentScript(),this.analytics=window.analytics,this.analytics.initialized||this.analytics.load(this.writeKey),this.ready=!0}async loadSegmentScript(){return new Promise(((e,t)=>{const i=window.analytics=window.analytics||[];if(i.initialize)e();else{if(i.invoked)return console.error("Segment snippet included twice."),void t(new Error("Segment already loaded"));i.invoked=!0,i.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on"],i.factory=function(e){return function(){const t=Array.prototype.slice.call(arguments);return t.unshift(e),i.push(t),i}};for(let e=0;e<i.methods.length;e++){const t=i.methods[e];i[t]=i.factory(t)}i.load=function(s,a){const n=document.createElement("script");n.type="text/javascript",n.async=!0,n.src="https://cdn.segment.com/analytics.js/v1/"+s+"/analytics.min.js",n.onload=e,n.onerror=t;const r=document.getElementsByTagName("script")[0];r.parentNode.insertBefore(n,r),i._loadOptions=a},i.SNIPPET_VERSION="4.13.1"}}))}async pushData(e,t){if(!this.isReady())throw new Error("Segment data source not ready");this.useUserProperties&&this.analytics.identify(e,{uiflow_areas:t.areas,uiflow_last_updated:Date.now(),uiflow_total_interactions:this.getTotalInteractions(t.usageHistory)}),this.analytics.track("UIFlow Settings Updated",{userId:e,areas:t.areas,source:this.trackingPlan,timestamp:Date.now()})}async pullData(e){if(!this.isReady())throw new Error("Segment data source not ready");return console.warn("Segment data source: pullData not implemented - Segment is primarily for tracking"),{areas:{},overrides:{},usageHistory:[],note:"Segment is write-only from browser. Use Segment Personas API for reading user data."}}async trackEvent(e,t){if(!this.isReady())return;const i=this.mapToSegmentEvent(t);this.analytics.track(i.name,{...i.properties,userId:e,source:this.trackingPlan,timestamp:Date.now()})}mapToSegmentEvent(e){const{elementId:t,category:i,area:s,action:a}=e;return{name:"UI Element Interacted",properties:{element_id:t,element_category:i,ui_area:s,action_type:a||"click",density_level:e.densityLevel,is_new_feature:e.isNew||!1}}}getTotalInteractions(e){return Array.isArray(e)?e.reduce(((e,[t,i])=>e+(Array.isArray(i)?i.length:0)),0):0}async identifyUser(e,t={}){return this.isReady()?(this.analytics.identify(e,{...t,uiflow_enabled:!0,uiflow_initialized_at:Date.now()}),Promise.resolve()):Promise.resolve()}trackPageView(e,t={}){this.isReady()&&this.analytics.page({...t,uiflow_enabled:!0,source:this.trackingPlan})}trackDensityChange(e,t,i,s,a){this.isReady()&&this.analytics.track("UIFlow Density Changed",{userId:e,ui_area:t,old_density:i,new_density:s,change_reason:a,source:this.trackingPlan})}trackFeatureDiscovery(e,t,i,s){this.isReady()&&this.analytics.track("UIFlow Feature Discovered",{userId:e,element_id:t,element_category:i,ui_area:s,source:this.trackingPlan})}}class a{constructor(){this.sources=new Map,this.primary=null}addSource(e,t,i=!1){this.sources.set(e,t),!i&&this.primary||(this.primary=e)}async initialize(){const e=Array.from(this.sources.values()).map((e=>e.initialize().catch((e=>(console.warn("Data source initialization failed:",e),null)))));await Promise.all(e)}async pushData(e,t){const i=Array.from(this.sources.entries()).map((async([i,s])=>{try{s.isReady()&&await s.pushData(e,t)}catch(e){console.warn(`Data push failed for source ${i}:`,e)}}));await Promise.all(i)}async pullData(e){if(!this.primary)return{areas:{},overrides:{},usageHistory:[]};const t=this.sources.get(this.primary);if(!t||!t.isReady())return{areas:{},overrides:{},usageHistory:[]};try{return await t.pullData(e)}catch(e){return console.warn("Primary data source pull failed:",e),{areas:{},overrides:{},usageHistory:[]}}}async trackEvent(e,t){const i=Array.from(this.sources.values()).map((async i=>{try{i.isReady()&&await i.trackEvent(e,t)}catch(e){console.warn("Event tracking failed for source:",e)}}));await Promise.all(i)}getSource(e){return this.sources.get(e)}getSourceNames(){return Array.from(this.sources.keys())}removeSource(e){const t=this.sources.get(e);t&&(t.destroy(),this.sources.delete(e),this.primary===e&&(this.primary=this.sources.size>0?this.sources.keys().next().value:null))}destroy(){for(const e of this.sources.values())e.destroy();this.sources.clear(),this.primary=null}}class n{constructor(e={}){this.sequenceTracker=new Map,this.activeRules=[],this.abTestMetrics=new Map,this.config={categories:["basic","advanced","expert"],learningRate:.1,storageKey:"uiflow-data",timeAcceleration:1,adaptationThreshold:3,decayRate:.95,syncInterval:3e5,userId:null,dataSources:{},...e},this.elements=new Map,this.areas=new Map,this.usageHistory=new Map,this.defaultDensity=.3,this.initialized=!1,this.syncTimer=null,this.remoteOverrides=new Map,this.highlights=new Map,this.highlightStyles=null,this.dataManager=new a}async init(e={}){return Object.assign(this.config,e),this.loadStoredData(),this.setupObservers(),this.injectHighlightStyles(),await this.setupDataSources(),this.config.userId&&this.dataManager.getSourceNames().length>0&&(await this.syncWithDataSources(),this.startSyncTimer()),this.initialized=!0,this.emit("uiflow:initialized",{areas:this.getAreaDensities()}),this}categorize(e,t,i="default",s={}){if(!this.config.categories.includes(t))throw new Error(`Invalid category: ${t}`);const a=this.getElementId(e);return this.elements.set(a,{element:e,category:t,area:i,visible:!0,interactions:0,lastUsed:null,helpText:s.helpText,isNew:s.isNew??!1,dependencies:s.dependencies??[]}),this.areas.has(i)||this.areas.set(i,{density:this.defaultDensity,lastActivity:Date.now(),totalInteractions:0}),e.setAttribute("data-uiflow-category",t),e.setAttribute("data-uiflow-area",i),e.setAttribute("data-uiflow-id",a),s.helpText&&e.setAttribute("data-uiflow-help",s.helpText),this.updateElementVisibility(a),this}getDensityLevel(e="default"){if(this.remoteOverrides.has(e))return this.remoteOverrides.get(e);const t=this.areas.get(e);return t?t.density:this.defaultDensity}getAreaDensities(){const e={};for(const[t,i]of this.areas)e[t]=i.density;return e}setDensityLevel(e,t="default",i={}){const s=Math.max(0,Math.min(1,e));return this.areas.has(t)?this.areas.get(t).density=s:this.areas.set(t,{density:s,lastActivity:Date.now(),totalInteractions:0}),this.updateAreaElementsVisibility(t),this.emit("uiflow:density-changed",{area:t,density:s,areas:this.getAreaDensities()}),!i.skipAPI&&this.config.userId&&this.pushToDataSources(),this}shouldShowElement(e,t="default"){const i=this.config.categories.indexOf(e),s={0:0,1:.4,2:.75}[i]??i/(this.config.categories.length-1);return this.getDensityLevel(t)>=s}shouldShowElementWithDependencies(e){const t=this.elements.get(e);if(!t)return!1;return!!this.shouldShowElement(t.category,t.area)&&this.validateDependencies(e)}setRemoteOverride(e,t){this.remoteOverrides.set(e,Math.max(0,Math.min(1,t))),this.updateAreaElementsVisibility(e),this.emit("uiflow:override-applied",{area:e,density:t})}clearRemoteOverride(e){this.remoteOverrides.delete(e),this.updateAreaElementsVisibility(e),this.emit("uiflow:override-cleared",{area:e})}getOverrides(){return Object.fromEntries(this.remoteOverrides)}hasOverride(e){return this.remoteOverrides.has(e)}highlightElement(e,t="default",i={}){const s=this.elements.get(e);if(!s||!s.visible)return this;const a={style:t,startTime:Date.now(),duration:i.duration??5e3,tooltip:i.tooltip??s.helpText??void 0,persistent:i.persistent??!1,onDismiss:i.onDismiss};return this.highlights.set(e,a),this.applyHighlight(e,a),a.persistent||setTimeout((()=>{this.removeHighlight(e)}),a.duration),this.emit("uiflow:highlight-added",{elementId:e,style:t,options:i}),this}removeHighlight(e){const t=this.highlights.get(e);if(!t)return this;const i=this.elements.get(e);if(i){i.element.classList.remove(`uiflow-highlight-${t.style}`),i.element.removeAttribute("data-uiflow-tooltip");const e=i.element.querySelector(".uiflow-tooltip");e&&e.remove()}return this.highlights.delete(e),t.onDismiss&&t.onDismiss(e),this.emit("uiflow:highlight-removed",{elementId:e}),this}flagAsNew(e,t,i=8e3){const s=this.elements.get(e);return s?(s.isNew=!0,t&&(s.helpText=t,s.element.setAttribute("data-uiflow-help",t)),s.visible&&this.highlightElement(e,"new-feature",{duration:i,tooltip:t??"New feature available!"}),this):this}showTooltip(e,t,i={}){return this.highlightElement(e,"tooltip",{tooltip:t,duration:i.duration??3e3,persistent:i.persistent??!1})}clearAllHighlights(){for(const e of this.highlights.keys())this.removeHighlight(e);return this}getHighlights(){return Array.from(this.highlights.keys())}addDataSource(e,t,a,n=!1){let r;switch(t){case"api":r=new i({endpoint:a.endpoint});break;case"segment":r=new s({writeKey:a.writeKey,trackingPlan:a.trackingPlan,useUserProperties:a.useUserProperties});break;default:throw new Error(`Unknown data source type: ${t}`)}return this.dataManager.addSource(e,r,n),r.initialize(),this.emit("uiflow:datasource-added",{sources:[e]}),this}removeDataSource(e){return this.dataManager.removeSource(e),this.emit("uiflow:datasource-removed",{sources:[e]}),this}getDataSource(e){return this.dataManager.getSource(e)}async forceSync(){await this.syncWithDataSources(),await this.pushToDataSources()}simulateUsage(e,t,i=7){const s=this.config.timeAcceleration;this.config.timeAcceleration=1e3;const a=864e5*i/t.length;t.forEach(((t,i)=>{const s=Date.now()+i*a;this.recordUsageHistory(e,t.category,s*this.config.timeAcceleration)})),this.adaptDensity(e),this.config.timeAcceleration=s}simulateUserType(e,t){const i="string"==typeof e?{beginner:{basic:.85,advanced:.13,expert:.02},intermediate:{basic:.6,advanced:.35,expert:.05},"power-user":{basic:.3,advanced:.5,expert:.2},expert:{basic:.15,advanced:.4,expert:.45}}[e]:e,s=t?Array.isArray(t)?t:[t]:Array.from(this.areas.keys()),a=[],n=Math.round(50*i.basic),r=Math.round(50*i.advanced),o=50-n-r;for(let e=0;e<n;e++)a.push({category:"basic"});for(let e=0;e<r;e++)a.push({category:"advanced"});for(let e=0;e<o;e++)a.push({category:"expert"});for(let e=a.length-1;e>0;e--){const t=Math.floor(Math.random()*(e+1)),i=a[e];void 0!==a[t]&&void 0!==i&&(a[e]=a[t],a[t]=i)}s.forEach((e=>{this.simulateUsage(e,a,7)})),console.log(`๐Ÿค– Simulated ${"string"==typeof e?e:"custom"} user for areas: ${s.join(", ")}`)}setDemoMode(e){this.demoModeOriginal||(this.demoModeOriginal={learningRate:this.config.learningRate,timeAcceleration:this.config.timeAcceleration,adaptationThreshold:this.config.adaptationThreshold}),e?(this.config.learningRate=.9,this.config.timeAcceleration=2,this.config.adaptationThreshold=1):Object.assign(this.config,this.demoModeOriginal)}boostDensity(e,t){const i=this.areas.get(e);if(i){const s=i.density;i.density=Math.max(0,Math.min(1,t)),this.updateAreaElementsVisibility(e),this.saveData(),this.emit("uiflow:density-changed",{area:e,density:i.density,previousDensity:s}),console.log(`๐ŸŽฏ Boosted ${e} density to ${Math.round(100*t)}%`)}}resetArea(e){const t=this.areas.get(e);if(t){t.density=.3,t.totalInteractions=0,t.lastActivity=0;for(const t of this.config.categories){const i=`${e}:${t}`;this.usageHistory.delete(i)}this.updateAreaElementsVisibility(e),this.saveData(),console.log(`๐Ÿ”„ Reset ${e} to initial state`)}}getAreaStats(e){const t=this.areas.get(e);if(!t)return{density:0,visibleElements:0,totalElements:0,recentUsage:{basic:0,advanced:0,expert:0},adaptationEvents:0};const i=Array.from(this.elements.values()).filter((t=>t.area===e)),s=i.filter((e=>e.visible)).length,a=this.getRecentUsageByArea(e);return{density:t.density,visibleElements:s,totalElements:i.length,recentUsage:a,adaptationEvents:t.totalInteractions}}getOverviewStats(){const e={};for(const t of this.areas.keys())e[t]=this.getAreaStats(t);return e}getElementCount(){return this.elements.size}getAreaCount(){return this.areas.size}getElementsForArea(e){return Array.from(this.elements.values()).filter((t=>t.area===e))}isInitialized(){return this.initialized}getNewVisibleElements(){return Array.from(this.elements.entries()).filter((([e,t])=>t.isNew&&t.visible)).map((([e,t])=>({elementId:e,helpText:t.helpText})))}getVisibleElementsWithHelp(){return Array.from(this.elements.entries()).filter((([e,t])=>t.visible&&t.helpText)).map((([e,t])=>({elementId:e,helpText:t.helpText})))}getVisibleElementsSorted(){return Array.from(this.elements.entries()).filter((([e,t])=>t.visible&&t.helpText)).sort((([,e],[,t])=>{const i={basic:0,advanced:1,expert:2};return i[e.category]-i[t.category]})).map((([e,t])=>({elementId:e,category:t.category,helpText:t.helpText})))}validateDependencies(e){const t=this.elements.get(e);return!t||!t.dependencies||t.dependencies.every((e=>this.validateSingleDependency(e)))}validateSingleDependency(e){switch(e.type){case"usage_count":return this.validateUsageCount(e);case"sequence":return this.validateSequence(e);case"time_based":return this.validateTimeBased(e);case"logical_and":return e.elements?.every((e=>this.validateDependencies(e)))??!1;case"logical_or":return e.elements?.some((e=>this.validateDependencies(e)))??!1;default:return console.warn(`Unknown dependency type: ${e.type}`),!1}}validateUsageCount(e){if(!e.elementId||!e.threshold)return!1;const t=this.elements.get(e.elementId);return!!t&&t.interactions>=e.threshold}validateSequence(e){if(!e.elements||0===e.elements.length)return!1;const t=e.elements.join("โ†’");return this.sequenceTracker.get(t),e.elements.every((e=>{const t=this.elements.get(e);return t&&t.interactions>0}))}validateTimeBased(e){if(!e.elementId||!e.timeWindow||!e.minUsage)return!1;const t=this.elements.get(e.elementId);if(!t)return!1;const i=this.parseTimeWindow(e.timeWindow),s=this.getAcceleratedTime()-i,a=`${t.area}:${t.category}`;return(this.usageHistory.get(a)||[]).filter((e=>e>s)).length>=e.minUsage}parseTimeWindow(e){const t=e.match(/^(\d+)([dwmy])$/);if(!t)return 6048e5;const[,i,s]=t,a=parseInt(i||"7",10);switch(s){case"d":return 24*a*60*60*1e3;case"w":return 7*a*24*60*60*1e3;case"m":return 30*a*24*60*60*1e3;case"y":return 365*a*24*60*60*1e3;default:return 6048e5}}trackElementSequence(e){const t=this.elements.get(e);if(!t)return;const i=Date.now(),s=`${t.area}:sequence`;this.addToSequence(s,{elementId:e,timestamp:i,area:t.area}),this.addToSequence("global:sequence",{elementId:e,timestamp:i,area:t.area}),this.analyzeUserJourney(t.area)}addToSequence(e,t){this.sequenceTracker.has(e)||this.sequenceTracker.set(e,[]);const i=this.sequenceTracker.get(e);i.push(t.timestamp),i.length>20&&i.splice(0,i.length-20)}analyzeUserJourney(e){const t=`${e}:sequence`,i=this.sequenceTracker.get(t)||[];if(i.length<5)return;const s=i.slice(-10),a=(s[s.length-1]||0)-(s[0]||0),n=s.length/(a/1e3/60);let r="exploring";n>2&&(r="focused");const o=this.getElementsForArea(e),l=o.filter((e=>("advanced"===e.category||"expert"===e.category)&&e.interactions>0)).length;l>.5*o.length&&(r="expert"),this.emit("uiflow:journey-analyzed",{area:e,behavior:r,interactionRate:n,advancedFeatureUsage:l,totalSequenceLength:i.length,recentActivity:s.length}),this.adaptToUserJourney(e,r)}adaptToUserJourney(e,t){switch(t){case"exploring":this.flagNewElementsInArea(e,"basic");break;case"focused":this.boostDensity(e,Math.min(this.getDensityLevel(e)+.1,.8));break;case"expert":this.unlockCategory("expert",e)}}flagNewElementsInArea(e,t){for(const[i,s]of this.elements)s.area===e&&s.visible&&(t&&s.category!==t||(s.isNew=!0,this.flagAsNew(i,s.helpText||"Try this feature!",5e3)))}async loadConfiguration(e){let t=e;if(e.abTest?.enabled){const i=this.selectABTestVariant(e.abTest);i&&(t=this.mergeConfiguration(e,i.configuration),this.abTestVariant=i.id,this.initializeABTestMetrics(e.abTest.metrics),console.log(`๐Ÿงช A/B Test Active: ${e.abTest.testId}, Variant: ${i.name}`))}this.loadedConfiguration=t;for(const[e,i]of Object.entries(t.areas)){this.areas.has(e)||this.areas.set(e,{density:i.defaultDensity,lastActivity:Date.now(),totalInteractions:0});for(const t of i.elements){const i=document.querySelector(t.selector);i?this.categorize(i,t.category,e,{helpText:t.helpText,dependencies:t.dependencies}):console.warn(`Element not found for selector: ${t.selector}`)}}return t.rules&&t.rules.length>0&&this.initializeRuleEngine(t.rules),console.log(`โœ… UIFlow configuration loaded: ${t.name} v${t.version}`),this.emit("uiflow:configuration-loaded",{config:t,abTestVariant:this.abTestVariant}),this}selectABTestVariant(e){if(!e.variants.length||!e.trafficAllocation.length)return null;const t=this.config.userId||"anonymous",i=this.hashString(t+e.testId)%100;let s=0;for(let t=0;t<e.variants.length;t++)if(s+=e.trafficAllocation[t]||0,i<s)return e.variants[t]||null;return e.variants[0]||null}hashString(e){let t=0;for(let i=0;i<e.length;i++){t=(t<<5)-t+e.charCodeAt(i),t&=t}return Math.abs(t)}mergeConfiguration(e,t){return{...e,...t,areas:{...e.areas,...t.areas},rules:t.rules||e.rules||[],templates:t.templates||e.templates||[]}}initializeABTestMetrics(e){for(const t of e)this.abTestMetrics.set(t,0)}trackABTestMetric(e,t=1){if(this.abTestVariant&&this.abTestMetrics.has(e)){const i=this.abTestMetrics.get(e)||0;this.abTestMetrics.set(e,i+t),this.emit("uiflow:ab-test-metric",{testVariant:this.abTestVariant,metric:e,value:i+t})}}getABTestResults(){const e={};for(const[t,i]of this.abTestMetrics)e[t]=i;return{variant:this.abTestVariant,metrics:e}}exportConfiguration(){if(this.loadedConfiguration)return this.loadedConfiguration;const e={};for(const[t,i]of this.areas){const s=this.getElementsForArea(t).map((e=>({id:e.element.getAttribute("data-uiflow-id")||"",selector:`#${e.element.id}`||`.${e.element.className}`,category:e.category,helpText:e.helpText,dependencies:e.dependencies})));e[t]={defaultDensity:i.density,elements:s}}return{name:"Generated UIFlow Configuration",version:"1.0.0",areas:e,rules:[]}}initializeRuleEngine(e){this.activeRules=e,this.ruleCheckTimer=window.setInterval((()=>{this.processRules()}),3e4),console.log(`๐Ÿ”ง Rule engine initialized with ${e.length} rules`)}processRules(){for(const e of this.activeRules)this.evaluateRuleTrigger(e.trigger)&&(this.executeRuleAction(e.action),console.log(`๐Ÿ“‹ Rule executed: ${e.name}`),this.emit("uiflow:rule-triggered",{rule:e.name,trigger:e.trigger,action:e.action}))}evaluateRuleTrigger(e){switch(e.type){case"usage_pattern":return this.evaluateUsagePattern(e);case"time_based":return this.evaluateTimeBased(e);case"element_interaction":return this.evaluateElementInteraction(e);case"custom_event":return!1;default:return console.warn(`Unknown trigger type: ${e.type}`),!1}}evaluateUsagePattern(e){if(!e.elements||!e.frequency||!e.duration)return!1;const t=this.parseTimeWindow(e.duration),i=this.getAcceleratedTime()-t;return e.elements.every((s=>{const a=this.elements.get(s);if(!a)return!1;const n=`${a.area}:${a.category}`,r=(this.usageHistory.get(n)||[]).filter((e=>e>i));switch(e.frequency){case"daily":return r.length>=Math.ceil(t/864e5);case"weekly":return r.length>=Math.ceil(t/6048e5);case"monthly":return r.length>=Math.ceil(t/2592e6);default:return!1}}))}evaluateTimeBased(e){if(e.threshold){return Array.from(this.elements.values()).reduce(((e,t)=>e+t.interactions),0)>=e.threshold}return!1}evaluateElementInteraction(e){return!!e.elements&&e.elements.some((e=>{const t=this.elements.get(e);return t&&t.interactions>0}))}executeRuleAction(e){switch(e.type){case"unlock_element":e.elementId&&this.unlockElement(e.elementId);break;case"unlock_category":e.category&&e.area&&this.unlockCategory(e.category,e.area);break;case"show_tutorial":this.showTutorial(e.data);break;case"send_event":this.emit("uiflow:custom-event",e.data);break;default:console.warn(`Unknown action type: ${e.type}`)}}unlockElement(e){const t=this.elements.get(e);t&&(t.visible=!0,this.updateElementVisibility(e),this.emit("uiflow:element-unlocked",{elementId:e,category:t.category,area:t.area,trigger:"rule-action"}))}unlockCategory(e,t){let i=0;for(const[s,a]of this.elements)if(a.category===e&&a.area===t){const e=a.visible;a.visible=!0,this.updateElementVisibility(s),e||i++}i>0&&this.emit("uiflow:category-unlocked",{category:e,area:t,elementsUnlocked:i,trigger:"rule-action"})}showTutorial(e){this.emit("uiflow:tutorial-requested",{tutorial:e?.tutorial||"default",autoStart:e?.autoStart??!1,data:e})}destroy(){this.stopSyncTimer(),this.clearAllHighlights(),this.dataManager.destroy(),this.ruleCheckTimer&&(clearInterval(this.ruleCheckTimer),this.ruleCheckTimer=void 0),this.highlightStyles&&(this.highlightStyles.remove(),this.highlightStyles=null),this.initialized=!1,this.emit("uiflow:destroyed",{})}getElementId(e){return e.id||`uiflow-${Date.now()}-${Math.random().toString(36).substr(2,9)}`}updateElementVisibility(e){const t=this.elements.get(e);if(!t)return;const i=this.shouldShowElement(t.category,t.area),s=t.visible;t.visible!==i&&(t.visible=i,t.element.style.display=i?"":"none",t.element.setAttribute("data-uiflow-visible",i.toString()),i&&!s&&t.isNew&&this.highlightElement(e,"new-feature"))}updateAreaElementsVisibility(e){for(const[t,i]of this.elements)i.area===e&&this.updateElementVisibility(t)}updateAllElementsVisibility(){for(const[e]of this.elements)this.updateElementVisibility(e)}setupObservers(){document.addEventListener("click",(e=>{const t=e.target.closest("[data-uiflow-category]");t&&this.recordInteraction(t)}))}recordInteraction(e){const t=e.getAttribute("data-uiflow-id");if(!t)return;const i=this.elements.get(t);if(i){const e=this.getAcceleratedTime();i.interactions++,i.lastUsed=e,this.trackElementSequence(t);const s=this.areas.get(i.area);s&&(s.lastActivity=e,s.totalInteractions++),this.trackInteraction(i),this.recordUsageHistory(i.area,i.category,e),this.adaptDensity(i.area),this.updateDependentElements()}}updateDependentElements(){for(const[e,t]of this.elements)if(t.dependencies&&t.dependencies.length>0){const i=this.shouldShowElementWithDependencies(e);i!==t.visible&&(t.visible=i,this.updateElementVisibility(e),i&&this.emit("uiflow:element-unlocked",{elementId:e,category:t.category,area:t.area,dependencies:t.dependencies}))}}async trackInteraction(e){if(!this.config.userId)return;const t={elementId:e.element.getAttribute("data-uiflow-id"),category:e.category,area:e.area,action:"click",densityLevel:this.getDensityLevel(e.area),isNew:e.isNew,timestamp:Date.now()};await this.dataManager.trackEvent(this.config.userId,t)}recordUsageHistory(e,t,i){const s=`${e}:${t}`;this.usageHistory.has(s)||this.usageHistory.set(s,[]);const a=this.usageHistory.get(s);a.push(i),a.length>30&&a.splice(0,a.length-30)}adaptDensity(e){const t=this.areas.get(e);if(!t)return;const i=this.getRecentUsageByArea(e),s=i.advanced+i.expert,a=i.basic+i.advanced+i.expert;if(a>=this.config.adaptationThreshold){const i=s/a,n=Math.min(.3+.7*i,1),r=t.density,o=r+(n-r)*this.config.learningRate;t.density=Math.max(0,Math.min(1,o)),this.updateAreaElementsVisibility(e),this.saveData(),this.emit("uiflow:adaptation",{area:e,oldDensity:r,newDensity:t.density,advancedRatio:i,totalInteractions:a})}}getRecentUsageByArea(e,t=6048e5){const i=this.getAcceleratedTime()-t/this.config.timeAcceleration,s={basic:0,advanced:0,expert:0};for(const t of this.config.categories){const a=`${e}:${t}`,n=(this.usageHistory.get(a)||[]).filter((e=>e>i)).length;s[t]=n}return s}getAcceleratedTime(){return Date.now()*this.config.timeAcceleration}loadStoredData(){try{const e=localStorage.getItem(this.config.storageKey);if(e){const t=JSON.parse(e);t.areas&&(this.areas=new Map(t.areas)),t.usageHistory&&(this.usageHistory=new Map(t.usageHistory))}}catch(e){console.warn("UIFlow: Failed to load stored data",e)}}saveData(){try{const e={areas:Array.from(this.areas.entries()),usageHistory:Array.from(this.usageHistory.entries()),lastSaved:Date.now()};localStorage.setItem(this.config.storageKey,JSON.stringify(e))}catch(e){console.warn("UIFlow: Failed to save data",e)}}emit(e,t){const i=new CustomEvent(e,{detail:t});document.dispatchEvent(i)}async setupDataSources(){const{dataSources:e}=this.config;for(const[t,a]of Object.entries(e))try{let e;switch(t){case"api":e=new i({endpoint:a.endpoint});break;case"segment":e=new s({writeKey:a.writeKey,trackingPlan:a.trackingPlan,useUserProperties:a.useUserProperties});break;default:console.warn(`Unknown data source type: ${t}`);continue}this.dataManager.addSource(t,e,a.primary??!1),this.emit("uiflow:datasource-added",{sources:[t]})}catch(e){console.warn(`Failed to setup data source ${t}:`,e)}await this.dataManager.initialize()}async syncWithDataSources(){if(this.config.userId)try{const e=await this.dataManager.pullData(this.config.userId);this.applyRemoteSettings(e),this.emit("uiflow:sync-success",{sources:this.dataManager.getSourceNames(),data:e})}catch(e){console.warn("UIFlow: Failed to sync with data sources",e),this.emit("uiflow:sync-error",{error:e.message})}}async pushToDataSources(){if(this.config.userId)try{const e={areas:this.getAreaDensities(),usageHistory:Array.from(this.usageHistory.entries()),lastUpdated:Date.now()};await this.dataManager.pushData(this.config.userId,e),this.emit("uiflow:push-success",{sources:this.dataManager.getSourceNames(),data:e})}catch(e){console.warn("UIFlow: Failed to push to data sources",e),this.emit("uiflow:push-error",{error:e.message})}}applyRemoteSettings(e){if(e.overrides)for(const[t,i]of Object.entries(e.overrides))this.remoteOverrides.set(t,i);if(e.areas)for(const[t,i]of Object.entries(e.areas))this.remoteOverrides.has(t)||this.setDensityLevel(i,t,{skipAPI:!0});this.updateAllElementsVisibility()}startSyncTimer(){this.syncTimer&&clearInterval(this.syncTimer),this.syncTimer=setInterval((()=>{this.syncWithDataSources()}),this.config.syncInterval)}stopSyncTimer(){this.syncTimer&&(clearInterval(this.syncTimer),this.syncTimer=null)}applyHighlight(e,t){const i=this.elements.get(e);if(!i)return;const s=i.element;s.classList.add(`uiflow-highlight-${t.style}`),t.tooltip&&this.createTooltip(s,t.tooltip),s.hasAttribute("tabindex")||s.setAttribute("tabindex","0")}createTooltip(e,t){const i=e.querySelector(".uiflow-tooltip");i&&i.remove();const s=document.createElement("div");s.className="uiflow-tooltip",s.textContent=t,s.setAttribute("role","tooltip"),e.appendChild(s),e.setAttribute("aria-describedby",s.id=`uiflow-tooltip-${Date.now()}`),this.positionTooltip(e,s),s.addEventListener("click",(t=>{t.stopPropagation();const i=e.getAttribute("data-uiflow-id");i&&this.removeHighlight(i)}))}positionTooltip(e,t){const i=e.getBoundingClientRect(),s=t.getBoundingClientRect();let a=i.top-s.height-8,n=i.left+(i.width-s.width)/2;a<8&&(a=i.bottom+8),n<8?n=8:n+s.width>window.innerWidth-8&&(n=window.innerWidth-s.width-8),t.style.position="fixed",t.style.top=a+"px",t.style.left=n+"px",t.style.zIndex="9999"}injectHighlightStyles(){if(this.highlightStyles)return;this.highlightStyles=document.createElement("style"),this.highlightStyles.textContent='\n .uiflow-highlight-default {\n position: relative;\n animation: uiflow-pulse 2s infinite;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.4) !important;\n }\n \n .uiflow-highlight-new-feature {\n position: relative;\n animation: uiflow-glow 2s infinite alternate;\n box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.6) !important;\n }\n \n .uiflow-highlight-new-feature::before {\n content: "NEW";\n position: absolute;\n top: -8px;\n right: -8px;\n background: #22c55e;\n color: white;\n font-size: 10px;\n font-weight: bold;\n padding: 2px 6px;\n border-radius: 10px;\n z-index: 10;\n }\n \n .uiflow-highlight-tooltip {\n box-shadow: 0 0 0 2px rgba(147, 51, 234, 0.4) !important;\n }\n \n .uiflow-tooltip {\n position: absolute;\n background: #1f2937;\n color: white;\n padding: 8px 12px;\n border-radius: 6px;\n font-size: 13px;\n max-width: 200px;\n line-height: 1.4;\n cursor: pointer;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n }\n \n .uiflow-tooltip::before {\n content: "";\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 6px solid transparent;\n border-top-color: #1f2937;\n }\n \n .uiflow-tooltip:hover {\n background: #374151;\n }\n \n @keyframes uiflow-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.7; }\n }\n \n @keyframes uiflow-glow {\n 0% { box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.6) !important; }\n 100% { box-shadow: 0 0 0 6px rgba(34, 197, 94, 0.3) !important; }\n }\n ',document.head.appendChild(this.highlightStyles)}}var r=new n;e.UIFlow=n,e.createUIFlow=function(e){return new n(e)},e.default=r,Object.defineProperty(e,"__esModule",{value:!0})}));