').hide();\n $('body').append(fakeNode);\n var fakeElemColor = fakeNode.css('backgroundColor');\n fakeNode.remove();\n\n elem = $(elem);\n while (elem.length && elem[0] !== document) {\n var color = elem.css('backgroundColor');\n if (color !== fakeElemColor) {\n return color;\n }\n elem = elem.parent();\n }\n\n return null;\n};\n\n/**\n * Calculate the inheritted position.\n *\n * @method calculatePosition\n * @param {jQuery} elem The element to calculate position for\n * @return {String} Calculated position\n */\nTour.prototype.calculatePosition = function (elem) {\n elem = $(elem);\n while (elem.length && elem[0] !== document) {\n var position = elem.css('position');\n if (position !== 'static') {\n return position;\n }\n elem = elem.parent();\n }\n\n return null;\n};\n\n/**\n * Perform accessibility changes for step shown.\n *\n * This will add aria-hidden=\"true\" to all siblings and parent siblings.\n *\n * @method accessibilityShow\n */\nTour.prototype.accessibilityShow = function () {\n var stateHolder = 'data-has-hidden';\n var attrName = 'aria-hidden';\n var hideFunction = function hideFunction(child) {\n var flexitourRole = child.data('flexitour');\n if (flexitourRole) {\n switch (flexitourRole) {\n case 'container':\n case 'target':\n return;\n }\n }\n\n var hidden = child.attr(attrName);\n if (!hidden) {\n child.attr(stateHolder, true);\n child.attr(attrName, true);\n }\n };\n\n this.currentStepNode.siblings().each(function (index, node) {\n hideFunction($(node));\n });\n this.currentStepNode.parentsUntil('body').siblings().each(function (index, node) {\n hideFunction($(node));\n });\n};\n\n/**\n * Perform accessibility changes for step hidden.\n *\n * This will remove any newly added aria-hidden=\"true\".\n *\n * @method accessibilityHide\n */\nTour.prototype.accessibilityHide = function () {\n var stateHolder = 'data-has-hidden';\n var attrName = 'aria-hidden';\n var showFunction = function showFunction(child) {\n var hidden = child.attr(stateHolder);\n if (typeof hidden !== 'undefined') {\n child.removeAttr(stateHolder);\n child.removeAttr(attrName);\n }\n };\n\n $('[' + stateHolder + ']').each(function (index, node) {\n showFunction($(node));\n });\n};\n\nif ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') {\n module.exports = Tour;\n}\n\nreturn Tour;\n\n}));\n"],"file":"tour.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../src/tour.js"],"names":["Tour","config","init","eventHandlers","reset","originalConfiguration","configure","apply","arguments","storage","window","sessionStorage","storageKey","tourName","e","hide","resetStepListeners","steps","currentStepNumber","eventName","forEach","handler","addEventHandler","resetStepDefaults","template","templateContent","checkMinimumRequirements","Error","length","loadOriginalConfiguration","stepDefaults","setStepDefaults","$","extend","element","placement","delay","moveOnClick","moveAfterTime","orphan","direction","parseInt","stepNumber","setItem","code","DOMException","QUOTA_EXCEEDED_ERR","removeItem","getCurrentStepNumber","nextStepNumber","isStepPotentiallyVisible","getStepConfig","previousStepNumber","getNextStepNumber","getPreviousStepNumber","stepConfig","isStepActuallyVisible","target","getStepTarget","is","gotoStep","endTour","_gotoStep","delayed","setTimeout","bind","fn","fireEventHandlers","renderStep","normalizeStepConfig","reflex","moveAfterClick","content","body","attachTo","attachPoint","first","data","thisEvent","call","push","listeners","node","currentStepNode","args","proxy","next","previous","handleKeyDown","targetNode","parents","listener","on","off","currentStepConfig","setCurrentStepNumber","getTemplateContent","find","html","title","isFirstStep","prop","isLastStep","attr","addStepToPage","processStepListeners","clone","animationTarget","stop","zIndex","calculateZIndex","css","positionBackdrop","document","append","top","left","animate","scrollTop","calculateScrollTop","promise","then","positionStep","revealStep","catch","isOrphan","addClass","offset","calculateStepPositionInPage","currentStepPopper","Popper","removeOnDestroy","arrowElement","modifiers","enabled","applyStyle","onLoad","fadeIn","announceStep","focus","stepId","bodyRegion","headerRegion","accessibilityShow","tabbableSelector","keyCode","hasBackdrop","activeElement","stepTarget","tabbableNodes","dialogContainer","currentIndex","filter","index","has","each","nextIndex","nextNode","focusRelevant","shiftKey","closest","last","preventDefault","startAt","storageStartValue","getItem","storageStartAt","startTour","previousTarget","transition","destroy","fadeTime","remove","removeAttr","fadeOut","currentStepElement","accessibilityHide","viewportHeight","height","Math","max","min","ceil","stepHeight","viewportWidth","width","stepWidth","flipBehavior","flip","behaviour","arrow","onCreate","recalculateArrowPosition","onUpdate","split","isVertical","indexOf","instance","popper","querySelector","stepElement","arrowHeight","parseFloat","getComputedStyle","arrowOffset","popperHeight","popperOffset","popperBorderWidth","popperBorderRadiusWidth","arrowPos","maxPos","minPos","newArrowPos","arrowWidth","popperWidth","background","backdrop","insertAfter","buffer","colorNode","outerWidth","outerHeight","backgroundColor","calculateInherittedBackgroundColor","targetRadius","targetPosition","calculatePosition","fader","opacity","elem","position","value","isNaN","parent","fakeNode","fakeElemColor","color","attrName","hideFunction","child","flexitourRole","hidden","siblings","parentsUntil","showFunction"],"mappings":"gKAsBA,OACA,O,wmBAOqBA,CAAAA,C,YAIjB,WAAYC,CAAZ,CAAoB,WAChB,KAAKC,IAAL,CAAUD,CAAV,CACH,C,qCAUIA,C,CAAQ,CAET,KAAKE,aAAL,CAAqB,EAArB,CAGA,KAAKC,KAAL,GAGA,KAAKC,qBAAL,CAA6BJ,CAAM,EAAI,EAAvC,CAGA,KAAKK,SAAL,CAAeC,KAAf,CAAqB,IAArB,CAA2BC,SAA3B,EAEA,GAAI,CACA,KAAKC,OAAL,CAAeC,MAAM,CAACC,cAAtB,CACA,KAAKC,UAAL,CAAkB,aAAe,KAAKC,QACzC,CAAC,MAAOC,CAAP,CAAU,CACR,KAAKL,OAAL,IACA,KAAKG,UAAL,CAAkB,EACrB,CAED,MAAO,KACV,C,qCASO,CAEJ,KAAKG,IAAL,GAGA,KAAKZ,aAAL,CAAqB,EAArB,CAGA,KAAKa,kBAAL,GAGA,KAAKX,qBAAL,CAA6B,EAA7B,CAGA,KAAKY,KAAL,CAAa,EAAb,CAGA,KAAKC,iBAAL,CAAyB,CAAzB,CAEA,MAAO,KACV,C,4CAUSjB,C,CAAQ,YACd,GAAsB,QAAlB,KAAOA,CAAP,CAAJ,CAAgC,CAE5B,GAA+B,WAA3B,QAAOA,CAAAA,CAAM,CAACY,QAAlB,CAA4C,CACxC,KAAKA,QAAL,CAAgBZ,CAAM,CAACY,QAC1B,CAGD,GAAIZ,CAAM,CAACE,aAAX,CAA0B,gBACbgB,CADa,EAElBlB,CAAM,CAACE,aAAP,CAAqBgB,CAArB,EAAgCC,OAAhC,CAAwC,SAASC,CAAT,CAAkB,CACtD,KAAKC,eAAL,CAAqBH,CAArB,CAAgCE,CAAhC,CACH,CAFD,CAEG,CAFH,CAFkB,EACtB,IAAK,GAAIF,CAAAA,CAAT,GAAsBlB,CAAAA,CAAM,CAACE,aAA7B,CAA4C,GAAnCgB,CAAmC,CAI3C,CACJ,CAGD,KAAKI,iBAAL,KAGA,GAA4B,QAAxB,KAAOtB,CAAM,CAACgB,KAAd,CAAJ,CAAsC,CAClC,KAAKA,KAAL,CAAahB,CAAM,CAACgB,KACvB,CAED,GAA+B,WAA3B,QAAOhB,CAAAA,CAAM,CAACuB,QAAlB,CAA4C,CACxC,KAAKC,eAAL,CAAuBxB,CAAM,CAACuB,QACjC,CACJ,CAGD,KAAKE,wBAAL,GAEA,MAAO,KACV,C,2EAO0B,CAEvB,GAAI,CAAC,KAAKb,QAAV,CAAoB,CAChB,KAAM,IAAIc,CAAAA,KAAJ,CAAU,oBAAV,CACT,CAGD,GAAI,CAAC,KAAKV,KAAN,EAAe,CAAC,KAAKA,KAAL,CAAWW,MAA/B,CAAuC,CACnC,KAAM,IAAID,CAAAA,KAAJ,CAAU,yBAAV,CACT,CACJ,C,4DAUiBE,C,CAA2B,CACzC,GAAyC,WAArC,QAAOA,CAAAA,CAAX,CAAsD,CAClDA,CAAyB,GAC5B,CAED,KAAKC,YAAL,CAAoB,EAApB,CACA,GAAI,CAACD,CAAD,EAAiF,WAAnD,QAAO,MAAKxB,qBAAL,CAA2ByB,YAApE,CAAkG,CAC9F,KAAKC,eAAL,CAAqB,EAArB,CACH,CAFD,IAEO,CACH,KAAKA,eAAL,CAAqB,KAAK1B,qBAAL,CAA2ByB,YAAhD,CACH,CAED,MAAO,KACV,C,wDAUeA,C,CAAc,CAC1B,GAAI,CAAC,KAAKA,YAAV,CAAwB,CACpB,KAAKA,YAAL,CAAoB,EACvB,CACDE,UAAEC,MAAF,CACI,KAAKH,YADT,CAEI,CACII,OAAO,CAAS,EADpB,CAEIC,SAAS,CAAO,KAFpB,CAGIC,KAAK,CAAW,CAHpB,CAIIC,WAAW,GAJf,CAKIC,aAAa,CAAG,CALpB,CAMIC,MAAM,GANV,CAOIC,SAAS,CAAO,CAPpB,CAFJ,CAWIV,CAXJ,EAcA,MAAO,KACV,C,mEAQsB,CACnB,MAAOW,CAAAA,QAAQ,CAAC,KAAKvB,iBAAN,CAAyB,EAAzB,CAClB,C,kEASoBwB,C,CAAY,CAC7B,KAAKxB,iBAAL,CAAyBwB,CAAzB,CACA,GAAI,KAAKjC,OAAT,CAAkB,CACd,GAAI,CACA,KAAKA,OAAL,CAAakC,OAAb,CAAqB,KAAK/B,UAA1B,CAAsC8B,CAAtC,CACH,CAAC,MAAO5B,CAAP,CAAU,CACR,GAAIA,CAAC,CAAC8B,IAAF,GAAWC,YAAY,CAACC,kBAA5B,CAAgD,CAC5C,KAAKrC,OAAL,CAAasC,UAAb,CAAwB,KAAKnC,UAA7B,CACH,CACJ,CACJ,CACJ,C,4DASiB8B,C,CAAY,CAC1B,GAA0B,WAAtB,QAAOA,CAAAA,CAAX,CAAuC,CACnCA,CAAU,CAAG,KAAKM,oBAAL,EAChB,CACD,GAAIC,CAAAA,CAAc,CAAGP,CAAU,CAAG,CAAlC,CAGA,MAAOO,CAAc,EAAI,KAAKhC,KAAL,CAAWW,MAApC,CAA4C,CACxC,GAAI,KAAKsB,wBAAL,CAA8B,KAAKC,aAAL,CAAmBF,CAAnB,CAA9B,CAAJ,CAAuE,CACnE,MAAOA,CAAAA,CACV,CACDA,CAAc,EACjB,CAED,MAAO,KACV,C,oEASqBP,C,CAAY,CAC9B,GAA0B,WAAtB,QAAOA,CAAAA,CAAX,CAAuC,CACnCA,CAAU,CAAG,KAAKM,oBAAL,EAChB,CACD,GAAII,CAAAA,CAAkB,CAAGV,CAAU,CAAG,CAAtC,CAGA,MAA6B,CAAtB,EAAAU,CAAP,CAAgC,CAC5B,GAAI,KAAKF,wBAAL,CAA8B,KAAKC,aAAL,CAAmBC,CAAnB,CAA9B,CAAJ,CAA2E,CACvE,MAAOA,CAAAA,CACV,CACDA,CAAkB,EACrB,CAED,MAAO,KACV,C,8CASUV,C,CAAY,CACnB,GAAIO,CAAAA,CAAc,CAAG,KAAKI,iBAAL,CAAuBX,CAAvB,CAArB,CAEA,MAA0B,KAAnB,GAAAO,CACV,C,gDASWP,C,CAAY,CACpB,GAAIU,CAAAA,CAAkB,CAAG,KAAKE,qBAAL,CAA2BZ,CAA3B,CAAzB,CAEA,MAA8B,KAAvB,GAAAU,CACV,C,0EASwBG,C,CAAY,CACjC,GAAI,CAACA,CAAL,CAAiB,CAEb,QACH,CAED,GAAI,KAAKC,qBAAL,CAA2BD,CAA3B,CAAJ,CAA4C,CAExC,QACH,CAED,GAAiC,WAA7B,QAAOA,CAAAA,CAAU,CAAChB,MAAlB,EAA4CgB,CAAU,CAAChB,MAA3D,CAAmE,CAE/D,QACH,CAED,GAAgC,WAA5B,QAAOgB,CAAAA,CAAU,CAACnB,KAAlB,EAA2CmB,CAAU,CAACnB,KAA1D,CAAiE,CAE7D,QACH,CAGD,QACH,C,oEASqBmB,C,CAAY,CAC9B,GAAI,CAACA,CAAL,CAAiB,CAEb,QACH,CAED,GAAIE,CAAAA,CAAM,CAAG,KAAKC,aAAL,CAAmBH,CAAnB,CAAb,CACA,GAAIE,CAAM,EAAIA,CAAM,CAAC7B,MAAjB,EAA2B6B,CAAM,CAACE,EAAP,CAAU,UAAV,CAA/B,CAAsD,CAElD,MAAO,CAAC,CAACF,CAAM,CAAC7B,MACnB,CAED,QACH,C,mCASM,CACH,MAAO,MAAKgC,QAAL,CAAc,KAAKP,iBAAL,EAAd,CACV,C,2CASU,CACP,MAAO,MAAKO,QAAL,CAAc,KAAKN,qBAAL,EAAd,CAA4C,CAAC,CAA7C,CACV,C,0CAWQZ,C,CAAYF,C,CAAW,CAC5B,GAAiB,CAAb,CAAAE,CAAJ,CAAoB,CAChB,MAAO,MAAKmB,OAAL,EACV,CAED,GAAIN,CAAAA,CAAU,CAAG,KAAKJ,aAAL,CAAmBT,CAAnB,CAAjB,CACA,GAAmB,IAAf,GAAAa,CAAJ,CAAyB,CACrB,MAAO,MAAKM,OAAL,EACV,CAED,MAAO,MAAKC,SAAL,CAAeP,CAAf,CAA2Bf,CAA3B,CACV,C,4CAESe,C,CAAYf,C,CAAW,CAC7B,GAAI,CAACe,CAAL,CAAiB,CACb,MAAO,MAAKM,OAAL,EACV,CAED,GAAgC,WAA5B,QAAON,CAAAA,CAAU,CAACnB,KAAlB,EAA2CmB,CAAU,CAACnB,KAAtD,EAA+D,CAACmB,CAAU,CAACQ,OAA/E,CAAwF,CACpFR,CAAU,CAACQ,OAAX,IACArD,MAAM,CAACsD,UAAP,CAAkB,KAAKF,SAAL,CAAeG,IAAf,CAAoB,IAApB,CAAlB,CAA6CV,CAAU,CAACnB,KAAxD,CAA+DmB,CAA/D,CAA2Ef,CAA3E,EAEA,MAAO,KACV,CALD,IAKO,IAAI,CAACe,CAAU,CAAChB,MAAZ,EAAsB,CAAC,KAAKiB,qBAAL,CAA2BD,CAA3B,CAA3B,CAAmE,CACtE,GAAIW,CAAAA,CAAE,CAAgB,CAAC,CAAd,EAAA1B,CAAS,CAAS,uBAAT,CAAmC,mBAArD,CACA,MAAO,MAAKoB,QAAL,CAAc,KAAKM,CAAL,EAASX,CAAU,CAACb,UAApB,CAAd,CAA+CF,CAA/C,CACV,CAED,KAAKzB,IAAL,GAEA,KAAKoD,iBAAL,CAAuB,cAAvB,CAAuCZ,CAAvC,EACA,KAAKa,UAAL,CAAgBb,CAAhB,EACA,KAAKY,iBAAL,CAAuB,aAAvB,CAAsCZ,CAAtC,EAEA,MAAO,KACV,C,oDASab,C,CAAY,CACtB,GAAmB,IAAf,GAAAA,CAAU,EAA0B,CAAb,CAAAA,CAAvB,EAAyCA,CAAU,EAAI,KAAKzB,KAAL,CAAWW,MAAtE,CAA8E,CAC1E,MAAO,KACV,CAGD,GAAI2B,CAAAA,CAAU,CAAG,KAAKc,mBAAL,CAAyB,KAAKpD,KAAL,CAAWyB,CAAX,CAAzB,CAAjB,CAGAa,CAAU,CAAGvB,UAAEC,MAAF,CAASsB,CAAT,CAAqB,CAACb,UAAU,CAAEA,CAAb,CAArB,CAAb,CAEA,MAAOa,CAAAA,CACV,C,gEASmBA,C,CAAY,CAE5B,GAAiC,WAA7B,QAAOA,CAAAA,CAAU,CAACe,MAAlB,EAAiF,WAArC,QAAOf,CAAAA,CAAU,CAACgB,cAAlE,CAAkG,CAC9FhB,CAAU,CAACgB,cAAX,CAA4BhB,CAAU,CAACe,MAC1C,CAED,GAAkC,WAA9B,QAAOf,CAAAA,CAAU,CAACrB,OAAlB,EAA0E,WAA7B,QAAOqB,CAAAA,CAAU,CAACE,MAAnE,CAA2F,CACvFF,CAAU,CAACE,MAAX,CAAoBF,CAAU,CAACrB,OAClC,CAED,GAAkC,WAA9B,QAAOqB,CAAAA,CAAU,CAACiB,OAAlB,EAAwE,WAA3B,QAAOjB,CAAAA,CAAU,CAACkB,IAAnE,CAAyF,CACrFlB,CAAU,CAACkB,IAAX,CAAkBlB,CAAU,CAACiB,OAChC,CAEDjB,CAAU,CAAGvB,UAAEC,MAAF,CAAS,EAAT,CAAa,KAAKH,YAAlB,CAAgCyB,CAAhC,CAAb,CAEAA,CAAU,CAAGvB,UAAEC,MAAF,CAAS,EAAT,CAAa,CACtByC,QAAQ,CAAEnB,CAAU,CAACE,MADC,CAEtBkB,WAAW,CAAE,OAFS,CAAb,CAGVpB,CAHU,CAAb,CAKA,GAAIA,CAAU,CAACmB,QAAf,CAAyB,CACrBnB,CAAU,CAACmB,QAAX,CAAsB,cAAEnB,CAAU,CAACmB,QAAb,EAAuBE,KAAvB,EACzB,CAED,MAAOrB,CAAAA,CACV,C,oDAWaA,C,CAAY,CACtB,GAAIA,CAAU,CAACE,MAAf,CAAuB,CACnB,MAAO,cAAEF,CAAU,CAACE,MAAb,CACV,CAED,MAAO,KACV,C,4DAUiBtC,C,CAAW0D,C,CAAM,CAC/B,GAA6C,WAAzC,QAAO,MAAK1E,aAAL,CAAmBgB,CAAnB,CAAX,CAA0D,CACtD,MAAO,KACV,CAED,KAAKhB,aAAL,CAAmBgB,CAAnB,EAA8BC,OAA9B,CAAsC,SAAS0D,CAAT,CAAoB,CACtDA,CAAS,CAACC,IAAV,CAAe,IAAf,CAAqBF,CAArB,CACH,CAFD,CAEG,IAFH,EAIA,MAAO,KACV,C,wDAQe1D,C,CAAWE,C,CAAS,CAChC,GAA6C,WAAzC,QAAO,MAAKlB,aAAL,CAAmBgB,CAAnB,CAAX,CAA0D,CACtD,KAAKhB,aAAL,CAAmBgB,CAAnB,EAAgC,EACnC,CAED,KAAKhB,aAAL,CAAmBgB,CAAnB,EAA8B6D,IAA9B,CAAmC3D,CAAnC,EAEA,MAAO,KACV,C,kEAUoBkC,C,CAAY,CAC7B,KAAK0B,SAAL,CAAeD,IAAf,CAEA,CACIE,IAAI,CAAE,KAAKC,eADf,CAEIC,IAAI,CAAE,CAAC,OAAD,CAAU,sBAAV,CAAgCpD,UAAEqD,KAAF,CAAQ,KAAKC,IAAb,CAAmB,IAAnB,CAAhC,CAFV,CAFA,CAKG,CACCJ,IAAI,CAAE,KAAKC,eADZ,CAECC,IAAI,CAAE,CAAC,OAAD,CAAU,0BAAV,CAAoCpD,UAAEqD,KAAF,CAAQ,KAAKE,QAAb,CAAuB,IAAvB,CAApC,CAFP,CALH,CAWA,CACIL,IAAI,CAAE,KAAKC,eADf,CAEIC,IAAI,CAAE,CAAC,OAAD,CAAU,qBAAV,CAA+BpD,UAAEqD,KAAF,CAAQ,KAAKxB,OAAb,CAAsB,IAAtB,CAA/B,CAFV,CAXA,CAiBA,CACIqB,IAAI,CAAE,cAAE,+BAAF,CADV,CAEIE,IAAI,CAAE,CAAC,OAAD,CAAUpD,UAAEqD,KAAF,CAAQ,KAAKtE,IAAb,CAAmB,IAAnB,CAAV,CAFV,CAjBA,CAuBA,CACImE,IAAI,CAAE,cAAE,MAAF,CADV,CAEIE,IAAI,CAAE,CAAC,SAAD,CAAYpD,UAAEqD,KAAF,CAAQ,KAAKG,aAAb,CAA4B,IAA5B,CAAZ,CAFV,CAvBA,EA4BA,GAAIjC,CAAU,CAAClB,WAAf,CAA4B,CACxB,GAAIoD,CAAAA,CAAU,CAAG,KAAK/B,aAAL,CAAmBH,CAAnB,CAAjB,CACA,KAAK0B,SAAL,CAAeD,IAAf,CAAoB,CAChBE,IAAI,CAAEO,CADU,CAEhBL,IAAI,CAAE,CAAC,OAAD,CAAUpD,UAAEqD,KAAF,CAAQ,SAASvE,CAAT,CAAY,CAChC,GAAmE,CAA/D,iBAAEA,CAAC,CAAC2C,MAAJ,EAAYiC,OAAZ,CAAoB,gCAApB,EAAoD9D,MAAxD,CAAsE,CAElElB,MAAM,CAACsD,UAAP,CAAkBhC,UAAEqD,KAAF,CAAQ,KAAKC,IAAb,CAAmB,IAAnB,CAAlB,CAA4C,GAA5C,CACH,CACJ,CALe,CAKb,IALa,CAAV,CAFU,CAApB,CASH,CAED,KAAKL,SAAL,CAAe7D,OAAf,CAAuB,SAAUuE,CAAV,CAAoB,CACvCA,CAAQ,CAACT,IAAT,CAAcU,EAAd,CAAiBrF,KAAjB,CAAuBoF,CAAQ,CAACT,IAAhC,CAAsCS,CAAQ,CAACP,IAA/C,CACH,CAFD,EAIA,MAAO,KACV,C,+DASoB,CAEjB,GAAI,KAAKH,SAAT,CAAoB,CAChB,KAAKA,SAAL,CAAe7D,OAAf,CAAuB,SAASuE,CAAT,CAAmB,CACtCA,CAAQ,CAACT,IAAT,CAAcW,GAAd,CAAkBtF,KAAlB,CAAwBoF,CAAQ,CAACT,IAAjC,CAAuCS,CAAQ,CAACP,IAAhD,CACH,CAFD,CAGH,CACD,KAAKH,SAAL,CAAiB,EAAjB,CAEA,MAAO,KACV,C,8CAUU1B,C,CAAY,CAEnB,KAAKuC,iBAAL,CAAyBvC,CAAzB,CACA,KAAKwC,oBAAL,CAA0BxC,CAAU,CAACb,UAArC,EAGA,GAAIlB,CAAAA,CAAQ,CAAG,cAAE,KAAKwE,kBAAL,EAAF,CAAf,CAGAxE,CAAQ,CAACyE,IAAT,CAAc,8BAAd,EACKC,IADL,CACU3C,CAAU,CAAC4C,KADrB,EAIA3E,CAAQ,CAACyE,IAAT,CAAc,6BAAd,EACKC,IADL,CACU3C,CAAU,CAACkB,IADrB,EAIA,GAAI,KAAK2B,WAAL,CAAiB7C,CAAU,CAACb,UAA5B,CAAJ,CAA6C,CACzClB,CAAQ,CAACyE,IAAT,CAAc,0BAAd,EAAwCI,IAAxC,CAA6C,UAA7C,IACH,CAFD,IAEO,CACH7E,CAAQ,CAACyE,IAAT,CAAc,0BAAd,EAAwCI,IAAxC,CAA6C,UAA7C,IACH,CAGD,GAAI,KAAKC,UAAL,CAAgB/C,CAAU,CAACb,UAA3B,CAAJ,CAA4C,CACxClB,CAAQ,CAACyE,IAAT,CAAc,sBAAd,EAAoCI,IAApC,CAAyC,UAAzC,IACH,CAFD,IAEO,CACH7E,CAAQ,CAACyE,IAAT,CAAc,sBAAd,EAAoCI,IAApC,CAAyC,UAAzC,IACH,CAED7E,CAAQ,CAACyE,IAAT,CAAc,0BAAd,EAAwCM,IAAxC,CAA6C,MAA7C,CAAqD,QAArD,EACA/E,CAAQ,CAACyE,IAAT,CAAc,sBAAd,EAAoCM,IAApC,CAAyC,MAAzC,CAAiD,QAAjD,EACA/E,CAAQ,CAACyE,IAAT,CAAc,qBAAd,EAAmCM,IAAnC,CAAwC,MAAxC,CAAgD,QAAhD,EAGAhD,CAAU,CAAC/B,QAAX,CAAsBA,CAAtB,CAGA,KAAKgF,aAAL,CAAmBjD,CAAnB,EAIA,KAAKkD,oBAAL,CAA0BlD,CAA1B,EAEA,MAAO,KACV,C,+DAQoB,CACjB,MAAO,cAAE,KAAK9B,eAAP,EAAwBiF,KAAxB,EACV,C,oDAUanD,C,CAAY,IAElB4B,CAAAA,CAAe,CAAG,cAAE,4CAAF,EACjBe,IADiB,CACZ3C,CAAU,CAAC/B,QADC,EAEjBT,IAFiB,EAFA,CAOlB4F,CAAe,CAAG,cAAE,YAAF,EACjBC,IADiB,OAPA,CAUtB,GAAI,KAAKpD,qBAAL,CAA2BD,CAA3B,CAAJ,CAA4C,CACxC,GAAIkC,CAAAA,CAAU,CAAG,KAAK/B,aAAL,CAAmBH,CAAnB,CAAjB,CAEAkC,CAAU,CAACZ,IAAX,CAAgB,WAAhB,CAA6B,QAA7B,EAEA,GAAIgC,CAAAA,CAAM,CAAG,KAAKC,eAAL,CAAqBrB,CAArB,CAAb,CACA,GAAIoB,CAAJ,CAAY,CACRtD,CAAU,CAACsD,MAAX,CAAoBA,CAAM,CAAG,CAChC,CAED,GAAItD,CAAU,CAACsD,MAAf,CAAuB,CACnB1B,CAAe,CAAC4B,GAAhB,CAAoB,QAApB,CAA8BxD,CAAU,CAACsD,MAAX,CAAoB,CAAlD,CACH,CAGD,KAAKG,gBAAL,CAAsBzD,CAAtB,EAEA,cAAE0D,QAAQ,CAACxC,IAAX,EAAiByC,MAAjB,CAAwB/B,CAAxB,EACA,KAAKA,eAAL,CAAuBA,CAAvB,CAIA,KAAKA,eAAL,CAAqB4B,GAArB,CAAyB,CACrBI,GAAG,CAAE,CADgB,CAErBC,IAAI,CAAE,CAFe,CAAzB,EAKAT,CAAe,CACVU,OADL,CACa,CACLC,SAAS,CAAE,KAAKC,kBAAL,CAAwBhE,CAAxB,CADN,CADb,EAGOiE,OAHP,GAGiBC,IAHjB,CAGsB,UAAW,CACrB,KAAKC,YAAL,CAAkBnE,CAAlB,EACA,KAAKoE,UAAL,CAAgBpE,CAAhB,CAEH,CAJa,CAIZU,IAJY,CAIP,IAJO,CAHtB,EAQS2D,KART,CAQe,UAAW,CAEjB,CAVT,CAYH,CAvCD,IAuCO,IAAIrE,CAAU,CAAChB,MAAf,CAAuB,CAC1BgB,CAAU,CAACsE,QAAX,IAGAtE,CAAU,CAACmB,QAAX,CAAsB,cAAE,MAAF,EAAUE,KAAV,EAAtB,CACArB,CAAU,CAACoB,WAAX,CAAyB,QAAzB,CAGA,KAAKqC,gBAAL,CAAsBzD,CAAtB,EAGA4B,CAAe,CAAC2C,QAAhB,CAAyB,QAAzB,EAGA,cAAEb,QAAQ,CAACxC,IAAX,EAAiByC,MAAjB,CAAwB/B,CAAxB,EACA,KAAKA,eAAL,CAAuBA,CAAvB,CAEA,KAAKA,eAAL,CAAqB4C,MAArB,CAA4B,KAAKC,2BAAL,EAA5B,EACA,KAAK7C,eAAL,CAAqB4B,GAArB,CAAyB,UAAzB,CAAqC,OAArC,EAEA,KAAKkB,iBAAL,CAAyB,GAAIC,UAAJ,CACrB,cAAE,MAAF,CADqB,CAErB,KAAK/C,eAAL,CAAqB,CAArB,CAFqB,CAEI,CACrBgD,eAAe,GADM,CAErBhG,SAAS,CAAEoB,CAAU,CAACpB,SAAX,CAAuB,QAFb,CAGrBiG,YAAY,CAAE,uBAHO,CAKrBC,SAAS,CAAE,CACPtH,IAAI,CAAE,CACFuH,OAAO,GADL,CADC,CAIPC,UAAU,CAAE,CACRC,MAAM,CAAE,IADA,CAERF,OAAO,GAFC,CAJL,CALU,CAFJ,CAAzB,CAmBA,KAAKX,UAAL,CAAgBpE,CAAhB,CACH,CAED,MAAO,KACV,C,8CAUUA,C,CAAY,CAEnB,KAAK4B,eAAL,CAAqBsD,MAArB,CAA4B,EAA5B,CAAgCzG,UAAEqD,KAAF,CAAQ,UAAW,CAE3C,KAAKqD,YAAL,CAAkBnF,CAAlB,EAGA,KAAK4B,eAAL,CAAqBwD,KAArB,GACAjI,MAAM,CAACsD,UAAP,CAAkBhC,UAAEqD,KAAF,CAAQ,UAAW,CAIjC,GAAI,KAAKF,eAAT,CAA0B,CACtB,KAAKA,eAAL,CAAqBwD,KAArB,EACH,CACJ,CAPiB,CAOf,IAPe,CAAlB,CAOU,GAPV,CASH,CAf2B,CAezB,IAfyB,CAAhC,EAiBA,MAAO,KACV,C,kDAUYpF,C,CAAY,CAMrB,GAAIqF,CAAAA,CAAM,CAAG,aAAe,KAAK/H,QAApB,CAA+B,GAA/B,CAAqC0C,CAAU,CAACb,UAA7D,CACA,KAAKyC,eAAL,CAAqBoB,IAArB,CAA0B,IAA1B,CAAgCqC,CAAhC,EAEA,GAAIC,CAAAA,CAAU,CAAG,KAAK1D,eAAL,CAAqBc,IAArB,CAA0B,6BAA1B,EAAuDrB,KAAvD,EAAjB,CACAiE,CAAU,CAACtC,IAAX,CAAgB,IAAhB,CAAsBqC,CAAM,CAAG,OAA/B,EACAC,CAAU,CAACtC,IAAX,CAAgB,MAAhB,CAAwB,UAAxB,EAEA,GAAIuC,CAAAA,CAAY,CAAG,KAAK3D,eAAL,CAAqBc,IAArB,CAA0B,8BAA1B,EAAwDrB,KAAxD,EAAnB,CACAkE,CAAY,CAACvC,IAAb,CAAkB,IAAlB,CAAwBqC,CAAM,CAAG,QAAjC,EACAE,CAAY,CAACvC,IAAb,CAAkB,iBAAlB,CAAqCqC,CAAM,CAAG,OAA9C,EAGA,KAAKzD,eAAL,CAAqBoB,IAArB,CAA0B,MAA1B,CAAkC,QAAlC,EACA,KAAKpB,eAAL,CAAqBoB,IAArB,CAA0B,UAA1B,CAAsC,CAAtC,EACA,KAAKpB,eAAL,CAAqBoB,IAArB,CAA0B,iBAA1B,CAA6CqC,CAAM,CAAG,QAAtD,EACA,KAAKzD,eAAL,CAAqBoB,IAArB,CAA0B,kBAA1B,CAA8CqC,CAAM,CAAG,OAAvD,EAGA,GAAInF,CAAAA,CAAM,CAAG,KAAKC,aAAL,CAAmBH,CAAnB,CAAb,CACA,GAAIE,CAAJ,CAAY,CACR,GAAI,CAACA,CAAM,CAAC8C,IAAP,CAAY,UAAZ,CAAL,CAA8B,CAC1B9C,CAAM,CAAC8C,IAAP,CAAY,UAAZ,CAAwB,CAAxB,CACH,CAED9C,CAAM,CACDoB,IADL,CACU,sBADV,CACkCpB,CAAM,CAAC8C,IAAP,CAAY,kBAAZ,CADlC,EAEKA,IAFL,CAEU,kBAFV,CAE8BqC,CAAM,CAAG,OAFvC,CAIH,CAED,KAAKG,iBAAL,CAAuBxF,CAAvB,EAEA,MAAO,KACV,C,oDAQazC,C,CAAG,CACb,GAAIkI,CAAAA,CAAgB,CAAG,iEAAvB,CACAA,CAAgB,EAAI,4CAApB,CACA,OAAQlI,CAAC,CAACmI,OAAV,EACI,IAAK,GAAL,CACI,KAAKpF,OAAL,GACA,MAGJ,IAAK,EAAL,CAEI,CAAC,UAAW,CACR,GAAI,CAAC,KAAKiC,iBAAL,CAAuBoD,WAA5B,CAAyC,CAErC,MACH,CAJO,GAOJC,CAAAA,CAAa,CAAG,cAAElC,QAAQ,CAACkC,aAAX,CAPZ,CAQJC,CAAU,CAAG,KAAK1F,aAAL,CAAmB,KAAKoC,iBAAxB,CART,CASJuD,CAAa,CAAG,cAAEL,CAAF,CATZ,CAUJM,CAAe,CAAG,cAAE,oCAAF,CAVd,CAWJC,CAXI,CAaR,GAAIH,CAAJ,CAAgB,CACZC,CAAa,CAAGA,CAAa,CAACG,MAAd,CAAqB,SAASC,CAAT,CAAgBvH,CAAhB,CAAyB,CAC1D,MAAsB,KAAf,GAAAkH,CAAU,GACTA,CAAU,CAACM,GAAX,CAAexH,CAAf,EAAwBN,MAAxB,EACG0H,CAAe,CAACI,GAAhB,CAAoBxH,CAApB,EAA6BN,MADhC,EAEGwH,CAAU,CAACzF,EAAX,CAAczB,CAAd,CAFH,EAGGoH,CAAe,CAAC3F,EAAhB,CAAmBzB,CAAnB,CAJM,CAKpB,CANe,CAOnB,CAGDmH,CAAa,CAACM,IAAd,CAAmB,SAASF,CAAT,CAAgBvH,CAAhB,CAAyB,CACxC,GAAIiH,CAAa,CAACxF,EAAd,CAAiBzB,CAAjB,CAAJ,CAA+B,CAC3BqH,CAAY,CAAGE,CAAf,CACA,QACH,CAED,QACH,CAPD,EAxBQ,GAiCJG,CAAAA,CAjCI,CAkCJC,CAlCI,CAmCJC,CAnCI,CAoCR,GAAoB,IAAK,EAArB,EAAAP,CAAJ,CAA4B,CACxB,GAAI/G,CAAAA,CAAS,CAAG,CAAhB,CACA,GAAI1B,CAAC,CAACiJ,QAAN,CAAgB,CACZvH,CAAS,CAAG,CAAC,CAChB,CACDoH,CAAS,CAAGL,CAAZ,CACA,EAAG,CACCK,CAAS,EAAIpH,CAAb,CACAqH,CAAQ,CAAG,cAAER,CAAa,CAACO,CAAD,CAAf,CACd,CAHD,MAGSC,CAAQ,CAACjI,MAAT,EAAmBiI,CAAQ,CAAClG,EAAT,CAAY,WAAZ,CAAnB,EAA+CkG,CAAQ,CAAClG,EAAT,CAAY,SAAZ,CAHxD,EAIA,GAAIkG,CAAQ,CAACjI,MAAb,CAAqB,CAEjBkI,CAAa,CAAGD,CAAQ,CAACG,OAAT,CAAiBZ,CAAjB,EAA6BxH,MAA7C,CACAkI,CAAa,CAAGA,CAAa,EAAID,CAAQ,CAACG,OAAT,CAAiB,KAAK7E,eAAtB,EAAuCvD,MAC3E,CAJD,IAIO,CAEHkI,CAAa,GAChB,CACJ,CAED,GAAIA,CAAJ,CAAmB,CACfD,CAAQ,CAAClB,KAAT,EACH,CAFD,IAEO,CACH,GAAI7H,CAAC,CAACiJ,QAAN,CAAgB,CAEZ,KAAK5E,eAAL,CAAqBc,IAArB,CAA0B+C,CAA1B,EAA4CiB,IAA5C,GAAmDtB,KAAnD,EACH,CAHD,IAGO,CACH,GAAI,KAAK7C,iBAAL,CAAuB+B,QAA3B,CAAqC,CAEjC,KAAK1C,eAAL,CAAqBwD,KAArB,EACH,CAHD,IAGO,CAEHS,CAAU,CAACT,KAAX,EACH,CACJ,CACJ,CACD7H,CAAC,CAACoJ,cAAF,EACH,CAzED,EAyEGnF,IAzEH,CAyEQ,IAzER,EA0EA,MAlFR,CAoFH,C,4CAUSoF,C,CAAS,CACf,GAAI,KAAK1J,OAAL,EAAmC,WAAnB,QAAO0J,CAAAA,CAA3B,CAAoD,CAChD,GAAIC,CAAAA,CAAiB,CAAG,KAAK3J,OAAL,CAAa4J,OAAb,CAAqB,KAAKzJ,UAA1B,CAAxB,CACA,GAAIwJ,CAAJ,CAAuB,CACnB,GAAIE,CAAAA,CAAc,CAAG7H,QAAQ,CAAC2H,CAAD,CAAoB,EAApB,CAA7B,CACA,GAAIE,CAAc,EAAI,KAAKrJ,KAAL,CAAWW,MAAjC,CAAyC,CACrCuI,CAAO,CAAGG,CACb,CACJ,CACJ,CAED,GAAuB,WAAnB,QAAOH,CAAAA,CAAX,CAAoC,CAChCA,CAAO,CAAG,KAAKnH,oBAAL,EACb,CAED,KAAKmB,iBAAL,CAAuB,aAAvB,CAAsCgG,CAAtC,EACA,KAAKvG,QAAL,CAAcuG,CAAd,EACA,KAAKhG,iBAAL,CAAuB,YAAvB,CAAqCgG,CAArC,EAEA,MAAO,KACV,C,iDASa,CACV,MAAO,MAAKI,SAAL,CAAe,CAAf,CACV,C,yCASS,CACN,KAAKpG,iBAAL,CAAuB,WAAvB,EAEA,GAAI,KAAK2B,iBAAT,CAA4B,CACxB,GAAI0E,CAAAA,CAAc,CAAG,KAAK9G,aAAL,CAAmB,KAAKoC,iBAAxB,CAArB,CACA,GAAI0E,CAAJ,CAAoB,CAChB,GAAI,CAACA,CAAc,CAACjE,IAAf,CAAoB,UAApB,CAAL,CAAsC,CAClCiE,CAAc,CAACjE,IAAf,CAAoB,UAApB,CAAgC,IAAhC,CACH,CACDiE,CAAc,CAAC7B,KAAf,EACH,CACJ,CAED,KAAK5H,IAAL,KAEA,KAAKoD,iBAAL,CAAuB,UAAvB,EAEA,MAAO,KACV,C,kCAUIsG,C,CAAY,CACb,KAAKtG,iBAAL,CAAuB,YAAvB,EAEA,GAAI,KAAKgB,eAAL,EAAwB,KAAKA,eAAL,CAAqBvD,MAAjD,CAAyD,CACrD,KAAKuD,eAAL,CAAqBpE,IAArB,GACA,GAAI,KAAKkH,iBAAT,CAA4B,CACxB,KAAKA,iBAAL,CAAuByC,OAAvB,EACH,CACJ,CAGD,GAAI,KAAK5E,iBAAT,CAA4B,CACxB,GAAIrC,CAAAA,CAAM,CAAG,KAAKC,aAAL,CAAmB,KAAKoC,iBAAxB,CAAb,CACA,GAAIrC,CAAJ,CAAY,CACR,GAAIA,CAAM,CAACoB,IAAP,CAAY,qBAAZ,CAAJ,CAAwC,CACpCpB,CAAM,CAAC8C,IAAP,CAAY,iBAAZ,CAA+B9C,CAAM,CAACoB,IAAP,CAAY,qBAAZ,CAA/B,CACH,CAED,GAAIpB,CAAM,CAACoB,IAAP,CAAY,sBAAZ,CAAJ,CAAyC,CACrCpB,CAAM,CAAC8C,IAAP,CAAY,kBAAZ,CAAgC9C,CAAM,CAACoB,IAAP,CAAY,sBAAZ,CAAhC,CACH,CAED,GAAIpB,CAAM,CAACoB,IAAP,CAAY,mBAAZ,CAAJ,CAAsC,CAClCpB,CAAM,CAAC8C,IAAP,CAAY,UAAZ,CAAwB9C,CAAM,CAACoB,IAAP,CAAY,UAAZ,CAAxB,CACH,CACJ,CAGD,KAAKiB,iBAAL,CAAyB,IAC5B,CAED,GAAI6E,CAAAA,CAAQ,CAAG,CAAf,CACA,GAAIF,CAAJ,CAAgB,CACZE,CAAQ,CAAG,GACd,CAGD,cAAE,sCAAF,EAAwCC,MAAxC,GACA,cAAE,oCAAF,EAAsCC,UAAtC,CAAiD,gBAAjD,EACA,cAAE,+BAAF,EAAiCC,OAAjC,CAAyCH,CAAzC,CAAmD,UAAW,CAC1D,cAAE,IAAF,EAAQC,MAAR,EACH,CAFD,EAKA,GAAI,KAAKzF,eAAL,EAAwB,KAAKA,eAAL,CAAqBvD,MAAjD,CAAyD,CACrD,GAAIgH,CAAAA,CAAM,CAAG,KAAKzD,eAAL,CAAqBoB,IAArB,CAA0B,IAA1B,CAAb,CACA,GAAIqC,CAAJ,CAAY,CACR,GAAImC,CAAAA,CAAkB,CAAG,uBAAwBnC,CAAxB,CAAiC,UAA1D,CACA,cAAEmC,CAAF,EAAsBF,UAAtB,CAAiC,UAAjC,EACA,cAAEE,CAAF,EAAsBF,UAAtB,CAAiC,kBAAjC,CACH,CACJ,CAGD,KAAK7J,kBAAL,GAEA,KAAKgK,iBAAL,GAEA,KAAK7G,iBAAL,CAAuB,WAAvB,EAEA,KAAKgB,eAAL,CAAuB,IAAvB,CACA,KAAK8C,iBAAL,CAAyB,IAAzB,CACA,MAAO,KACV,C,mCASM,CAEH,GAAIkC,CAAAA,CAAO,CAAG,KAAKnH,oBAAL,EAAd,CAEA,MAAO,MAAKY,QAAL,CAAcuG,CAAd,CACV,C,2DAQkB,CACf,MAAO,cAAE,KAAKhF,eAAP,CACV,C,8DASkB5B,C,CAAY,IACvB+D,CAAAA,CAAS,CAAG,cAAE5G,MAAF,EAAU4G,SAAV,EADW,CAEvB2D,CAAc,CAAG,cAAEvK,MAAF,EAAUwK,MAAV,EAFM,CAGvBzF,CAAU,CAAG,KAAK/B,aAAL,CAAmBH,CAAnB,CAHU,CAK3B,GAA6B,KAAzB,GAAAA,CAAU,CAACpB,SAAf,CAAoC,CAEhCmF,CAAS,CAAG7B,CAAU,CAACsC,MAAX,GAAoBZ,GAApB,CAA2B8D,CAAc,CAAG,CAC3D,CAHD,IAGO,IAA6B,QAAzB,GAAA1H,CAAU,CAACpB,SAAf,CAAuC,CAE1CmF,CAAS,CAAG7B,CAAU,CAACsC,MAAX,GAAoBZ,GAApB,CAA0B1B,CAAU,CAACyF,MAAX,EAA1B,CAAiDD,CAAc,CAAG,CACjF,CAHM,IAGA,IAAIxF,CAAU,CAACyF,MAAX,IAAyC,EAAjB,CAAAD,CAA5B,CAAmD,CAEtD3D,CAAS,CAAG7B,CAAU,CAACsC,MAAX,GAAoBZ,GAApB,CAA2B,CAAC8D,CAAc,CAAGxF,CAAU,CAACyF,MAAX,EAAlB,EAAyC,CACnF,CAHM,IAGA,CAGH5D,CAAS,CAAG7B,CAAU,CAACsC,MAAX,GAAoBZ,GAApB,CAA4C,EAAjB,CAAA8D,CAC1C,CAGD3D,CAAS,CAAG6D,IAAI,CAACC,GAAL,CAAS,CAAT,CAAY9D,CAAZ,CAAZ,CAGAA,CAAS,CAAG6D,IAAI,CAACE,GAAL,CAAS,cAAEpE,QAAF,EAAYiE,MAAZ,GAAuBD,CAAhC,CAAgD3D,CAAhD,CAAZ,CAEA,MAAO6D,CAAAA,IAAI,CAACG,IAAL,CAAUhE,CAAV,CACV,C,iFAQ6B,IACtB2D,CAAAA,CAAc,CAAG,cAAEvK,MAAF,EAAUwK,MAAV,EADK,CAEtBK,CAAU,CAAG,KAAKpG,eAAL,CAAqB+F,MAArB,EAFS,CAItBM,CAAa,CAAG,cAAE9K,MAAF,EAAU+K,KAAV,EAJM,CAKtBC,CAAS,CAAG,KAAKvG,eAAL,CAAqBsG,KAArB,EALU,CAO1B,MAAO,CACHtE,GAAG,CAAEgE,IAAI,CAACG,IAAL,CAAU,CAACL,CAAc,CAAGM,CAAlB,EAAgC,CAA1C,CADF,CAEHnE,IAAI,CAAE+D,IAAI,CAACG,IAAL,CAAU,CAACE,CAAa,CAAGE,CAAjB,EAA8B,CAAxC,CAFH,CAIV,C,kDAUYnI,C,CAAY,CACrB,GAAIiB,CAAAA,CAAO,CAAG,KAAKW,eAAnB,CACA,GAAI,CAACX,CAAD,EAAY,CAACA,CAAO,CAAC5C,MAAzB,CAAiC,CAE7B,MAAO,KACV,CAED,GAAI+J,CAAAA,CAAJ,CACA,OAAQpI,CAAU,CAACpB,SAAnB,EACI,IAAK,MAAL,CACIwJ,CAAY,CAAG,CAAC,MAAD,CAAS,OAAT,CAAkB,KAAlB,CAAyB,QAAzB,CAAf,CACA,MACJ,IAAK,OAAL,CACIA,CAAY,CAAG,CAAC,OAAD,CAAU,MAAV,CAAkB,KAAlB,CAAyB,QAAzB,CAAf,CACA,MACJ,IAAK,KAAL,CACIA,CAAY,CAAG,CAAC,KAAD,CAAQ,QAAR,CAAkB,OAAlB,CAA2B,MAA3B,CAAf,CACA,MACJ,IAAK,QAAL,CACIA,CAAY,CAAG,CAAC,QAAD,CAAW,KAAX,CAAkB,OAAlB,CAA2B,MAA3B,CAAf,CACA,MACJ,QACIA,CAAY,CAAG,MAAf,CACA,MAfR,CARqB,GA0BjBlI,CAAAA,CAAM,CAAG,KAAKC,aAAL,CAAmBH,CAAnB,CA1BQ,CA2BjBtD,CAAM,CAAG,CACTkC,SAAS,CAAEoB,CAAU,CAACpB,SAAX,CAAuB,QADzB,CAETgG,eAAe,GAFN,CAGTE,SAAS,CAAE,CACPuD,IAAI,CAAE,CACFC,SAAS,CAAEF,CADT,CADC,CAIPG,KAAK,CAAE,CACH5J,OAAO,CAAE,uBADN,CAJA,CAHF,CAWT6J,QAAQ,CAAE,kBAASlH,CAAT,CAAe,CACrBmH,CAAwB,CAACnH,CAAD,CAC3B,CAbQ,CAcToH,QAAQ,CAAE,kBAASpH,CAAT,CAAe,CACrBmH,CAAwB,CAACnH,CAAD,CAC3B,CAhBQ,CA3BQ,CA8CjBmH,CAAwB,CAAG,SAASnH,CAAT,CAAe,IACtC1C,CAAAA,CAAS,CAAG0C,CAAI,CAAC1C,SAAL,CAAe+J,KAAf,CAAqB,GAArB,EAA0B,CAA1B,CAD0B,CAEpCC,CAAU,CAA4C,CAAC,CAA1C,IAAC,MAAD,CAAS,OAAT,EAAkBC,OAAlB,CAA0BjK,CAA1B,CAFuB,CAGpCiG,CAAY,CAAGvD,CAAI,CAACwH,QAAL,CAAcC,MAAd,CAAqBC,aAArB,CAAmC,uBAAnC,CAHqB,CAIpCC,CAAW,CAAG,cAAE3H,CAAI,CAACwH,QAAL,CAAcC,MAAd,CAAqBC,aAArB,CAAmC,gCAAnC,CAAF,CAJsB,CAK1C,GAAIJ,CAAJ,CAAgB,IACRM,CAAAA,CAAW,CAAGC,UAAU,CAAChM,MAAM,CAACiM,gBAAP,CAAwBvE,CAAxB,EAAsC8C,MAAvC,CADhB,CAER0B,CAAW,CAAGF,UAAU,CAAChM,MAAM,CAACiM,gBAAP,CAAwBvE,CAAxB,EAAsCjB,GAAvC,CAFhB,CAGR0F,CAAY,CAAGH,UAAU,CAAChM,MAAM,CAACiM,gBAAP,CAAwB9H,CAAI,CAACwH,QAAL,CAAcC,MAAtC,EAA8CpB,MAA/C,CAHjB,CAIR4B,CAAY,CAAGJ,UAAU,CAAChM,MAAM,CAACiM,gBAAP,CAAwB9H,CAAI,CAACwH,QAAL,CAAcC,MAAtC,EAA8CnF,GAA/C,CAJjB,CAKR4F,CAAiB,CAAGL,UAAU,CAACF,CAAW,CAACzF,GAAZ,CAAgB,gBAAhB,CAAD,CALtB,CAMRiG,CAAuB,CAAwD,CAArD,CAAAN,UAAU,CAACF,CAAW,CAACzF,GAAZ,CAAgB,qBAAhB,CAAD,CAN5B,CAORkG,CAAQ,CAAGL,CAAW,CAAIH,CAAW,CAAG,CAPhC,CAQRS,CAAM,CAAGL,CAAY,CAAGC,CAAf,CAA8BC,CAA9B,CAAkDC,CARnD,CASRG,CAAM,CAAGL,CAAY,CAAGC,CAAf,CAAmCC,CATpC,CAUZ,GAAIC,CAAQ,EAAIC,CAAZ,EAAsBD,CAAQ,EAAIE,CAAtC,CAA8C,CAC1C,GAAIC,CAAAA,CAAW,CAAG,CAAlB,CACA,GAAIH,CAAQ,CAAIJ,CAAY,CAAG,CAA/B,CAAmC,CAC/BO,CAAW,CAAGF,CAAM,CAAGT,CAC1B,CAFD,IAEO,CACHW,CAAW,CAAGD,CAAM,CAAGV,CAC1B,CACD,cAAErE,CAAF,EAAgBrB,GAAhB,CAAoB,KAApB,CAA2BqG,CAA3B,CACH,CACJ,CAnBD,IAmBO,IACCC,CAAAA,CAAU,CAAGX,UAAU,CAAChM,MAAM,CAACiM,gBAAP,CAAwBvE,CAAxB,EAAsCqD,KAAvC,CADxB,CAECmB,CAAW,CAAGF,UAAU,CAAChM,MAAM,CAACiM,gBAAP,CAAwBvE,CAAxB,EAAsChB,IAAvC,CAFzB,CAGCkG,CAAW,CAAGZ,UAAU,CAAChM,MAAM,CAACiM,gBAAP,CAAwB9H,CAAI,CAACwH,QAAL,CAAcC,MAAtC,EAA8Cb,KAA/C,CAHzB,CAICqB,CAAY,CAAGJ,UAAU,CAAChM,MAAM,CAACiM,gBAAP,CAAwB9H,CAAI,CAACwH,QAAL,CAAcC,MAAtC,EAA8ClF,IAA/C,CAJ1B,CAKC2F,CAAiB,CAAGL,UAAU,CAACF,CAAW,CAACzF,GAAZ,CAAgB,gBAAhB,CAAD,CAL/B,CAMCiG,CAAuB,CAAwD,CAArD,CAAAN,UAAU,CAACF,CAAW,CAACzF,GAAZ,CAAgB,qBAAhB,CAAD,CANrC,CAOCkG,CAAQ,CAAGL,CAAW,CAAIS,CAAU,CAAG,CAPxC,CAQCH,CAAM,CAAGI,CAAW,CAAGR,CAAd,CAA6BC,CAA7B,CAAiDC,CAR3D,CASCG,CAAM,CAAGL,CAAY,CAAGC,CAAf,CAAmCC,CAT7C,CAUH,GAAIC,CAAQ,EAAIC,CAAZ,EAAsBD,CAAQ,EAAIE,CAAtC,CAA8C,CAC1C,GAAIC,CAAAA,CAAW,CAAG,CAAlB,CACA,GAAIH,CAAQ,CAAIK,CAAW,CAAG,CAA9B,CAAkC,CAC9BF,CAAW,CAAGF,CAAM,CAAGG,CAC1B,CAFD,IAEO,CACHD,CAAW,CAAGD,CAAM,CAAGE,CAC1B,CACD,cAAEjF,CAAF,EAAgBrB,GAAhB,CAAoB,MAApB,CAA4BqG,CAA5B,CACH,CACJ,CACJ,CA1FoB,CA4FjBG,CAAU,CAAG,cAAE,sCAAF,CA5FI,CA6FrB,GAAIA,CAAU,CAAC3L,MAAf,CAAuB,CACnB6B,CAAM,CAAG8J,CACZ,CACD,KAAKtF,iBAAL,CAAyB,GAAIC,UAAJ,CAAWzE,CAAX,CAAmBe,CAAO,CAAC,CAAD,CAA1B,CAA+BvE,CAA/B,CAAzB,CAEA,MAAO,KACV,C,0DAUgBsD,C,CAAY,CACzB,GAAIA,CAAU,CAACiK,QAAf,CAAyB,CACrB,KAAK1H,iBAAL,CAAuBoD,WAAvB,IACA,GAAIsE,CAAAA,CAAQ,CAAG,cAAE,yCAAF,CAAf,CAEA,GAAIjK,CAAU,CAACsD,MAAf,CAAuB,CACnB,GAA+B,QAA3B,GAAAtD,CAAU,CAACoB,WAAf,CAAyC,CACrCpB,CAAU,CAACmB,QAAX,CAAoBwC,MAApB,CAA2BsG,CAA3B,CACH,CAFD,IAEO,CACHA,CAAQ,CAACC,WAAT,CAAqBlK,CAAU,CAACmB,QAAhC,CACH,CACJ,CAND,IAMO,CACH,cAAE,MAAF,EAAUwC,MAAV,CAAiBsG,CAAjB,CACH,CAED,GAAI,KAAKhK,qBAAL,CAA2BD,CAA3B,CAAJ,CAA4C,IAGpCgK,CAAAA,CAAU,CAAG,cAAE,gDAAF,CAHuB,CAKpC9H,CAAU,CAAG,KAAK/B,aAAL,CAAmBH,CAAnB,CALuB,CAOpCmK,CAAM,CAAG,EAP2B,CASpCC,CAAS,CAAGlI,CATwB,CAUxC,GAAIiI,CAAJ,CAAY,CACRC,CAAS,CAAG,cAAE,MAAF,CACf,CAEDJ,CAAU,CAACxG,GAAX,CAAe,CACX0E,KAAK,CAAEhG,CAAU,CAACmI,UAAX,GAA0BF,CAA1B,CAAmCA,CAD/B,CAEXxC,MAAM,CAAEzF,CAAU,CAACoI,WAAX,GAA2BH,CAA3B,CAAoCA,CAFjC,CAGXtG,IAAI,CAAE3B,CAAU,CAACsC,MAAX,GAAoBX,IAApB,CAA2BsG,CAHtB,CAIXvG,GAAG,CAAE1B,CAAU,CAACsC,MAAX,GAAoBZ,GAApB,CAA0BuG,CAJpB,CAKXI,eAAe,CAAE,KAAKC,kCAAL,CAAwCJ,CAAxC,CALN,CAAf,EAQA,GAAIlI,CAAU,CAACsC,MAAX,GAAoBX,IAApB,CAA2BsG,CAA/B,CAAuC,CACnCH,CAAU,CAACxG,GAAX,CAAe,CACX0E,KAAK,CAAEhG,CAAU,CAACmI,UAAX,GAA0BnI,CAAU,CAACsC,MAAX,GAAoBX,IAA9C,CAAqDsG,CADjD,CAEXtG,IAAI,CAAE3B,CAAU,CAACsC,MAAX,GAAoBX,IAFf,CAAf,CAIH,CAED,GAAI3B,CAAU,CAACsC,MAAX,GAAoBZ,GAApB,CAA0BuG,CAA9B,CAAsC,CAClCH,CAAU,CAACxG,GAAX,CAAe,CACXmE,MAAM,CAAEzF,CAAU,CAACoI,WAAX,GAA2BpI,CAAU,CAACsC,MAAX,GAAoBZ,GAA/C,CAAqDuG,CADlD,CAEXvG,GAAG,CAAE1B,CAAU,CAACsC,MAAX,GAAoBZ,GAFd,CAAf,CAIH,CAED,GAAI6G,CAAAA,CAAY,CAAGvI,CAAU,CAACsB,GAAX,CAAe,cAAf,CAAnB,CACA,GAAIiH,CAAY,EAAIA,CAAY,GAAK,cAAE,MAAF,EAAUjH,GAAV,CAAc,cAAd,CAArC,CAAoE,CAChEwG,CAAU,CAACxG,GAAX,CAAe,cAAf,CAA+BiH,CAA/B,CACH,CAED,GAAIC,CAAAA,CAAc,CAAG,KAAKC,iBAAL,CAAuBzI,CAAvB,CAArB,CACA,GAAuB,OAAnB,GAAAwI,CAAJ,CAAgC,CAC5BV,CAAU,CAACxG,GAAX,CAAe,KAAf,CAAsB,CAAtB,CACH,CAFD,IAEO,IAAuB,UAAnB,GAAAkH,CAAJ,CAAmC,CACtCV,CAAU,CAACxG,GAAX,CAAe,UAAf,CAA2B,OAA3B,CACH,CAED,GAAIoH,CAAAA,CAAK,CAAGZ,CAAU,CAAC7G,KAAX,EAAZ,CACAyH,CAAK,CAACpH,GAAN,CAAU,CACN+G,eAAe,CAAEN,CAAQ,CAACzG,GAAT,CAAa,iBAAb,CADX,CAENqH,OAAO,CAAEZ,CAAQ,CAACzG,GAAT,CAAa,SAAb,CAFH,CAAV,EAIAoH,CAAK,CAAC5H,IAAN,CAAW,gBAAX,CAA6B,uBAA7B,EAEA,GAAIhD,CAAU,CAACsD,MAAf,CAAuB,CACnB,GAA+B,QAA3B,GAAAtD,CAAU,CAACoB,WAAf,CAAyC,CACrCpB,CAAU,CAACmB,QAAX,CAAoBwC,MAApB,CAA2BqG,CAA3B,CACH,CAFD,IAEO,CACHY,CAAK,CAACV,WAAN,CAAkBlK,CAAU,CAACmB,QAA7B,EACA6I,CAAU,CAACE,WAAX,CAAuBlK,CAAU,CAACmB,QAAlC,CACH,CACJ,CAPD,IAOO,CACH,cAAE,MAAF,EAAUwC,MAAV,CAAiBiH,CAAjB,EACA,cAAE,MAAF,EAAUjH,MAAV,CAAiBqG,CAAjB,CACH,CAID9H,CAAU,CAACc,IAAX,CAAgB,gBAAhB,CAAkC,eAAlC,EAEA,GAAIhD,CAAU,CAACsD,MAAf,CAAuB,CACnB2G,CAAQ,CAACzG,GAAT,CAAa,QAAb,CAAuBxD,CAAU,CAACsD,MAAlC,EACA0G,CAAU,CAACxG,GAAX,CAAe,QAAf,CAAyBxD,CAAU,CAACsD,MAAX,CAAoB,CAA7C,EACApB,CAAU,CAACsB,GAAX,CAAe,QAAf,CAAyBxD,CAAU,CAACsD,MAAX,CAAoB,CAA7C,CACH,CAEDsH,CAAK,CAACrD,OAAN,CAAc,MAAd,CAAsB,UAAW,CAC7B,cAAE,IAAF,EAAQF,MAAR,EACH,CAFD,CAGH,CACJ,CACD,MAAO,KACV,C,wDASeyD,C,CAAM,CAClBA,CAAI,CAAG,cAAEA,CAAF,CAAP,CACA,MAAOA,CAAI,CAACzM,MAAL,EAAeyM,CAAI,CAAC,CAAD,CAAJ,GAAYpH,QAAlC,CAA4C,CAIxC,GAAIqH,CAAAA,CAAQ,CAAGD,CAAI,CAACtH,GAAL,CAAS,UAAT,CAAf,CACA,GAAiB,UAAb,GAAAuH,CAAQ,EAAgC,UAAb,GAAAA,CAA3B,EAAmE,OAAb,GAAAA,CAA1D,CAAgF,CAK5E,GAAIC,CAAAA,CAAK,CAAG9L,QAAQ,CAAC4L,CAAI,CAACtH,GAAL,CAAS,QAAT,CAAD,CAAqB,EAArB,CAApB,CACA,GAAI,CAACyH,KAAK,CAACD,CAAD,CAAN,EAA2B,CAAV,GAAAA,CAArB,CAAkC,CAC9B,MAAOA,CAAAA,CACV,CACJ,CACDF,CAAI,CAAGA,CAAI,CAACI,MAAL,EACV,CAED,MAAO,EACV,C,8FASkCJ,C,CAAM,CAErC,GAAIK,CAAAA,CAAQ,CAAG,cAAE,OAAF,EAAW3N,IAAX,EAAf,CACA,cAAE,MAAF,EAAUmG,MAAV,CAAiBwH,CAAjB,EACA,GAAIC,CAAAA,CAAa,CAAGD,CAAQ,CAAC3H,GAAT,CAAa,iBAAb,CAApB,CACA2H,CAAQ,CAAC9D,MAAT,GAEAyD,CAAI,CAAG,cAAEA,CAAF,CAAP,CACA,MAAOA,CAAI,CAACzM,MAAL,EAAeyM,CAAI,CAAC,CAAD,CAAJ,GAAYpH,QAAlC,CAA4C,CACxC,GAAI2H,CAAAA,CAAK,CAAGP,CAAI,CAACtH,GAAL,CAAS,iBAAT,CAAZ,CACA,GAAI6H,CAAK,GAAKD,CAAd,CAA6B,CACzB,MAAOC,CAAAA,CACV,CACDP,CAAI,CAAGA,CAAI,CAACI,MAAL,EACV,CAED,MAAO,KACV,C,4DASiBJ,C,CAAM,CACpBA,CAAI,CAAG,cAAEA,CAAF,CAAP,CACA,MAAOA,CAAI,CAACzM,MAAL,EAAeyM,CAAI,CAAC,CAAD,CAAJ,GAAYpH,QAAlC,CAA4C,CACxC,GAAIqH,CAAAA,CAAQ,CAAGD,CAAI,CAACtH,GAAL,CAAS,UAAT,CAAf,CACA,GAAiB,QAAb,GAAAuH,CAAJ,CAA2B,CACvB,MAAOA,CAAAA,CACV,CACDD,CAAI,CAAGA,CAAI,CAACI,MAAL,EACV,CAED,MAAO,KACV,C,6DASmB,IAEZI,CAAAA,CAAQ,CAAG,aAFC,CAGZC,CAAY,CAAG,SAASC,CAAT,CAAgB,CAC/B,GAAIC,CAAAA,CAAa,CAAGD,CAAK,CAAClK,IAAN,CAAW,WAAX,CAApB,CACA,GAAImK,CAAJ,CAAmB,CACf,OAAQA,CAAR,EACI,IAAK,WAAL,CACA,IAAK,QAAL,CACI,OAHR,CAKH,CAED,GAAIC,CAAAA,CAAM,CAAGF,CAAK,CAACxI,IAAN,CAAWsI,CAAX,CAAb,CACA,GAAI,CAACI,CAAL,CAAa,CACTF,CAAK,CAACxI,IAAN,uBACAwI,CAAK,CAACxI,IAAN,CAAWsI,CAAX,IACH,CACJ,CAlBe,CAoBhB,KAAK1J,eAAL,CAAqB+J,QAArB,GAAgCvF,IAAhC,CAAqC,SAASF,CAAT,CAAgBvE,CAAhB,CAAsB,CACvD4J,CAAY,CAAC,cAAE5J,CAAF,CAAD,CACf,CAFD,EAGA,KAAKC,eAAL,CAAqBgK,YAArB,CAAkC,MAAlC,EAA0CD,QAA1C,GAAqDvF,IAArD,CAA0D,SAASF,CAAT,CAAgBvE,CAAhB,CAAsB,CAC5E4J,CAAY,CAAC,cAAE5J,CAAF,CAAD,CACf,CAFD,CAGH,C,6DASmB,IAGZkK,CAAAA,CAAY,CAAG,SAASL,CAAT,CAAgB,CAC/B,GAAIE,CAAAA,CAAM,CAAGF,CAAK,CAACxI,IAAN,mBAAb,CACA,GAAsB,WAAlB,QAAO0I,CAAAA,CAAX,CAAmC,CAC/BF,CAAK,CAAClE,UAAN,oBACAkE,CAAK,CAAClE,UAAN,CALO,aAKP,CACH,CACJ,CATe,CAWhB,mCAA2BlB,IAA3B,CAAgC,SAASF,CAAT,CAAgBvE,CAAhB,CAAsB,CAClDkK,CAAY,CAAC,cAAElK,CAAF,CAAD,CACf,CAFD,CAGH,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see
.\n\n/**\n * Manage user tours in Moodle.\n *\n * @copyright 2018 Andrew Nicols
\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Popper from 'core/popper';\n\n/**\n * A Tour.\n *\n * @class Tour\n */\nexport default class Tour {\n /**\n * @param {object} config The configuration object.\n */\n constructor(config) {\n this.init(config);\n }\n\n /**\n * Initialise the tour.\n *\n * @method init\n * @param {Object} config The configuration object.\n * @chainable\n * @return {Object} this.\n */\n init(config) {\n // Unset all handlers.\n this.eventHandlers = {};\n\n // Reset the current tour states.\n this.reset();\n\n // Store the initial configuration.\n this.originalConfiguration = config || {};\n\n // Apply configuration.\n this.configure.apply(this, arguments);\n\n try {\n this.storage = window.sessionStorage;\n this.storageKey = 'tourstate_' + this.tourName;\n } catch (e) {\n this.storage = false;\n this.storageKey = '';\n }\n\n return this;\n }\n\n /**\n * Reset the current tour state.\n *\n * @method reset\n * @chainable\n * @return {Object} this.\n */\n reset() {\n // Hide the current step.\n this.hide();\n\n // Unset all handlers.\n this.eventHandlers = [];\n\n // Unset all listeners.\n this.resetStepListeners();\n\n // Unset the original configuration.\n this.originalConfiguration = {};\n\n // Reset the current step number and list of steps.\n this.steps = [];\n\n // Reset the current step number.\n this.currentStepNumber = 0;\n\n return this;\n }\n\n /**\n * Prepare tour configuration.\n *\n * @method configure\n * @param {Object} config The configuration object.\n * @chainable\n * @return {Object} this.\n */\n configure(config) {\n if (typeof config === 'object') {\n // Tour name.\n if (typeof config.tourName !== 'undefined') {\n this.tourName = config.tourName;\n }\n\n // Set up eventHandlers.\n if (config.eventHandlers) {\n for (let eventName in config.eventHandlers) {\n config.eventHandlers[eventName].forEach(function(handler) {\n this.addEventHandler(eventName, handler);\n }, this);\n }\n }\n\n // Reset the step configuration.\n this.resetStepDefaults(true);\n\n // Configure the steps.\n if (typeof config.steps === 'object') {\n this.steps = config.steps;\n }\n\n if (typeof config.template !== 'undefined') {\n this.templateContent = config.template;\n }\n }\n\n // Check that we have enough to start the tour.\n this.checkMinimumRequirements();\n\n return this;\n }\n\n /**\n * Check that the configuration meets the minimum requirements.\n *\n * @method checkMinimumRequirements\n */\n checkMinimumRequirements() {\n // Need a tourName.\n if (!this.tourName) {\n throw new Error(\"Tour Name required\");\n }\n\n // Need a minimum of one step.\n if (!this.steps || !this.steps.length) {\n throw new Error(\"Steps must be specified\");\n }\n }\n\n /**\n * Reset step default configuration.\n *\n * @method resetStepDefaults\n * @param {Boolean} loadOriginalConfiguration Whether to load the original configuration supplied with the Tour.\n * @chainable\n * @return {Object} this.\n */\n resetStepDefaults(loadOriginalConfiguration) {\n if (typeof loadOriginalConfiguration === 'undefined') {\n loadOriginalConfiguration = true;\n }\n\n this.stepDefaults = {};\n if (!loadOriginalConfiguration || typeof this.originalConfiguration.stepDefaults === 'undefined') {\n this.setStepDefaults({});\n } else {\n this.setStepDefaults(this.originalConfiguration.stepDefaults);\n }\n\n return this;\n }\n\n /**\n * Set the step defaults.\n *\n * @method setStepDefaults\n * @param {Object} stepDefaults The step defaults to apply to all steps\n * @chainable\n * @return {Object} this.\n */\n setStepDefaults(stepDefaults) {\n if (!this.stepDefaults) {\n this.stepDefaults = {};\n }\n $.extend(\n this.stepDefaults,\n {\n element: '',\n placement: 'top',\n delay: 0,\n moveOnClick: false,\n moveAfterTime: 0,\n orphan: false,\n direction: 1,\n },\n stepDefaults\n );\n\n return this;\n }\n\n /**\n * Retrieve the current step number.\n *\n * @method getCurrentStepNumber\n * @return {Integer} The current step number\n */\n getCurrentStepNumber() {\n return parseInt(this.currentStepNumber, 10);\n }\n\n /**\n * Store the current step number.\n *\n * @method setCurrentStepNumber\n * @param {Integer} stepNumber The current step number\n * @chainable\n */\n setCurrentStepNumber(stepNumber) {\n this.currentStepNumber = stepNumber;\n if (this.storage) {\n try {\n this.storage.setItem(this.storageKey, stepNumber);\n } catch (e) {\n if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {\n this.storage.removeItem(this.storageKey);\n }\n }\n }\n }\n\n /**\n * Get the next step number after the currently displayed step.\n *\n * @method getNextStepNumber\n * @param {Integer} stepNumber The current step number\n * @return {Integer} The next step number to display\n */\n getNextStepNumber(stepNumber) {\n if (typeof stepNumber === 'undefined') {\n stepNumber = this.getCurrentStepNumber();\n }\n let nextStepNumber = stepNumber + 1;\n\n // Keep checking the remaining steps.\n while (nextStepNumber <= this.steps.length) {\n if (this.isStepPotentiallyVisible(this.getStepConfig(nextStepNumber))) {\n return nextStepNumber;\n }\n nextStepNumber++;\n }\n\n return null;\n }\n\n /**\n * Get the previous step number before the currently displayed step.\n *\n * @method getPreviousStepNumber\n * @param {Integer} stepNumber The current step number\n * @return {Integer} The previous step number to display\n */\n getPreviousStepNumber(stepNumber) {\n if (typeof stepNumber === 'undefined') {\n stepNumber = this.getCurrentStepNumber();\n }\n let previousStepNumber = stepNumber - 1;\n\n // Keep checking the remaining steps.\n while (previousStepNumber >= 0) {\n if (this.isStepPotentiallyVisible(this.getStepConfig(previousStepNumber))) {\n return previousStepNumber;\n }\n previousStepNumber--;\n }\n\n return null;\n }\n\n /**\n * Is the step the final step number?\n *\n * @method isLastStep\n * @param {Integer} stepNumber Step number to test\n * @return {Boolean} Whether the step is the final step\n */\n isLastStep(stepNumber) {\n let nextStepNumber = this.getNextStepNumber(stepNumber);\n\n return nextStepNumber === null;\n }\n\n /**\n * Is the step the first step number?\n *\n * @method isFirstStep\n * @param {Integer} stepNumber Step number to test\n * @return {Boolean} Whether the step is the first step\n */\n isFirstStep(stepNumber) {\n let previousStepNumber = this.getPreviousStepNumber(stepNumber);\n\n return previousStepNumber === null;\n }\n\n /**\n * Is this step potentially visible?\n *\n * @method isStepPotentiallyVisible\n * @param {Object} stepConfig The step configuration to normalise\n * @return {Boolean} Whether the step is the potentially visible\n */\n isStepPotentiallyVisible(stepConfig) {\n if (!stepConfig) {\n // Without step config, there can be no step.\n return false;\n }\n\n if (this.isStepActuallyVisible(stepConfig)) {\n // If it is actually visible, it is already potentially visible.\n return true;\n }\n\n if (typeof stepConfig.orphan !== 'undefined' && stepConfig.orphan) {\n // Orphan steps have no target. They are always visible.\n return true;\n }\n\n if (typeof stepConfig.delay !== 'undefined' && stepConfig.delay) {\n // Only return true if the activated has not been used yet.\n return true;\n }\n\n // Not theoretically, or actually visible.\n return false;\n }\n\n /**\n * Is this step actually visible?\n *\n * @method isStepActuallyVisible\n * @param {Object} stepConfig The step configuration to normalise\n * @return {Boolean} Whether the step is actually visible\n */\n isStepActuallyVisible(stepConfig) {\n if (!stepConfig) {\n // Without step config, there can be no step.\n return false;\n }\n\n let target = this.getStepTarget(stepConfig);\n if (target && target.length && target.is(':visible')) {\n // Without a target, there can be no step.\n return !!target.length;\n }\n\n return false;\n }\n\n /**\n * Go to the next step in the tour.\n *\n * @method next\n * @chainable\n * @return {Object} this.\n */\n next() {\n return this.gotoStep(this.getNextStepNumber());\n }\n\n /**\n * Go to the previous step in the tour.\n *\n * @method previous\n * @chainable\n * @return {Object} this.\n */\n previous() {\n return this.gotoStep(this.getPreviousStepNumber(), -1);\n }\n\n /**\n * Go to the specified step in the tour.\n *\n * @method gotoStep\n * @param {Integer} stepNumber The step number to display\n * @param {Integer} direction Next or previous step\n * @chainable\n * @return {Object} this.\n */\n gotoStep(stepNumber, direction) {\n if (stepNumber < 0) {\n return this.endTour();\n }\n\n let stepConfig = this.getStepConfig(stepNumber);\n if (stepConfig === null) {\n return this.endTour();\n }\n\n return this._gotoStep(stepConfig, direction);\n }\n\n _gotoStep(stepConfig, direction) {\n if (!stepConfig) {\n return this.endTour();\n }\n\n if (typeof stepConfig.delay !== 'undefined' && stepConfig.delay && !stepConfig.delayed) {\n stepConfig.delayed = true;\n window.setTimeout(this._gotoStep.bind(this), stepConfig.delay, stepConfig, direction);\n\n return this;\n } else if (!stepConfig.orphan && !this.isStepActuallyVisible(stepConfig)) {\n let fn = direction == -1 ? 'getPreviousStepNumber' : 'getNextStepNumber';\n return this.gotoStep(this[fn](stepConfig.stepNumber), direction);\n }\n\n this.hide();\n\n this.fireEventHandlers('beforeRender', stepConfig);\n this.renderStep(stepConfig);\n this.fireEventHandlers('afterRender', stepConfig);\n\n return this;\n }\n\n /**\n * Fetch the normalised step configuration for the specified step number.\n *\n * @method getStepConfig\n * @param {Integer} stepNumber The step number to fetch configuration for\n * @return {Object} The step configuration\n */\n getStepConfig(stepNumber) {\n if (stepNumber === null || stepNumber < 0 || stepNumber >= this.steps.length) {\n return null;\n }\n\n // Normalise the step configuration.\n let stepConfig = this.normalizeStepConfig(this.steps[stepNumber]);\n\n // Add the stepNumber to the stepConfig.\n stepConfig = $.extend(stepConfig, {stepNumber: stepNumber});\n\n return stepConfig;\n }\n\n /**\n * Normalise the supplied step configuration.\n *\n * @method normalizeStepConfig\n * @param {Object} stepConfig The step configuration to normalise\n * @return {Object} The normalised step configuration\n */\n normalizeStepConfig(stepConfig) {\n\n if (typeof stepConfig.reflex !== 'undefined' && typeof stepConfig.moveAfterClick === 'undefined') {\n stepConfig.moveAfterClick = stepConfig.reflex;\n }\n\n if (typeof stepConfig.element !== 'undefined' && typeof stepConfig.target === 'undefined') {\n stepConfig.target = stepConfig.element;\n }\n\n if (typeof stepConfig.content !== 'undefined' && typeof stepConfig.body === 'undefined') {\n stepConfig.body = stepConfig.content;\n }\n\n stepConfig = $.extend({}, this.stepDefaults, stepConfig);\n\n stepConfig = $.extend({}, {\n attachTo: stepConfig.target,\n attachPoint: 'after',\n }, stepConfig);\n\n if (stepConfig.attachTo) {\n stepConfig.attachTo = $(stepConfig.attachTo).first();\n }\n\n return stepConfig;\n }\n\n /**\n * Fetch the actual step target from the selector.\n *\n * This should not be called until after any delay has completed.\n *\n * @method getStepTarget\n * @param {Object} stepConfig The step configuration\n * @return {$}\n */\n getStepTarget(stepConfig) {\n if (stepConfig.target) {\n return $(stepConfig.target);\n }\n\n return null;\n }\n\n /**\n * Fire any event handlers for the specified event.\n *\n * @param {String} eventName The name of the event to handle\n * @param {Object} data Any data to pass to the event\n * @chainable\n * @return {Object} this.\n */\n fireEventHandlers(eventName, data) {\n if (typeof this.eventHandlers[eventName] === 'undefined') {\n return this;\n }\n\n this.eventHandlers[eventName].forEach(function(thisEvent) {\n thisEvent.call(this, data);\n }, this);\n\n return this;\n }\n\n /**\n * @method addEventHandler\n * @param {string} eventName The name of the event to listen for\n * @param {function} handler The event handler to call\n * @return {Object} this.\n */\n addEventHandler(eventName, handler) {\n if (typeof this.eventHandlers[eventName] === 'undefined') {\n this.eventHandlers[eventName] = [];\n }\n\n this.eventHandlers[eventName].push(handler);\n\n return this;\n }\n\n /**\n * Process listeners for the step being shown.\n *\n * @method processStepListeners\n * @param {object} stepConfig The configuration for the step\n * @chainable\n * @return {Object} this.\n */\n processStepListeners(stepConfig) {\n this.listeners.push(\n // Next/Previous buttons.\n {\n node: this.currentStepNode,\n args: ['click', '[data-role=\"next\"]', $.proxy(this.next, this)]\n }, {\n node: this.currentStepNode,\n args: ['click', '[data-role=\"previous\"]', $.proxy(this.previous, this)]\n },\n\n // Close and end tour buttons.\n {\n node: this.currentStepNode,\n args: ['click', '[data-role=\"end\"]', $.proxy(this.endTour, this)]\n },\n\n // Click backdrop and hide tour.\n {\n node: $('[data-flexitour=\"backdrop\"]'),\n args: ['click', $.proxy(this.hide, this)]\n },\n\n // Keypresses.\n {\n node: $('body'),\n args: ['keydown', $.proxy(this.handleKeyDown, this)]\n });\n\n if (stepConfig.moveOnClick) {\n var targetNode = this.getStepTarget(stepConfig);\n this.listeners.push({\n node: targetNode,\n args: ['click', $.proxy(function(e) {\n if ($(e.target).parents('[data-flexitour=\"container\"]').length === 0) {\n // Ignore clicks when they are in the flexitour.\n window.setTimeout($.proxy(this.next, this), 500);\n }\n }, this)]\n });\n }\n\n this.listeners.forEach(function (listener) {\n listener.node.on.apply(listener.node, listener.args);\n });\n\n return this;\n }\n\n /**\n * Reset step listeners.\n *\n * @method resetStepListeners\n * @chainable\n * @return {Object} this.\n */\n resetStepListeners() {\n // Stop listening to all external handlers.\n if (this.listeners) {\n this.listeners.forEach(function(listener) {\n listener.node.off.apply(listener.node, listener.args);\n });\n }\n this.listeners = [];\n\n return this;\n }\n\n /**\n * The standard step renderer.\n *\n * @method renderStep\n * @param {Object} stepConfig The step configuration of the step\n * @chainable\n * @return {Object} this.\n */\n renderStep(stepConfig) {\n // Store the current step configuration for later.\n this.currentStepConfig = stepConfig;\n this.setCurrentStepNumber(stepConfig.stepNumber);\n\n // Fetch the template and convert it to a $ object.\n let template = $(this.getTemplateContent());\n\n // Title.\n template.find('[data-placeholder=\"title\"]')\n .html(stepConfig.title);\n\n // Body.\n template.find('[data-placeholder=\"body\"]')\n .html(stepConfig.body);\n\n // Is this the first step?\n if (this.isFirstStep(stepConfig.stepNumber)) {\n template.find('[data-role=\"previous\"]').prop('disabled', true);\n } else {\n template.find('[data-role=\"previous\"]').prop('disabled', false);\n }\n\n // Is this the final step?\n if (this.isLastStep(stepConfig.stepNumber)) {\n template.find('[data-role=\"next\"]').prop('disabled', true);\n } else {\n template.find('[data-role=\"next\"]').prop('disabled', false);\n }\n\n template.find('[data-role=\"previous\"]').attr('role', 'button');\n template.find('[data-role=\"next\"]').attr('role', 'button');\n template.find('[data-role=\"end\"]').attr('role', 'button');\n\n // Replace the template with the updated version.\n stepConfig.template = template;\n\n // Add to the page.\n this.addStepToPage(stepConfig);\n\n // Process step listeners after adding to the page.\n // This uses the currentNode.\n this.processStepListeners(stepConfig);\n\n return this;\n }\n\n /**\n * Getter for the template content.\n *\n * @method getTemplateContent\n * @return {$}\n */\n getTemplateContent() {\n return $(this.templateContent).clone();\n }\n\n /**\n * Helper to add a step to the page.\n *\n * @method addStepToPage\n * @param {Object} stepConfig The step configuration of the step\n * @chainable\n * @return {Object} this.\n */\n addStepToPage(stepConfig) {\n // Create the stepNode from the template data.\n let currentStepNode = $('')\n .html(stepConfig.template)\n .hide();\n\n // The scroll animation occurs on the body or html.\n let animationTarget = $('body, html')\n .stop(true, true);\n\n if (this.isStepActuallyVisible(stepConfig)) {\n let targetNode = this.getStepTarget(stepConfig);\n\n targetNode.data('flexitour', 'target');\n\n let zIndex = this.calculateZIndex(targetNode);\n if (zIndex) {\n stepConfig.zIndex = zIndex + 1;\n }\n\n if (stepConfig.zIndex) {\n currentStepNode.css('zIndex', stepConfig.zIndex + 1);\n }\n\n // Add the backdrop.\n this.positionBackdrop(stepConfig);\n\n $(document.body).append(currentStepNode);\n this.currentStepNode = currentStepNode;\n\n // Ensure that the step node is positioned.\n // Some situations mean that the value is not properly calculated without this step.\n this.currentStepNode.css({\n top: 0,\n left: 0,\n });\n\n animationTarget\n .animate({\n scrollTop: this.calculateScrollTop(stepConfig),\n }).promise().then(function() {\n this.positionStep(stepConfig);\n this.revealStep(stepConfig);\n return;\n }.bind(this))\n .catch(function() {\n // Silently fail.\n });\n\n } else if (stepConfig.orphan) {\n stepConfig.isOrphan = true;\n\n // This will be appended to the body instead.\n stepConfig.attachTo = $('body').first();\n stepConfig.attachPoint = 'append';\n\n // Add the backdrop.\n this.positionBackdrop(stepConfig);\n\n // This is an orphaned step.\n currentStepNode.addClass('orphan');\n\n // It lives in the body.\n $(document.body).append(currentStepNode);\n this.currentStepNode = currentStepNode;\n\n this.currentStepNode.offset(this.calculateStepPositionInPage());\n this.currentStepNode.css('position', 'fixed');\n\n this.currentStepPopper = new Popper(\n $('body'),\n this.currentStepNode[0], {\n removeOnDestroy: true,\n placement: stepConfig.placement + '-start',\n arrowElement: '[data-role=\"arrow\"]',\n // Empty the modifiers. We've already placed the step and don't want it moved.\n modifiers: {\n hide: {\n enabled: false,\n },\n applyStyle: {\n onLoad: null,\n enabled: false,\n },\n }\n }\n );\n\n this.revealStep(stepConfig);\n }\n\n return this;\n }\n\n /**\n * Make the given step visible.\n *\n * @method revealStep\n * @param {Object} stepConfig The step configuration of the step\n * @chainable\n * @return {Object} this.\n */\n revealStep(stepConfig) {\n // Fade the step in.\n this.currentStepNode.fadeIn('', $.proxy(function() {\n // Announce via ARIA.\n this.announceStep(stepConfig);\n\n // Focus on the current step Node.\n this.currentStepNode.focus();\n window.setTimeout($.proxy(function() {\n // After a brief delay, focus again.\n // There seems to be an issue with Jaws where it only reads the dialogue title initially.\n // This second focus helps it to read the full dialogue.\n if (this.currentStepNode) {\n this.currentStepNode.focus();\n }\n }, this), 100);\n\n }, this));\n\n return this;\n }\n\n /**\n * Helper to announce the step on the page.\n *\n * @method announceStep\n * @param {Object} stepConfig The step configuration of the step\n * @chainable\n * @return {Object} this.\n */\n announceStep(stepConfig) {\n // Setup the step Dialogue as per:\n // * https://www.w3.org/TR/wai-aria-practices/#dialog_nonmodal\n // * https://www.w3.org/TR/wai-aria-practices/#dialog_modal\n\n // Generate an ID for the current step node.\n let stepId = 'tour-step-' + this.tourName + '-' + stepConfig.stepNumber;\n this.currentStepNode.attr('id', stepId);\n\n let bodyRegion = this.currentStepNode.find('[data-placeholder=\"body\"]').first();\n bodyRegion.attr('id', stepId + '-body');\n bodyRegion.attr('role', 'document');\n\n let headerRegion = this.currentStepNode.find('[data-placeholder=\"title\"]').first();\n headerRegion.attr('id', stepId + '-title');\n headerRegion.attr('aria-labelledby', stepId + '-body');\n\n // Generally, a modal dialog has a role of dialog.\n this.currentStepNode.attr('role', 'dialog');\n this.currentStepNode.attr('tabindex', 0);\n this.currentStepNode.attr('aria-labelledby', stepId + '-title');\n this.currentStepNode.attr('aria-describedby', stepId + '-body');\n\n // Configure ARIA attributes on the target.\n let target = this.getStepTarget(stepConfig);\n if (target) {\n if (!target.attr('tabindex')) {\n target.attr('tabindex', 0);\n }\n\n target\n .data('original-describedby', target.attr('aria-describedby'))\n .attr('aria-describedby', stepId + '-body')\n ;\n }\n\n this.accessibilityShow(stepConfig);\n\n return this;\n }\n\n /**\n * Handle key down events.\n *\n * @method handleKeyDown\n * @param {EventFacade} e\n */\n handleKeyDown(e) {\n let tabbableSelector = 'a[href], link[href], [draggable=true], [contenteditable=true], ';\n tabbableSelector += ':input:enabled, [tabindex], button:enabled';\n switch (e.keyCode) {\n case 27:\n this.endTour();\n break;\n\n // 9 == Tab - trap focus for items with a backdrop.\n case 9:\n // Tab must be handled on key up only in this instance.\n (function() {\n if (!this.currentStepConfig.hasBackdrop) {\n // Trapping tab focus is only handled for those steps with a backdrop.\n return;\n }\n\n // Find all tabbable locations.\n let activeElement = $(document.activeElement);\n let stepTarget = this.getStepTarget(this.currentStepConfig);\n let tabbableNodes = $(tabbableSelector);\n let dialogContainer = $('span[data-flexitour=\"container\"]');\n let currentIndex;\n // Filter out element which is not belong to target section or dialogue.\n if (stepTarget) {\n tabbableNodes = tabbableNodes.filter(function(index, element) {\n return stepTarget !== null\n && (stepTarget.has(element).length\n || dialogContainer.has(element).length\n || stepTarget.is(element)\n || dialogContainer.is(element));\n });\n }\n\n // Find index of focusing element.\n tabbableNodes.each(function(index, element) {\n if (activeElement.is(element)) {\n currentIndex = index;\n return false;\n }\n // Keep looping.\n return true;\n });\n\n let nextIndex;\n let nextNode;\n let focusRelevant;\n if (currentIndex != void 0) {\n let direction = 1;\n if (e.shiftKey) {\n direction = -1;\n }\n nextIndex = currentIndex;\n do {\n nextIndex += direction;\n nextNode = $(tabbableNodes[nextIndex]);\n } while (nextNode.length && nextNode.is(':disabled') || nextNode.is(':hidden'));\n if (nextNode.length) {\n // A new f\n focusRelevant = nextNode.closest(stepTarget).length;\n focusRelevant = focusRelevant || nextNode.closest(this.currentStepNode).length;\n } else {\n // Unable to find the target somehow.\n focusRelevant = false;\n }\n }\n\n if (focusRelevant) {\n nextNode.focus();\n } else {\n if (e.shiftKey) {\n // Focus on the last tabbable node in the step.\n this.currentStepNode.find(tabbableSelector).last().focus();\n } else {\n if (this.currentStepConfig.isOrphan) {\n // Focus on the step - there is no target.\n this.currentStepNode.focus();\n } else {\n // Focus on the step target.\n stepTarget.focus();\n }\n }\n }\n e.preventDefault();\n }).call(this);\n break;\n }\n }\n\n /**\n * Start the current tour.\n *\n * @method startTour\n * @param {Integer} startAt Which step number to start at. If not specified, starts at the last point.\n * @chainable\n * @return {Object} this.\n */\n startTour(startAt) {\n if (this.storage && typeof startAt === 'undefined') {\n let storageStartValue = this.storage.getItem(this.storageKey);\n if (storageStartValue) {\n let storageStartAt = parseInt(storageStartValue, 10);\n if (storageStartAt <= this.steps.length) {\n startAt = storageStartAt;\n }\n }\n }\n\n if (typeof startAt === 'undefined') {\n startAt = this.getCurrentStepNumber();\n }\n\n this.fireEventHandlers('beforeStart', startAt);\n this.gotoStep(startAt);\n this.fireEventHandlers('afterStart', startAt);\n\n return this;\n }\n\n /**\n * Restart the tour from the beginning, resetting the completionlag.\n *\n * @method restartTour\n * @chainable\n * @return {Object} this.\n */\n restartTour() {\n return this.startTour(0);\n }\n\n /**\n * End the current tour.\n *\n * @method endTour\n * @chainable\n * @return {Object} this.\n */\n endTour() {\n this.fireEventHandlers('beforeEnd');\n\n if (this.currentStepConfig) {\n let previousTarget = this.getStepTarget(this.currentStepConfig);\n if (previousTarget) {\n if (!previousTarget.attr('tabindex')) {\n previousTarget.attr('tabindex', '-1');\n }\n previousTarget.focus();\n }\n }\n\n this.hide(true);\n\n this.fireEventHandlers('afterEnd');\n\n return this;\n }\n\n /**\n * Hide any currently visible steps.\n *\n * @method hide\n * @param {Bool} transition Animate the visibility change\n * @chainable\n * @return {Object} this.\n */\n hide(transition) {\n this.fireEventHandlers('beforeHide');\n\n if (this.currentStepNode && this.currentStepNode.length) {\n this.currentStepNode.hide();\n if (this.currentStepPopper) {\n this.currentStepPopper.destroy();\n }\n }\n\n // Restore original target configuration.\n if (this.currentStepConfig) {\n let target = this.getStepTarget(this.currentStepConfig);\n if (target) {\n if (target.data('original-labelledby')) {\n target.attr('aria-labelledby', target.data('original-labelledby'));\n }\n\n if (target.data('original-describedby')) {\n target.attr('aria-describedby', target.data('original-describedby'));\n }\n\n if (target.data('original-tabindex')) {\n target.attr('tabindex', target.data('tabindex'));\n }\n }\n\n // Clear the step configuration.\n this.currentStepConfig = null;\n }\n\n let fadeTime = 0;\n if (transition) {\n fadeTime = 400;\n }\n\n // Remove the backdrop features.\n $('[data-flexitour=\"step-background\"]').remove();\n $('[data-flexitour=\"step-backdrop\"]').removeAttr('data-flexitour');\n $('[data-flexitour=\"backdrop\"]').fadeOut(fadeTime, function() {\n $(this).remove();\n });\n\n // Remove aria-describedby and tabindex attributes.\n if (this.currentStepNode && this.currentStepNode.length) {\n let stepId = this.currentStepNode.attr('id');\n if (stepId) {\n let currentStepElement = '[aria-describedby=\"' + stepId + '-body\"]';\n $(currentStepElement).removeAttr('tabindex');\n $(currentStepElement).removeAttr('aria-describedby');\n }\n }\n\n // Reset the listeners.\n this.resetStepListeners();\n\n this.accessibilityHide();\n\n this.fireEventHandlers('afterHide');\n\n this.currentStepNode = null;\n this.currentStepPopper = null;\n return this;\n }\n\n /**\n * Show the current steps.\n *\n * @method show\n * @chainable\n * @return {Object} this.\n */\n show() {\n // Show the current step.\n let startAt = this.getCurrentStepNumber();\n\n return this.gotoStep(startAt);\n }\n\n /**\n * Return the current step node.\n *\n * @method getStepContainer\n * @return {jQuery}\n */\n getStepContainer() {\n return $(this.currentStepNode);\n }\n\n /**\n * Calculate scrollTop.\n *\n * @method calculateScrollTop\n * @param {Object} stepConfig The step configuration of the step\n * @return {Number}\n */\n calculateScrollTop(stepConfig) {\n let scrollTop = $(window).scrollTop();\n let viewportHeight = $(window).height();\n let targetNode = this.getStepTarget(stepConfig);\n\n if (stepConfig.placement === 'top') {\n // If the placement is top, center scroll at the top of the target.\n scrollTop = targetNode.offset().top - (viewportHeight / 2);\n } else if (stepConfig.placement === 'bottom') {\n // If the placement is bottom, center scroll at the bottom of the target.\n scrollTop = targetNode.offset().top + targetNode.height() - (viewportHeight / 2);\n } else if (targetNode.height() <= (viewportHeight * 0.8)) {\n // If the placement is left/right, and the target fits in the viewport, centre screen on the target\n scrollTop = targetNode.offset().top - ((viewportHeight - targetNode.height()) / 2);\n } else {\n // If the placement is left/right, and the target is bigger than the viewport, set scrollTop to target.top + buffer\n // and change step attachmentTarget to top+.\n scrollTop = targetNode.offset().top - (viewportHeight * 0.2);\n }\n\n // Never scroll over the top.\n scrollTop = Math.max(0, scrollTop);\n\n // Never scroll beyond the bottom.\n scrollTop = Math.min($(document).height() - viewportHeight, scrollTop);\n\n return Math.ceil(scrollTop);\n }\n\n /**\n * Calculate dialogue position for page middle.\n *\n * @method calculateScrollTop\n * @return {Number}\n */\n calculateStepPositionInPage() {\n let viewportHeight = $(window).height();\n let stepHeight = this.currentStepNode.height();\n\n let viewportWidth = $(window).width();\n let stepWidth = this.currentStepNode.width();\n\n return {\n top: Math.ceil((viewportHeight - stepHeight) / 2),\n left: Math.ceil((viewportWidth - stepWidth) / 2)\n };\n }\n\n /**\n * Position the step on the page.\n *\n * @method positionStep\n * @param {Object} stepConfig The step configuration of the step\n * @chainable\n * @return {Object} this.\n */\n positionStep(stepConfig) {\n let content = this.currentStepNode;\n if (!content || !content.length) {\n // Unable to find the step node.\n return this;\n }\n\n let flipBehavior;\n switch (stepConfig.placement) {\n case 'left':\n flipBehavior = ['left', 'right', 'top', 'bottom'];\n break;\n case 'right':\n flipBehavior = ['right', 'left', 'top', 'bottom'];\n break;\n case 'top':\n flipBehavior = ['top', 'bottom', 'right', 'left'];\n break;\n case 'bottom':\n flipBehavior = ['bottom', 'top', 'right', 'left'];\n break;\n default:\n flipBehavior = 'flip';\n break;\n }\n\n let target = this.getStepTarget(stepConfig);\n var config = {\n placement: stepConfig.placement + '-start',\n removeOnDestroy: true,\n modifiers: {\n flip: {\n behaviour: flipBehavior,\n },\n arrow: {\n element: '[data-role=\"arrow\"]',\n },\n },\n onCreate: function(data) {\n recalculateArrowPosition(data);\n },\n onUpdate: function(data) {\n recalculateArrowPosition(data);\n },\n };\n\n let recalculateArrowPosition = function(data) {\n let placement = data.placement.split('-')[0];\n const isVertical = ['left', 'right'].indexOf(placement) !== -1;\n const arrowElement = data.instance.popper.querySelector('[data-role=\"arrow\"]');\n const stepElement = $(data.instance.popper.querySelector('[data-role=\"flexitour-step\"]'));\n if (isVertical) {\n let arrowHeight = parseFloat(window.getComputedStyle(arrowElement).height);\n let arrowOffset = parseFloat(window.getComputedStyle(arrowElement).top);\n let popperHeight = parseFloat(window.getComputedStyle(data.instance.popper).height);\n let popperOffset = parseFloat(window.getComputedStyle(data.instance.popper).top);\n let popperBorderWidth = parseFloat(stepElement.css('borderTopWidth'));\n let popperBorderRadiusWidth = parseFloat(stepElement.css('borderTopLeftRadius')) * 2;\n let arrowPos = arrowOffset + (arrowHeight / 2);\n let maxPos = popperHeight + popperOffset - popperBorderWidth - popperBorderRadiusWidth;\n let minPos = popperOffset + popperBorderWidth + popperBorderRadiusWidth;\n if (arrowPos >= maxPos || arrowPos <= minPos) {\n let newArrowPos = 0;\n if (arrowPos > (popperHeight / 2)) {\n newArrowPos = maxPos - arrowHeight;\n } else {\n newArrowPos = minPos + arrowHeight;\n }\n $(arrowElement).css('top', newArrowPos);\n }\n } else {\n let arrowWidth = parseFloat(window.getComputedStyle(arrowElement).width);\n let arrowOffset = parseFloat(window.getComputedStyle(arrowElement).left);\n let popperWidth = parseFloat(window.getComputedStyle(data.instance.popper).width);\n let popperOffset = parseFloat(window.getComputedStyle(data.instance.popper).left);\n let popperBorderWidth = parseFloat(stepElement.css('borderTopWidth'));\n let popperBorderRadiusWidth = parseFloat(stepElement.css('borderTopLeftRadius')) * 2;\n let arrowPos = arrowOffset + (arrowWidth / 2);\n let maxPos = popperWidth + popperOffset - popperBorderWidth - popperBorderRadiusWidth;\n let minPos = popperOffset + popperBorderWidth + popperBorderRadiusWidth;\n if (arrowPos >= maxPos || arrowPos <= minPos) {\n let newArrowPos = 0;\n if (arrowPos > (popperWidth / 2)) {\n newArrowPos = maxPos - arrowWidth;\n } else {\n newArrowPos = minPos + arrowWidth;\n }\n $(arrowElement).css('left', newArrowPos);\n }\n }\n };\n\n let background = $('[data-flexitour=\"step-background\"]');\n if (background.length) {\n target = background;\n }\n this.currentStepPopper = new Popper(target, content[0], config);\n\n return this;\n }\n\n /**\n * Add the backdrop.\n *\n * @method positionBackdrop\n * @param {Object} stepConfig The step configuration of the step\n * @chainable\n * @return {Object} this.\n */\n positionBackdrop(stepConfig) {\n if (stepConfig.backdrop) {\n this.currentStepConfig.hasBackdrop = true;\n let backdrop = $('');\n\n if (stepConfig.zIndex) {\n if (stepConfig.attachPoint === 'append') {\n stepConfig.attachTo.append(backdrop);\n } else {\n backdrop.insertAfter(stepConfig.attachTo);\n }\n } else {\n $('body').append(backdrop);\n }\n\n if (this.isStepActuallyVisible(stepConfig)) {\n // The step has a visible target.\n // Punch a hole through the backdrop.\n let background = $('');\n\n let targetNode = this.getStepTarget(stepConfig);\n\n let buffer = 10;\n\n let colorNode = targetNode;\n if (buffer) {\n colorNode = $('body');\n }\n\n background.css({\n width: targetNode.outerWidth() + buffer + buffer,\n height: targetNode.outerHeight() + buffer + buffer,\n left: targetNode.offset().left - buffer,\n top: targetNode.offset().top - buffer,\n backgroundColor: this.calculateInherittedBackgroundColor(colorNode),\n });\n\n if (targetNode.offset().left < buffer) {\n background.css({\n width: targetNode.outerWidth() + targetNode.offset().left + buffer,\n left: targetNode.offset().left,\n });\n }\n\n if (targetNode.offset().top < buffer) {\n background.css({\n height: targetNode.outerHeight() + targetNode.offset().top + buffer,\n top: targetNode.offset().top,\n });\n }\n\n let targetRadius = targetNode.css('borderRadius');\n if (targetRadius && targetRadius !== $('body').css('borderRadius')) {\n background.css('borderRadius', targetRadius);\n }\n\n let targetPosition = this.calculatePosition(targetNode);\n if (targetPosition === 'fixed') {\n background.css('top', 0);\n } else if (targetPosition === 'absolute') {\n background.css('position', 'fixed');\n }\n\n let fader = background.clone();\n fader.css({\n backgroundColor: backdrop.css('backgroundColor'),\n opacity: backdrop.css('opacity'),\n });\n fader.attr('data-flexitour', 'step-background-fader');\n\n if (stepConfig.zIndex) {\n if (stepConfig.attachPoint === 'append') {\n stepConfig.attachTo.append(background);\n } else {\n fader.insertAfter(stepConfig.attachTo);\n background.insertAfter(stepConfig.attachTo);\n }\n } else {\n $('body').append(fader);\n $('body').append(background);\n }\n\n // Add the backdrop data to the actual target.\n // This is the part which actually does the work.\n targetNode.attr('data-flexitour', 'step-backdrop');\n\n if (stepConfig.zIndex) {\n backdrop.css('zIndex', stepConfig.zIndex);\n background.css('zIndex', stepConfig.zIndex + 1);\n targetNode.css('zIndex', stepConfig.zIndex + 2);\n }\n\n fader.fadeOut('2000', function() {\n $(this).remove();\n });\n }\n }\n return this;\n }\n\n /**\n * Calculate the inheritted z-index.\n *\n * @method calculateZIndex\n * @param {jQuery} elem The element to calculate z-index for\n * @return {Number} Calculated z-index\n */\n calculateZIndex(elem) {\n elem = $(elem);\n while (elem.length && elem[0] !== document) {\n // Ignore z-index if position is set to a value where z-index is ignored by the browser\n // This makes behavior of this function consistent across browsers\n // WebKit always returns auto if the element is positioned.\n let position = elem.css(\"position\");\n if (position === \"absolute\" || position === \"relative\" || position === \"fixed\") {\n // IE returns 0 when zIndex is not specified\n // other browsers return a string\n // we ignore the case of nested elements with an explicit value of 0\n // \n let value = parseInt(elem.css(\"zIndex\"), 10);\n if (!isNaN(value) && value !== 0) {\n return value;\n }\n }\n elem = elem.parent();\n }\n\n return 0;\n }\n\n /**\n * Calculate the inheritted background colour.\n *\n * @method calculateInherittedBackgroundColor\n * @param {jQuery} elem The element to calculate colour for\n * @return {String} Calculated background colour\n */\n calculateInherittedBackgroundColor(elem) {\n // Use a fake node to compare each element against.\n let fakeNode = $('').hide();\n $('body').append(fakeNode);\n let fakeElemColor = fakeNode.css('backgroundColor');\n fakeNode.remove();\n\n elem = $(elem);\n while (elem.length && elem[0] !== document) {\n let color = elem.css('backgroundColor');\n if (color !== fakeElemColor) {\n return color;\n }\n elem = elem.parent();\n }\n\n return null;\n }\n\n /**\n * Calculate the inheritted position.\n *\n * @method calculatePosition\n * @param {jQuery} elem The element to calculate position for\n * @return {String} Calculated position\n */\n calculatePosition(elem) {\n elem = $(elem);\n while (elem.length && elem[0] !== document) {\n let position = elem.css('position');\n if (position !== 'static') {\n return position;\n }\n elem = elem.parent();\n }\n\n return null;\n }\n\n /**\n * Perform accessibility changes for step shown.\n *\n * This will add aria-hidden=\"true\" to all siblings and parent siblings.\n *\n * @method accessibilityShow\n */\n accessibilityShow() {\n let stateHolder = 'data-has-hidden';\n let attrName = 'aria-hidden';\n let hideFunction = function(child) {\n let flexitourRole = child.data('flexitour');\n if (flexitourRole) {\n switch (flexitourRole) {\n case 'container':\n case 'target':\n return;\n }\n }\n\n let hidden = child.attr(attrName);\n if (!hidden) {\n child.attr(stateHolder, true);\n child.attr(attrName, true);\n }\n };\n\n this.currentStepNode.siblings().each(function(index, node) {\n hideFunction($(node));\n });\n this.currentStepNode.parentsUntil('body').siblings().each(function(index, node) {\n hideFunction($(node));\n });\n }\n\n /**\n * Perform accessibility changes for step hidden.\n *\n * This will remove any newly added aria-hidden=\"true\".\n *\n * @method accessibilityHide\n */\n accessibilityHide() {\n let stateHolder = 'data-has-hidden';\n let attrName = 'aria-hidden';\n let showFunction = function(child) {\n let hidden = child.attr(stateHolder);\n if (typeof hidden !== 'undefined') {\n child.removeAttr(stateHolder);\n child.removeAttr(attrName);\n }\n };\n\n $('[' + stateHolder + ']').each(function(index, node) {\n showFunction($(node));\n });\n }\n}\n"],"file":"tour.min.js"}
\ No newline at end of file
diff --git a/admin/tool/usertours/amd/readme_moodle.txt b/admin/tool/usertours/amd/readme_moodle.txt
deleted file mode 100644
index 1fb69a323ab..00000000000
--- a/admin/tool/usertours/amd/readme_moodle.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-Description of External library imports into Moodle
-
-Flexitour Instructions
-----------------------
-1. Clone https://github.com/andrewnicols/flexitour into an unrelated directory
-2. Copy /build/tour.js to amd/src/tour.js
-3. Open the amd/src/tour.js file and find the AMD module define.
-4. Change the "popper" inclusion to "core/popper"
-5. Update thirdpartylibs.xml
-6. Run `grunt amd`
diff --git a/admin/tool/usertours/amd/src/tour.js b/admin/tool/usertours/amd/src/tour.js
index 65e48a70bdf..0f9ad947c62 100644
--- a/admin/tool/usertours/amd/src/tour.js
+++ b/admin/tool/usertours/amd/src/tour.js
@@ -1,1563 +1,1534 @@
-// jshint ignore: start
-(function (root, factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module unless amdModuleId is set
- define(["jquery","core/popper"], function (a0,b1) {
- return (root['Tour'] = factory(a0,b1));
- });
- } else if (typeof module === 'object' && module.exports) {
- // Node. Does not work with strict CommonJS, but
- // only CommonJS-like environments that support module.exports,
- // like Node.
- module.exports = factory(require("jquery"),require("popper.js"));
- } else {
- root['Tour'] = factory(root["$"],root["Popper"]);
- }
-}(this, function ($, Popper) {
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see
.
-"use strict";
+/**
+ * Manage user tours in Moodle.
+ *
+ * @copyright 2018 Andrew Nicols
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import $ from 'jquery';
+import Popper from 'core/popper';
/**
* A Tour.
*
- * @class Tour
- * @param {object} config The configuration object.
+ * @class Tour
*/
-
-var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
-
-function Tour(config) {
- this.init(config);
-}
-
-/**
- * The name of the tour.
- *
- * @property {String} tourName
- */
-Tour.prototype.tourName;
-
-/**
- * The name of the tour storage key.
- *
- * @property {String} storageKey
- */
-Tour.prototype.storageKey;
-
-/**
- * The session storage object
- *
- * @property {Storage} storage
- */
-Tour.prototype.storage;
-
-/**
- * The original configuration as passed into the constructor.
- *
- * @property {Object} originalConfiguration
- */
-Tour.prototype.originalConfiguration;
-
-/**
- * The list of step listeners.
- *
- * @property {Array} listeners
- */
-Tour.prototype.listeners;
-
-/**
- * The list of event handlers.
- *
- * @property {Object} eventHandlers
- */
-Tour.prototype.eventHandlers;
-
-/**
- * The list of steps.
- *
- * @property {Object[]} steps
- */
-Tour.prototype.steps;
-
-/**
- * The current step node.
- *
- * @property {jQuery} currentStepNode
- */
-Tour.prototype.currentStepNode;
-
-/**
- * The current step number.
- *
- * @property {Number} currentStepNumber
- */
-Tour.prototype.currentStepNumber;
-
-/**
- * The popper for the current step.
- *
- * @property {Popper} currentStepPopper
- */
-Tour.prototype.currentStepPopper;
-
-/**
- * The config for the current step.
- *
- * @property {Object} currentStepConfig
- */
-Tour.prototype.currentStepConfig;
-
-/**
- * The template content.
- *
- * @property {String} templateContent
- */
-Tour.prototype.templateContent;
-
-/**
- * Initialise the tour.
- *
- * @method init
- * @param {Object} config The configuration object.
- * @chainable
- */
-Tour.prototype.init = function (config) {
- // Unset all handlers.
- this.eventHandlers = {};
-
- // Reset the current tour states.
- this.reset();
-
- // Store the initial configuration.
- this.originalConfiguration = config || {};
-
- // Apply configuration.
- this.configure.apply(this, arguments);
-
- try {
- this.storage = window.sessionStorage;
- this.storageKey = 'tourstate_' + this.tourName;
- } catch (e) {
- this.storage = false;
- this.storageKey = '';
+export default class Tour {
+ /**
+ * @param {object} config The configuration object.
+ */
+ constructor(config) {
+ this.init(config);
}
- return this;
-};
+ /**
+ * Initialise the tour.
+ *
+ * @method init
+ * @param {Object} config The configuration object.
+ * @chainable
+ * @return {Object} this.
+ */
+ init(config) {
+ // Unset all handlers.
+ this.eventHandlers = {};
-/**
- * Reset the current tour state.
- *
- * @method reset
- * @chainable
- */
-Tour.prototype.reset = function () {
- // Hide the current step.
- this.hide();
+ // Reset the current tour states.
+ this.reset();
- // Unset all handlers.
- this.eventHandlers = [];
+ // Store the initial configuration.
+ this.originalConfiguration = config || {};
- // Unset all listeners.
- this.resetStepListeners();
+ // Apply configuration.
+ this.configure.apply(this, arguments);
- // Unset the original configuration.
- this.originalConfiguration = {};
-
- // Reset the current step number and list of steps.
- this.steps = [];
-
- // Reset the current step number.
- this.currentStepNumber = 0;
-
- return this;
-};
-
-/**
- * Prepare tour configuration.
- *
- * @method configure
- * @chainable
- */
-Tour.prototype.configure = function (config) {
- var _this = this;
-
- if ((typeof config === 'undefined' ? 'undefined' : _typeof(config)) === 'object') {
- // Tour name.
- if (typeof config.tourName !== 'undefined') {
- this.tourName = config.tourName;
- }
-
- // Set up eventHandlers.
- if (config.eventHandlers) {
- (function () {
- var eventName = void 0;
- for (eventName in config.eventHandlers) {
- config.eventHandlers[eventName].forEach(function (handler) {
- this.addEventHandler(eventName, handler);
- }, _this);
- }
- })();
- }
-
- // Reset the step configuration.
- this.resetStepDefaults(true);
-
- // Configure the steps.
- if (_typeof(config.steps) === 'object') {
- this.steps = config.steps;
- }
-
- if (typeof config.template !== 'undefined') {
- this.templateContent = config.template;
- }
- }
-
- // Check that we have enough to start the tour.
- this.checkMinimumRequirements();
-
- return this;
-};
-
-/**
- * Check that the configuration meets the minimum requirements.
- *
- * @method checkMinimumRequirements
- * @chainable
- */
-Tour.prototype.checkMinimumRequirements = function () {
- // Need a tourName.
- if (!this.tourName) {
- throw new Error("Tour Name required");
- }
-
- // Need a minimum of one step.
- if (!this.steps || !this.steps.length) {
- throw new Error("Steps must be specified");
- }
-};
-
-/**
- * Reset step default configuration.
- *
- * @method resetStepDefaults
- * @param {Boolean} loadOriginalConfiguration Whether to load the original configuration supplied with the Tour.
- * @chainable
- */
-Tour.prototype.resetStepDefaults = function (loadOriginalConfiguration) {
- if (typeof loadOriginalConfiguration === 'undefined') {
- loadOriginalConfiguration = true;
- }
-
- this.stepDefaults = {};
- if (!loadOriginalConfiguration || typeof this.originalConfiguration.stepDefaults === 'undefined') {
- this.setStepDefaults({});
- } else {
- this.setStepDefaults(this.originalConfiguration.stepDefaults);
- }
-
- return this;
-};
-
-/**
- * Set the step defaults.
- *
- * @method setStepDefaults
- * @param {Object} stepDefaults The step defaults to apply to all steps
- * @chainable
- */
-Tour.prototype.setStepDefaults = function (stepDefaults) {
- if (!this.stepDefaults) {
- this.stepDefaults = {};
- }
- $.extend(this.stepDefaults, {
- element: '',
- placement: 'top',
- delay: 0,
- moveOnClick: false,
- moveAfterTime: 0,
- orphan: false,
- direction: 1
- }, stepDefaults);
-
- return this;
-};
-
-/**
- * Retrieve the current step number.
- *
- * @method getCurrentStepNumber
- * @return {Integer} The current step number
- */
-Tour.prototype.getCurrentStepNumber = function () {
- return parseInt(this.currentStepNumber, 10);
-};
-
-/**
- * Store the current step number.
- *
- * @method setCurrentStepNumber
- * @param {Integer} stepNumber The current step number
- * @chainable
- */
-Tour.prototype.setCurrentStepNumber = function (stepNumber) {
- this.currentStepNumber = stepNumber;
- if (this.storage) {
try {
- this.storage.setItem(this.storageKey, stepNumber);
+ this.storage = window.sessionStorage;
+ this.storageKey = 'tourstate_' + this.tourName;
} catch (e) {
- if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {
- this.storage.removeItem(this.storageKey);
+ this.storage = false;
+ this.storageKey = '';
+ }
+
+ return this;
+ }
+
+ /**
+ * Reset the current tour state.
+ *
+ * @method reset
+ * @chainable
+ * @return {Object} this.
+ */
+ reset() {
+ // Hide the current step.
+ this.hide();
+
+ // Unset all handlers.
+ this.eventHandlers = [];
+
+ // Unset all listeners.
+ this.resetStepListeners();
+
+ // Unset the original configuration.
+ this.originalConfiguration = {};
+
+ // Reset the current step number and list of steps.
+ this.steps = [];
+
+ // Reset the current step number.
+ this.currentStepNumber = 0;
+
+ return this;
+ }
+
+ /**
+ * Prepare tour configuration.
+ *
+ * @method configure
+ * @param {Object} config The configuration object.
+ * @chainable
+ * @return {Object} this.
+ */
+ configure(config) {
+ if (typeof config === 'object') {
+ // Tour name.
+ if (typeof config.tourName !== 'undefined') {
+ this.tourName = config.tourName;
+ }
+
+ // Set up eventHandlers.
+ if (config.eventHandlers) {
+ for (let eventName in config.eventHandlers) {
+ config.eventHandlers[eventName].forEach(function(handler) {
+ this.addEventHandler(eventName, handler);
+ }, this);
+ }
+ }
+
+ // Reset the step configuration.
+ this.resetStepDefaults(true);
+
+ // Configure the steps.
+ if (typeof config.steps === 'object') {
+ this.steps = config.steps;
+ }
+
+ if (typeof config.template !== 'undefined') {
+ this.templateContent = config.template;
+ }
+ }
+
+ // Check that we have enough to start the tour.
+ this.checkMinimumRequirements();
+
+ return this;
+ }
+
+ /**
+ * Check that the configuration meets the minimum requirements.
+ *
+ * @method checkMinimumRequirements
+ */
+ checkMinimumRequirements() {
+ // Need a tourName.
+ if (!this.tourName) {
+ throw new Error("Tour Name required");
+ }
+
+ // Need a minimum of one step.
+ if (!this.steps || !this.steps.length) {
+ throw new Error("Steps must be specified");
+ }
+ }
+
+ /**
+ * Reset step default configuration.
+ *
+ * @method resetStepDefaults
+ * @param {Boolean} loadOriginalConfiguration Whether to load the original configuration supplied with the Tour.
+ * @chainable
+ * @return {Object} this.
+ */
+ resetStepDefaults(loadOriginalConfiguration) {
+ if (typeof loadOriginalConfiguration === 'undefined') {
+ loadOriginalConfiguration = true;
+ }
+
+ this.stepDefaults = {};
+ if (!loadOriginalConfiguration || typeof this.originalConfiguration.stepDefaults === 'undefined') {
+ this.setStepDefaults({});
+ } else {
+ this.setStepDefaults(this.originalConfiguration.stepDefaults);
+ }
+
+ return this;
+ }
+
+ /**
+ * Set the step defaults.
+ *
+ * @method setStepDefaults
+ * @param {Object} stepDefaults The step defaults to apply to all steps
+ * @chainable
+ * @return {Object} this.
+ */
+ setStepDefaults(stepDefaults) {
+ if (!this.stepDefaults) {
+ this.stepDefaults = {};
+ }
+ $.extend(
+ this.stepDefaults,
+ {
+ element: '',
+ placement: 'top',
+ delay: 0,
+ moveOnClick: false,
+ moveAfterTime: 0,
+ orphan: false,
+ direction: 1,
+ },
+ stepDefaults
+ );
+
+ return this;
+ }
+
+ /**
+ * Retrieve the current step number.
+ *
+ * @method getCurrentStepNumber
+ * @return {Integer} The current step number
+ */
+ getCurrentStepNumber() {
+ return parseInt(this.currentStepNumber, 10);
+ }
+
+ /**
+ * Store the current step number.
+ *
+ * @method setCurrentStepNumber
+ * @param {Integer} stepNumber The current step number
+ * @chainable
+ */
+ setCurrentStepNumber(stepNumber) {
+ this.currentStepNumber = stepNumber;
+ if (this.storage) {
+ try {
+ this.storage.setItem(this.storageKey, stepNumber);
+ } catch (e) {
+ if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {
+ this.storage.removeItem(this.storageKey);
+ }
}
}
}
-};
-/**
- * Get the next step number after the currently displayed step.
- *
- * @method getNextStepNumber
- * @return {Integer} The next step number to display
- */
-Tour.prototype.getNextStepNumber = function (stepNumber) {
- if (typeof stepNumber === 'undefined') {
- stepNumber = this.getCurrentStepNumber();
- }
- var nextStepNumber = stepNumber + 1;
-
- // Keep checking the remaining steps.
- while (nextStepNumber <= this.steps.length) {
- if (this.isStepPotentiallyVisible(this.getStepConfig(nextStepNumber))) {
- return nextStepNumber;
+ /**
+ * Get the next step number after the currently displayed step.
+ *
+ * @method getNextStepNumber
+ * @param {Integer} stepNumber The current step number
+ * @return {Integer} The next step number to display
+ */
+ getNextStepNumber(stepNumber) {
+ if (typeof stepNumber === 'undefined') {
+ stepNumber = this.getCurrentStepNumber();
}
- nextStepNumber++;
- }
+ let nextStepNumber = stepNumber + 1;
- return null;
-};
-
-/**
- * Get the previous step number before the currently displayed step.
- *
- * @method getPreviousStepNumber
- * @return {Integer} The previous step number to display
- */
-Tour.prototype.getPreviousStepNumber = function (stepNumber) {
- if (typeof stepNumber === 'undefined') {
- stepNumber = this.getCurrentStepNumber();
- }
- var previousStepNumber = stepNumber - 1;
-
- // Keep checking the remaining steps.
- while (previousStepNumber >= 0) {
- if (this.isStepPotentiallyVisible(this.getStepConfig(previousStepNumber))) {
- return previousStepNumber;
+ // Keep checking the remaining steps.
+ while (nextStepNumber <= this.steps.length) {
+ if (this.isStepPotentiallyVisible(this.getStepConfig(nextStepNumber))) {
+ return nextStepNumber;
+ }
+ nextStepNumber++;
}
- previousStepNumber--;
- }
- return null;
-};
-
-/**
- * Is the step the final step number?
- *
- * @method isLastStep
- * @param {Integer} stepNumber Step number to test
- * @return {Boolean} Whether the step is the final step
- */
-Tour.prototype.isLastStep = function (stepNumber) {
- var nextStepNumber = this.getNextStepNumber(stepNumber);
-
- return nextStepNumber === null;
-};
-
-/**
- * Is the step the first step number?
- *
- * @method isFirstStep
- * @param {Integer} stepNumber Step number to test
- * @return {Boolean} Whether the step is the first step
- */
-Tour.prototype.isFirstStep = function (stepNumber) {
- var previousStepNumber = this.getPreviousStepNumber(stepNumber);
-
- return previousStepNumber === null;
-};
-
-/**
- * Is this step potentially visible?
- *
- * @method isStepPotentiallyVisible
- * @param {Integer} stepNumber Step number to test
- * @return {Boolean} Whether the step is the potentially visible
- */
-Tour.prototype.isStepPotentiallyVisible = function (stepConfig) {
- if (!stepConfig) {
- // Without step config, there can be no step.
- return false;
- }
-
- if (this.isStepActuallyVisible(stepConfig)) {
- // If it is actually visible, it is already potentially visible.
- return true;
- }
-
- if (typeof stepConfig.orphan !== 'undefined' && stepConfig.orphan) {
- // Orphan steps have no target. They are always visible.
- return true;
- }
-
- if (typeof stepConfig.delay !== 'undefined' && stepConfig.delay) {
- // Only return true if the activated has not been used yet.
- return true;
- }
-
- // Not theoretically, or actually visible.
- return false;
-};
-
-/**
- * Is this step actually visible?
- *
- * @method isStepActuallyVisible
- * @param {Integer} stepNumber Step number to test
- * @return {Boolean} Whether the step is actually visible
- */
-Tour.prototype.isStepActuallyVisible = function (stepConfig) {
- if (!stepConfig) {
- // Without step config, there can be no step.
- return false;
- }
-
- var target = this.getStepTarget(stepConfig);
- if (target && target.length && target.is(':visible')) {
- // Without a target, there can be no step.
- return !!target.length;
- }
-
- return false;
-};
-
-/**
- * Go to the next step in the tour.
- *
- * @method next
- * @chainable
- */
-Tour.prototype.next = function () {
- return this.gotoStep(this.getNextStepNumber());
-};
-
-/**
- * Go to the previous step in the tour.
- *
- * @method previous
- * @chainable
- */
-Tour.prototype.previous = function () {
- return this.gotoStep(this.getPreviousStepNumber(), -1);
-};
-
-/**
- * Go to the specified step in the tour.
- *
- * @method gotoStep
- * @param {Integer} stepNumber The step number to display
- * @chainable
- */
-Tour.prototype.gotoStep = function (stepNumber, direction) {
- if (stepNumber < 0) {
- return this.endTour();
- }
-
- var stepConfig = this.getStepConfig(stepNumber);
- if (stepConfig === null) {
- return this.endTour();
- }
-
- return this._gotoStep(stepConfig, direction);
-};
-
-Tour.prototype._gotoStep = function (stepConfig, direction) {
- if (!stepConfig) {
- return this.endTour();
- }
-
- if (typeof stepConfig.delay !== 'undefined' && stepConfig.delay && !stepConfig.delayed) {
- stepConfig.delayed = true;
- window.setTimeout(this._gotoStep.bind(this), stepConfig.delay, stepConfig, direction);
-
- return this;
- } else if (!stepConfig.orphan && !this.isStepActuallyVisible(stepConfig)) {
- var fn = direction == -1 ? 'getPreviousStepNumber' : 'getNextStepNumber';
- return this.gotoStep(this[fn](stepConfig.stepNumber), direction);
- }
-
- this.hide();
-
- this.fireEventHandlers('beforeRender', stepConfig);
- this.renderStep(stepConfig);
- this.fireEventHandlers('afterRender', stepConfig);
-
- return this;
-};
-
-/**
- * Fetch the normalised step configuration for the specified step number.
- *
- * @method getStepConfig
- * @param {Integer} stepNumber The step number to fetch configuration for
- * @return {Object} The step configuration
- */
-Tour.prototype.getStepConfig = function (stepNumber) {
- if (stepNumber === null || stepNumber < 0 || stepNumber >= this.steps.length) {
return null;
}
- // Normalise the step configuration.
- var stepConfig = this.normalizeStepConfig(this.steps[stepNumber]);
+ /**
+ * Get the previous step number before the currently displayed step.
+ *
+ * @method getPreviousStepNumber
+ * @param {Integer} stepNumber The current step number
+ * @return {Integer} The previous step number to display
+ */
+ getPreviousStepNumber(stepNumber) {
+ if (typeof stepNumber === 'undefined') {
+ stepNumber = this.getCurrentStepNumber();
+ }
+ let previousStepNumber = stepNumber - 1;
- // Add the stepNumber to the stepConfig.
- stepConfig = $.extend(stepConfig, { stepNumber: stepNumber });
-
- return stepConfig;
-};
-
-/**
- * Normalise the supplied step configuration.
- *
- * @method normalizeStepConfig
- * @param {Object} stepConfig The step configuration to normalise
- * @return {Object} The normalised step configuration
- */
-Tour.prototype.normalizeStepConfig = function (stepConfig) {
-
- if (typeof stepConfig.reflex !== 'undefined' && typeof stepConfig.moveAfterClick === 'undefined') {
- stepConfig.moveAfterClick = stepConfig.reflex;
- }
-
- if (typeof stepConfig.element !== 'undefined' && typeof stepConfig.target === 'undefined') {
- stepConfig.target = stepConfig.element;
- }
-
- if (typeof stepConfig.content !== 'undefined' && typeof stepConfig.body === 'undefined') {
- stepConfig.body = stepConfig.content;
- }
-
- stepConfig = $.extend({}, this.stepDefaults, stepConfig);
-
- stepConfig = $.extend({}, {
- attachTo: stepConfig.target,
- attachPoint: 'after'
- }, stepConfig);
-
- if (stepConfig.attachTo) {
- stepConfig.attachTo = $(stepConfig.attachTo).first();
- }
-
- return stepConfig;
-};
-
-/**
- * Fetch the actual step target from the selector.
- *
- * This should not be called until after any delay has completed.
- *
- * @method getStepTarget
- * @param {Object} stepConfig The step configuration
- * @return {$}
- */
-Tour.prototype.getStepTarget = function (stepConfig) {
- if (stepConfig.target) {
- return $(stepConfig.target);
- }
-
- return null;
-};
-
-/**
- * Fire any event handlers for the specified event.
- *
- * @param {String} eventName The name of the event to handle
- * @param {Object} data Any data to pass to the event
- * @chainable
- */
-Tour.prototype.fireEventHandlers = function (eventName, data) {
- if (typeof this.eventHandlers[eventName] === 'undefined') {
- return this;
- }
-
- this.eventHandlers[eventName].forEach(function (thisEvent) {
- thisEvent.call(this, data);
- }, this);
-
- return this;
-};
-
-/**
- * @method addEventHandler
- * @param string eventName The name of the event to listen for
- * @param function handler The event handler to call
- */
-Tour.prototype.addEventHandler = function (eventName, handler) {
- if (typeof this.eventHandlers[eventName] === 'undefined') {
- this.eventHandlers[eventName] = [];
- }
-
- this.eventHandlers[eventName].push(handler);
-
- return this;
-};
-
-/**
- * Process listeners for the step being shown.
- *
- * @method processStepListeners
- * @param {object} stepConfig The configuration for the step
- * @chainable
- */
-Tour.prototype.processStepListeners = function (stepConfig) {
- this.listeners.push(
- // Next/Previous buttons.
- {
- node: this.currentStepNode,
- args: ['click', '[data-role="next"]', $.proxy(this.next, this)]
- }, {
- node: this.currentStepNode,
- args: ['click', '[data-role="previous"]', $.proxy(this.previous, this)]
- },
-
- // Close and end tour buttons.
- {
- node: this.currentStepNode,
- args: ['click', '[data-role="end"]', $.proxy(this.endTour, this)]
- },
-
- // Click backdrop and hide tour.
- {
- node: $('[data-flexitour="backdrop"]'),
- args: ['click', $.proxy(this.hide, this)]
- },
-
- // Keypresses.
- {
- node: $('body'),
- args: ['keydown', $.proxy(this.handleKeyDown, this)]
- });
-
- if (stepConfig.moveOnClick) {
- var targetNode = this.getStepTarget(stepConfig);
- this.listeners.push({
- node: targetNode,
- args: ['click', $.proxy(function (e) {
- if ($(e.target).parents('[data-flexitour="container"]').length === 0) {
- // Ignore clicks when they are in the flexitour.
- window.setTimeout($.proxy(this.next, this), 500);
- }
- }, this)]
- });
- }
-
- this.listeners.forEach(function (listener) {
- listener.node.on.apply(listener.node, listener.args);
- });
-
- return this;
-};
-
-/**
- * Reset step listeners.
- *
- * @method resetStepListeners
- * @chainable
- */
-Tour.prototype.resetStepListeners = function () {
- // Stop listening to all external handlers.
- if (this.listeners) {
- this.listeners.forEach(function (listener) {
- listener.node.off.apply(listener.node, listener.args);
- });
- }
- this.listeners = [];
-
- return this;
-};
-
-/**
- * The standard step renderer.
- *
- * @method renderStep
- * @param {Object} stepConfig The step configuration of the step
- * @chainable
- */
-Tour.prototype.renderStep = function (stepConfig) {
- // Store the current step configuration for later.
- this.currentStepConfig = stepConfig;
- this.setCurrentStepNumber(stepConfig.stepNumber);
-
- // Fetch the template and convert it to a $ object.
- var template = $(this.getTemplateContent());
-
- // Title.
- template.find('[data-placeholder="title"]').html(stepConfig.title);
-
- // Body.
- template.find('[data-placeholder="body"]').html(stepConfig.body);
-
- // Is this the first step?
- if (this.isFirstStep(stepConfig.stepNumber)) {
- template.find('[data-role="previous"]').prop('disabled', true);
- } else {
- template.find('[data-role="previous"]').prop('disabled', false);
- }
-
- // Is this the final step?
- if (this.isLastStep(stepConfig.stepNumber)) {
- template.find('[data-role="next"]').prop('disabled', true);
- } else {
- template.find('[data-role="next"]').prop('disabled', false);
- }
-
- template.find('[data-role="previous"]').attr('role', 'button');
- template.find('[data-role="next"]').attr('role', 'button');
- template.find('[data-role="end"]').attr('role', 'button');
-
- // Replace the template with the updated version.
- stepConfig.template = template;
-
- // Add to the page.
- this.addStepToPage(stepConfig);
-
- // Process step listeners after adding to the page.
- // This uses the currentNode.
- this.processStepListeners(stepConfig);
-
- return this;
-};
-
-/**
- * Getter for the template content.
- *
- * @method getTemplateContent
- * @return {$}
- */
-Tour.prototype.getTemplateContent = function () {
- return $(this.templateContent).clone();
-};
-
-/**
- * Helper to add a step to the page.
- *
- * @method addStepToPage
- * @param {Object} stepConfig The step configuration of the step
- * @chainable
- */
-Tour.prototype.addStepToPage = function (stepConfig) {
- var stepContent = stepConfig.template;
-
- // Create the stepNode from the template data.
- var currentStepNode = $('').html(stepConfig.template).hide();
-
- // The scroll animation occurs on the body or html.
- var animationTarget = $('body, html').stop(true, true);
-
- if (this.isStepActuallyVisible(stepConfig)) {
- var targetNode = this.getStepTarget(stepConfig);
-
- targetNode.data('flexitour', 'target');
-
- var zIndex = this.calculateZIndex(targetNode);
- if (zIndex) {
- stepConfig.zIndex = zIndex + 1;
+ // Keep checking the remaining steps.
+ while (previousStepNumber >= 0) {
+ if (this.isStepPotentiallyVisible(this.getStepConfig(previousStepNumber))) {
+ return previousStepNumber;
+ }
+ previousStepNumber--;
}
- if (stepConfig.zIndex) {
- currentStepNode.css('zIndex', stepConfig.zIndex + 1);
- }
-
- // Add the backdrop.
- this.positionBackdrop(stepConfig);
-
- $(document.body).append(currentStepNode);
- this.currentStepNode = currentStepNode;
-
- // Ensure that the step node is positioned.
- // Some situations mean that the value is not properly calculated without this step.
- this.currentStepNode.css({
- top: 0,
- left: 0
- });
-
- animationTarget.animate({
- scrollTop: this.calculateScrollTop(stepConfig)
- }).promise().then(function () {
- this.positionStep(stepConfig);
- this.revealStep(stepConfig);
- }.bind(this));
- } else if (stepConfig.orphan) {
- stepConfig.isOrphan = true;
-
- // This will be appended to the body instead.
- stepConfig.attachTo = $('body').first();
- stepConfig.attachPoint = 'append';
-
- // Add the backdrop.
- this.positionBackdrop(stepConfig);
-
- // This is an orphaned step.
- currentStepNode.addClass('orphan');
-
- // It lives in the body.
- $(document.body).append(currentStepNode);
- this.currentStepNode = currentStepNode;
-
- this.currentStepNode.offset(this.calculateStepPositionInPage());
- this.currentStepNode.css('position', 'fixed');
-
- this.currentStepPopper = new Popper($('body'), this.currentStepNode[0], {
- removeOnDestroy: true,
- placement: stepConfig.placement + '-start',
- arrowElement: '[data-role="arrow"]',
- // Empty the modifiers. We've already placed the step and don't want it moved.
- modifiers: {
- hide: {
- enabled: false
- },
- applyStyle: {
- onLoad: null,
- enabled: false
- }
- }
- });
-
- this.revealStep(stepConfig);
+ return null;
}
- return this;
-};
+ /**
+ * Is the step the final step number?
+ *
+ * @method isLastStep
+ * @param {Integer} stepNumber Step number to test
+ * @return {Boolean} Whether the step is the final step
+ */
+ isLastStep(stepNumber) {
+ let nextStepNumber = this.getNextStepNumber(stepNumber);
-Tour.prototype.revealStep = function (stepConfig) {
- // Fade the step in.
- this.currentStepNode.fadeIn('', $.proxy(function () {
- // Announce via ARIA.
- this.announceStep(stepConfig);
-
- // Focus on the current step Node.
- this.currentStepNode.focus();
- window.setTimeout($.proxy(function () {
- // After a brief delay, focus again.
- // There seems to be an issue with Jaws where it only reads the dialogue title initially.
- // This second focus helps it to read the full dialogue.
- if (this.currentStepNode) {
- this.currentStepNode.focus();
- }
- }, this), 100);
- }, this));
-
- return this;
-};
-
-/**
- * Helper to announce the step on the page.
- *
- * @method announceStep
- * @param {Object} stepConfig The step configuration of the step
- * @chainable
- */
-Tour.prototype.announceStep = function (stepConfig) {
- // Setup the step Dialogue as per:
- // * https://www.w3.org/TR/wai-aria-practices/#dialog_nonmodal
- // * https://www.w3.org/TR/wai-aria-practices/#dialog_modal
-
- // Generate an ID for the current step node.
- var stepId = 'tour-step-' + this.tourName + '-' + stepConfig.stepNumber;
- this.currentStepNode.attr('id', stepId);
-
- var bodyRegion = this.currentStepNode.find('[data-placeholder="body"]').first();
- bodyRegion.attr('id', stepId + '-body');
- bodyRegion.attr('role', 'document');
-
- var headerRegion = this.currentStepNode.find('[data-placeholder="title"]').first();
- headerRegion.attr('id', stepId + '-title');
- headerRegion.attr('aria-labelledby', stepId + '-body');
-
- // Generally, a modal dialog has a role of dialog.
- this.currentStepNode.attr('role', 'dialog');
- this.currentStepNode.attr('tabindex', 0);
- this.currentStepNode.attr('aria-labelledby', stepId + '-title');
- this.currentStepNode.attr('aria-describedby', stepId + '-body');
-
- // Configure ARIA attributes on the target.
- var target = this.getStepTarget(stepConfig);
- if (target) {
- if (!target.attr('tabindex')) {
- target.attr('tabindex', 0);
- }
-
- target.data('original-describedby', target.attr('aria-describedby')).attr('aria-describedby', stepId + '-body');
+ return nextStepNumber === null;
}
- this.accessibilityShow(stepConfig);
+ /**
+ * Is the step the first step number?
+ *
+ * @method isFirstStep
+ * @param {Integer} stepNumber Step number to test
+ * @return {Boolean} Whether the step is the first step
+ */
+ isFirstStep(stepNumber) {
+ let previousStepNumber = this.getPreviousStepNumber(stepNumber);
- return this;
-};
-
-/**
- * Handle key down events.
- *
- * @method handleKeyDown
- * @param {EventFacade} e
- */
-Tour.prototype.handleKeyDown = function (e) {
- var tabbableSelector = 'a[href], link[href], [draggable=true], [contenteditable=true], :input:enabled, [tabindex], button:enabled';
- switch (e.keyCode) {
- case 27:
- this.endTour();
- break;
-
- // 9 == Tab - trap focus for items with a backdrop.
- case 9:
- // Tab must be handled on key up only in this instance.
- (function () {
- if (!this.currentStepConfig.hasBackdrop) {
- // Trapping tab focus is only handled for those steps with a backdrop.
- return;
- }
-
- // Find all tabbable locations.
- var activeElement = $(document.activeElement);
- var stepTarget = this.getStepTarget(this.currentStepConfig);
- var tabbableNodes = $(tabbableSelector);
- var dialogContainer = $('span[data-flexitour="container"]');
- var currentIndex = void 0;
- // Filter out element which is not belong to target section or dialogue.
- if (stepTarget) {
- tabbableNodes = tabbableNodes.filter(function (index, element) {
- return stepTarget != null && (stepTarget.has(element).length || dialogContainer.has(element).length || stepTarget.is(element) || dialogContainer.is(element));
- });
- }
-
- // Find index of focusing element.
- tabbableNodes.each(function (index, element) {
- if (activeElement.is(element)) {
- currentIndex = index;
- return false;
- }
- });
-
- var nextIndex = void 0;
- var nextNode = void 0;
- var focusRelevant = void 0;
- if (currentIndex != void 0) {
- var direction = 1;
- if (e.shiftKey) {
- direction = -1;
- }
- nextIndex = currentIndex;
- do {
- nextIndex += direction;
- nextNode = $(tabbableNodes[nextIndex]);
- } while (nextNode.length && nextNode.is(':disabled') || nextNode.is(':hidden'));
- if (nextNode.length) {
- // A new f
- focusRelevant = nextNode.closest(stepTarget).length;
- focusRelevant = focusRelevant || nextNode.closest(this.currentStepNode).length;
- } else {
- // Unable to find the target somehow.
- focusRelevant = false;
- }
- }
-
- if (focusRelevant) {
- nextNode.focus();
- } else {
- if (e.shiftKey) {
- // Focus on the last tabbable node in the step.
- this.currentStepNode.find(tabbableSelector).last().focus();
- } else {
- if (this.currentStepConfig.isOrphan) {
- // Focus on the step - there is no target.
- this.currentStepNode.focus();
- } else {
- // Focus on the step target.
- stepTarget.focus();
- }
- }
- }
- e.preventDefault();
- }).call(this);
- break;
- }
-};
-
-/**
- * Start the current tour.
- *
- * @method startTour
- * @param {Integer} startAt Which step number to start at. If not specified, starts at the last point.
- * @chainable
- */
-Tour.prototype.startTour = function (startAt) {
- if (this.storage && typeof startAt === 'undefined') {
- var storageStartValue = this.storage.getItem(this.storageKey);
- if (storageStartValue) {
- var storageStartAt = parseInt(storageStartValue, 10);
- if (storageStartAt <= this.steps.length) {
- startAt = storageStartAt;
- }
- }
+ return previousStepNumber === null;
}
- if (typeof startAt === 'undefined') {
- startAt = this.getCurrentStepNumber();
- }
-
- this.fireEventHandlers('beforeStart', startAt);
- this.gotoStep(startAt);
- this.fireEventHandlers('afterStart', startAt);
-
- return this;
-};
-
-/**
- * Restart the tour from the beginning, resetting the completionlag.
- *
- * @method restartTour
- * @chainable
- */
-Tour.prototype.restartTour = function () {
- return this.startTour(0);
-};
-
-/**
- * End the current tour.
- *
- * @method endTour
- * @chainable
- */
-Tour.prototype.endTour = function () {
- this.fireEventHandlers('beforeEnd');
-
- if (this.currentStepConfig) {
- var previousTarget = this.getStepTarget(this.currentStepConfig);
- if (previousTarget) {
- if (!previousTarget.attr('tabindex')) {
- previousTarget.attr('tabindex', '-1');
- }
- previousTarget.focus();
- }
- }
-
- this.hide(true);
-
- this.fireEventHandlers('afterEnd');
-
- return this;
-};
-
-/**
- * Hide any currently visible steps.
- *
- * @method hide
- * @chainable
- */
-Tour.prototype.hide = function (transition) {
- this.fireEventHandlers('beforeHide');
-
- if (this.currentStepNode && this.currentStepNode.length) {
- this.currentStepNode.hide();
- if (this.currentStepPopper) {
- this.currentStepPopper.destroy();
- }
- }
-
- // Restore original target configuration.
- if (this.currentStepConfig) {
- var target = this.getStepTarget(this.currentStepConfig);
- if (target) {
- if (target.data('original-labelledby')) {
- target.attr('aria-labelledby', target.data('original-labelledby'));
- }
-
- if (target.data('original-describedby')) {
- target.attr('aria-describedby', target.data('original-describedby'));
- }
-
- if (target.data('original-tabindex')) {
- target.attr('tabindex', target.data('tabindex'));
- }
- }
-
- // Clear the step configuration.
- this.currentStepConfig = null;
- }
-
- var fadeTime = 0;
- if (transition) {
- fadeTime = 400;
- }
-
- // Remove the backdrop features.
- $('[data-flexitour="step-background"]').remove();
- $('[data-flexitour="step-backdrop"]').removeAttr('data-flexitour');
- $('[data-flexitour="backdrop"]').fadeOut(fadeTime, function () {
- $(this).remove();
- });
-
- // Remove aria-describedby and tabindex attributes.
- if (this.currentStepNode && this.currentStepNode.length) {
- var stepId = this.currentStepNode.attr('id');
- if (stepId) {
- var currentStepElement = '[aria-describedby="' + stepId + '-body"]';
- $(currentStepElement).removeAttr('tabindex');
- $(currentStepElement).removeAttr('aria-describedby');
- }
- }
-
- // Reset the listeners.
- this.resetStepListeners();
-
- this.accessibilityHide();
-
- this.fireEventHandlers('afterHide');
-
- this.currentStepNode = null;
- this.currentStepPopper = null;
- return this;
-};
-
-/**
- * Show the current steps.
- *
- * @method show
- * @chainable
- */
-Tour.prototype.show = function () {
- // Show the current step.
- var startAt = this.getCurrentStepNumber();
-
- return this.gotoStep(startAt);
-};
-
-/**
- * Return the current step node.
- *
- * @method getStepContainer
- * @return {jQuery}
- */
-Tour.prototype.getStepContainer = function () {
- return $(this.currentStepNode);
-};
-
-/**
- * Calculate scrollTop.
- *
- * @method calculateScrollTop
- * @param {Object} stepConfig The step configuration of the step
- * @return {Number}
- */
-Tour.prototype.calculateScrollTop = function (stepConfig) {
- var scrollTop = $(window).scrollTop();
- var viewportHeight = $(window).height();
- var targetNode = this.getStepTarget(stepConfig);
-
- if (stepConfig.placement === 'top') {
- // If the placement is top, center scroll at the top of the target.
- scrollTop = targetNode.offset().top - viewportHeight / 2;
- } else if (stepConfig.placement === 'bottom') {
- // If the placement is bottom, center scroll at the bottom of the target.
- scrollTop = targetNode.offset().top + targetNode.height() - viewportHeight / 2;
- } else if (targetNode.height() <= viewportHeight * 0.8) {
- // If the placement is left/right, and the target fits in the viewport, centre screen on the target
- scrollTop = targetNode.offset().top - (viewportHeight - targetNode.height()) / 2;
- } else {
- // If the placement is left/right, and the target is bigger than the viewport, set scrollTop to target.top + buffer
- // and change step attachmentTarget to top+.
- scrollTop = targetNode.offset().top - viewportHeight * 0.2;
- }
-
- // Never scroll over the top.
- scrollTop = Math.max(0, scrollTop);
-
- // Never scroll beyond the bottom.
- scrollTop = Math.min($(document).height() - viewportHeight, scrollTop);
-
- return Math.ceil(scrollTop);
-};
-
-/**
- * Calculate dialogue position for page middle.
- *
- * @method calculateScrollTop
- * @return {Number}
- */
-Tour.prototype.calculateStepPositionInPage = function () {
- var viewportHeight = $(window).height();
- var stepHeight = this.currentStepNode.height();
-
- var viewportWidth = $(window).width();
- var stepWidth = this.currentStepNode.width();
-
- return {
- top: Math.ceil((viewportHeight - stepHeight) / 2),
- left: Math.ceil((viewportWidth - stepWidth) / 2)
- };
-};
-
-/**
- * Position the step on the page.
- *
- * @method positionStep
- * @param {Object} stepConfig The step configuration of the step
- * @chainable
- */
-Tour.prototype.positionStep = function (stepConfig) {
- var content = this.currentStepNode;
- if (!content || !content.length) {
- // Unable to find the step node.
- return this;
- }
-
- var flipBehavior = void 0;
- switch (stepConfig.placement) {
- case 'left':
- flipBehavior = ['left', 'right', 'top', 'bottom'];
- break;
- case 'right':
- flipBehavior = ['right', 'left', 'top', 'bottom'];
- break;
- case 'top':
- flipBehavior = ['top', 'bottom', 'right', 'left'];
- break;
- case 'bottom':
- flipBehavior = ['bottom', 'top', 'right', 'left'];
- break;
- default:
- flipBehavior = 'flip';
- break;
- }
-
- var target = this.getStepTarget(stepConfig);
- var config = {
- placement: stepConfig.placement + '-start',
- removeOnDestroy: true,
- modifiers: {
- flip: {
- behaviour: flipBehavior
- },
- arrow: {
- element: '[data-role="arrow"]'
- }
- },
- onCreate: function onCreate(data) {
- recalculateArrowPosition(data);
- },
- onUpdate: function onUpdate(data) {
- recalculateArrowPosition(data);
- }
- };
-
- var recalculateArrowPosition = function recalculateArrowPosition(data) {
- var placement = data.placement.split('-')[0];
- var isVertical = ['left', 'right'].indexOf(placement) !== -1;
- var arrowElement = data.instance.popper.querySelector('[data-role="arrow"]');
- var stepElement = $(data.instance.popper.querySelector('[data-role="flexitour-step"]'));
- if (isVertical) {
- var arrowHeight = parseFloat(window.getComputedStyle(arrowElement).height);
- var arrowOffset = parseFloat(window.getComputedStyle(arrowElement).top);
- var popperHeight = parseFloat(window.getComputedStyle(data.instance.popper).height);
- var popperOffset = parseFloat(window.getComputedStyle(data.instance.popper).top);
- var popperBorderWidth = parseFloat(stepElement.css('borderTopWidth'));
- var popperBorderRadiusWidth = parseFloat(stepElement.css('borderTopLeftRadius')) * 2;
- var arrowPos = arrowOffset + arrowHeight / 2;
- var maxPos = popperHeight + popperOffset - popperBorderWidth - popperBorderRadiusWidth;
- var minPos = popperOffset + popperBorderWidth + popperBorderRadiusWidth;
- if (arrowPos >= maxPos || arrowPos <= minPos) {
- var newArrowPos = 0;
- if (arrowPos > popperHeight / 2) {
- newArrowPos = maxPos - arrowHeight;
- } else {
- newArrowPos = minPos + arrowHeight;
- }
- $(arrowElement).css('top', newArrowPos);
- }
- } else {
- var arrowWidth = parseFloat(window.getComputedStyle(arrowElement).width);
- var _arrowOffset = parseFloat(window.getComputedStyle(arrowElement).left);
- var popperWidth = parseFloat(window.getComputedStyle(data.instance.popper).width);
- var _popperOffset = parseFloat(window.getComputedStyle(data.instance.popper).left);
- var _popperBorderWidth = parseFloat(stepElement.css('borderTopWidth'));
- var _popperBorderRadiusWidth = parseFloat(stepElement.css('borderTopLeftRadius')) * 2;
- var _arrowPos = _arrowOffset + arrowWidth / 2;
- var _maxPos = popperWidth + _popperOffset - _popperBorderWidth - _popperBorderRadiusWidth;
- var _minPos = _popperOffset + _popperBorderWidth + _popperBorderRadiusWidth;
- if (_arrowPos >= _maxPos || _arrowPos <= _minPos) {
- var _newArrowPos = 0;
- if (_arrowPos > popperWidth / 2) {
- _newArrowPos = _maxPos - arrowWidth;
- } else {
- _newArrowPos = _minPos + arrowWidth;
- }
- $(arrowElement).css('left', _newArrowPos);
- }
- }
- };
-
- var background = $('[data-flexitour="step-background"]');
- if (background.length) {
- target = background;
- }
- this.currentStepPopper = new Popper(target, content[0], config);
-
- return this;
-};
-
-/**
- * Add the backdrop.
- *
- * @method positionBackdrop
- * @param {Object} stepConfig The step configuration of the step
- * @chainable
- */
-Tour.prototype.positionBackdrop = function (stepConfig) {
- if (stepConfig.backdrop) {
- this.currentStepConfig.hasBackdrop = true;
- var backdrop = $('');
-
- if (stepConfig.zIndex) {
- if (stepConfig.attachPoint === 'append') {
- stepConfig.attachTo.append(backdrop);
- } else {
- backdrop.insertAfter(stepConfig.attachTo);
- }
- } else {
- $('body').append(backdrop);
+ /**
+ * Is this step potentially visible?
+ *
+ * @method isStepPotentiallyVisible
+ * @param {Object} stepConfig The step configuration to normalise
+ * @return {Boolean} Whether the step is the potentially visible
+ */
+ isStepPotentiallyVisible(stepConfig) {
+ if (!stepConfig) {
+ // Without step config, there can be no step.
+ return false;
}
if (this.isStepActuallyVisible(stepConfig)) {
- // The step has a visible target.
- // Punch a hole through the backdrop.
- var background = $('');
+ // If it is actually visible, it is already potentially visible.
+ return true;
+ }
+ if (typeof stepConfig.orphan !== 'undefined' && stepConfig.orphan) {
+ // Orphan steps have no target. They are always visible.
+ return true;
+ }
+
+ if (typeof stepConfig.delay !== 'undefined' && stepConfig.delay) {
+ // Only return true if the activated has not been used yet.
+ return true;
+ }
+
+ // Not theoretically, or actually visible.
+ return false;
+ }
+
+ /**
+ * Is this step actually visible?
+ *
+ * @method isStepActuallyVisible
+ * @param {Object} stepConfig The step configuration to normalise
+ * @return {Boolean} Whether the step is actually visible
+ */
+ isStepActuallyVisible(stepConfig) {
+ if (!stepConfig) {
+ // Without step config, there can be no step.
+ return false;
+ }
+
+ let target = this.getStepTarget(stepConfig);
+ if (target && target.length && target.is(':visible')) {
+ // Without a target, there can be no step.
+ return !!target.length;
+ }
+
+ return false;
+ }
+
+ /**
+ * Go to the next step in the tour.
+ *
+ * @method next
+ * @chainable
+ * @return {Object} this.
+ */
+ next() {
+ return this.gotoStep(this.getNextStepNumber());
+ }
+
+ /**
+ * Go to the previous step in the tour.
+ *
+ * @method previous
+ * @chainable
+ * @return {Object} this.
+ */
+ previous() {
+ return this.gotoStep(this.getPreviousStepNumber(), -1);
+ }
+
+ /**
+ * Go to the specified step in the tour.
+ *
+ * @method gotoStep
+ * @param {Integer} stepNumber The step number to display
+ * @param {Integer} direction Next or previous step
+ * @chainable
+ * @return {Object} this.
+ */
+ gotoStep(stepNumber, direction) {
+ if (stepNumber < 0) {
+ return this.endTour();
+ }
+
+ let stepConfig = this.getStepConfig(stepNumber);
+ if (stepConfig === null) {
+ return this.endTour();
+ }
+
+ return this._gotoStep(stepConfig, direction);
+ }
+
+ _gotoStep(stepConfig, direction) {
+ if (!stepConfig) {
+ return this.endTour();
+ }
+
+ if (typeof stepConfig.delay !== 'undefined' && stepConfig.delay && !stepConfig.delayed) {
+ stepConfig.delayed = true;
+ window.setTimeout(this._gotoStep.bind(this), stepConfig.delay, stepConfig, direction);
+
+ return this;
+ } else if (!stepConfig.orphan && !this.isStepActuallyVisible(stepConfig)) {
+ let fn = direction == -1 ? 'getPreviousStepNumber' : 'getNextStepNumber';
+ return this.gotoStep(this[fn](stepConfig.stepNumber), direction);
+ }
+
+ this.hide();
+
+ this.fireEventHandlers('beforeRender', stepConfig);
+ this.renderStep(stepConfig);
+ this.fireEventHandlers('afterRender', stepConfig);
+
+ return this;
+ }
+
+ /**
+ * Fetch the normalised step configuration for the specified step number.
+ *
+ * @method getStepConfig
+ * @param {Integer} stepNumber The step number to fetch configuration for
+ * @return {Object} The step configuration
+ */
+ getStepConfig(stepNumber) {
+ if (stepNumber === null || stepNumber < 0 || stepNumber >= this.steps.length) {
+ return null;
+ }
+
+ // Normalise the step configuration.
+ let stepConfig = this.normalizeStepConfig(this.steps[stepNumber]);
+
+ // Add the stepNumber to the stepConfig.
+ stepConfig = $.extend(stepConfig, {stepNumber: stepNumber});
+
+ return stepConfig;
+ }
+
+ /**
+ * Normalise the supplied step configuration.
+ *
+ * @method normalizeStepConfig
+ * @param {Object} stepConfig The step configuration to normalise
+ * @return {Object} The normalised step configuration
+ */
+ normalizeStepConfig(stepConfig) {
+
+ if (typeof stepConfig.reflex !== 'undefined' && typeof stepConfig.moveAfterClick === 'undefined') {
+ stepConfig.moveAfterClick = stepConfig.reflex;
+ }
+
+ if (typeof stepConfig.element !== 'undefined' && typeof stepConfig.target === 'undefined') {
+ stepConfig.target = stepConfig.element;
+ }
+
+ if (typeof stepConfig.content !== 'undefined' && typeof stepConfig.body === 'undefined') {
+ stepConfig.body = stepConfig.content;
+ }
+
+ stepConfig = $.extend({}, this.stepDefaults, stepConfig);
+
+ stepConfig = $.extend({}, {
+ attachTo: stepConfig.target,
+ attachPoint: 'after',
+ }, stepConfig);
+
+ if (stepConfig.attachTo) {
+ stepConfig.attachTo = $(stepConfig.attachTo).first();
+ }
+
+ return stepConfig;
+ }
+
+ /**
+ * Fetch the actual step target from the selector.
+ *
+ * This should not be called until after any delay has completed.
+ *
+ * @method getStepTarget
+ * @param {Object} stepConfig The step configuration
+ * @return {$}
+ */
+ getStepTarget(stepConfig) {
+ if (stepConfig.target) {
+ return $(stepConfig.target);
+ }
+
+ return null;
+ }
+
+ /**
+ * Fire any event handlers for the specified event.
+ *
+ * @param {String} eventName The name of the event to handle
+ * @param {Object} data Any data to pass to the event
+ * @chainable
+ * @return {Object} this.
+ */
+ fireEventHandlers(eventName, data) {
+ if (typeof this.eventHandlers[eventName] === 'undefined') {
+ return this;
+ }
+
+ this.eventHandlers[eventName].forEach(function(thisEvent) {
+ thisEvent.call(this, data);
+ }, this);
+
+ return this;
+ }
+
+ /**
+ * @method addEventHandler
+ * @param {string} eventName The name of the event to listen for
+ * @param {function} handler The event handler to call
+ * @return {Object} this.
+ */
+ addEventHandler(eventName, handler) {
+ if (typeof this.eventHandlers[eventName] === 'undefined') {
+ this.eventHandlers[eventName] = [];
+ }
+
+ this.eventHandlers[eventName].push(handler);
+
+ return this;
+ }
+
+ /**
+ * Process listeners for the step being shown.
+ *
+ * @method processStepListeners
+ * @param {object} stepConfig The configuration for the step
+ * @chainable
+ * @return {Object} this.
+ */
+ processStepListeners(stepConfig) {
+ this.listeners.push(
+ // Next/Previous buttons.
+ {
+ node: this.currentStepNode,
+ args: ['click', '[data-role="next"]', $.proxy(this.next, this)]
+ }, {
+ node: this.currentStepNode,
+ args: ['click', '[data-role="previous"]', $.proxy(this.previous, this)]
+ },
+
+ // Close and end tour buttons.
+ {
+ node: this.currentStepNode,
+ args: ['click', '[data-role="end"]', $.proxy(this.endTour, this)]
+ },
+
+ // Click backdrop and hide tour.
+ {
+ node: $('[data-flexitour="backdrop"]'),
+ args: ['click', $.proxy(this.hide, this)]
+ },
+
+ // Keypresses.
+ {
+ node: $('body'),
+ args: ['keydown', $.proxy(this.handleKeyDown, this)]
+ });
+
+ if (stepConfig.moveOnClick) {
var targetNode = this.getStepTarget(stepConfig);
+ this.listeners.push({
+ node: targetNode,
+ args: ['click', $.proxy(function(e) {
+ if ($(e.target).parents('[data-flexitour="container"]').length === 0) {
+ // Ignore clicks when they are in the flexitour.
+ window.setTimeout($.proxy(this.next, this), 500);
+ }
+ }, this)]
+ });
+ }
- var buffer = 10;
+ this.listeners.forEach(function (listener) {
+ listener.node.on.apply(listener.node, listener.args);
+ });
- var colorNode = targetNode;
- if (buffer) {
- colorNode = $('body');
+ return this;
+ }
+
+ /**
+ * Reset step listeners.
+ *
+ * @method resetStepListeners
+ * @chainable
+ * @return {Object} this.
+ */
+ resetStepListeners() {
+ // Stop listening to all external handlers.
+ if (this.listeners) {
+ this.listeners.forEach(function(listener) {
+ listener.node.off.apply(listener.node, listener.args);
+ });
+ }
+ this.listeners = [];
+
+ return this;
+ }
+
+ /**
+ * The standard step renderer.
+ *
+ * @method renderStep
+ * @param {Object} stepConfig The step configuration of the step
+ * @chainable
+ * @return {Object} this.
+ */
+ renderStep(stepConfig) {
+ // Store the current step configuration for later.
+ this.currentStepConfig = stepConfig;
+ this.setCurrentStepNumber(stepConfig.stepNumber);
+
+ // Fetch the template and convert it to a $ object.
+ let template = $(this.getTemplateContent());
+
+ // Title.
+ template.find('[data-placeholder="title"]')
+ .html(stepConfig.title);
+
+ // Body.
+ template.find('[data-placeholder="body"]')
+ .html(stepConfig.body);
+
+ // Is this the first step?
+ if (this.isFirstStep(stepConfig.stepNumber)) {
+ template.find('[data-role="previous"]').prop('disabled', true);
+ } else {
+ template.find('[data-role="previous"]').prop('disabled', false);
+ }
+
+ // Is this the final step?
+ if (this.isLastStep(stepConfig.stepNumber)) {
+ template.find('[data-role="next"]').prop('disabled', true);
+ } else {
+ template.find('[data-role="next"]').prop('disabled', false);
+ }
+
+ template.find('[data-role="previous"]').attr('role', 'button');
+ template.find('[data-role="next"]').attr('role', 'button');
+ template.find('[data-role="end"]').attr('role', 'button');
+
+ // Replace the template with the updated version.
+ stepConfig.template = template;
+
+ // Add to the page.
+ this.addStepToPage(stepConfig);
+
+ // Process step listeners after adding to the page.
+ // This uses the currentNode.
+ this.processStepListeners(stepConfig);
+
+ return this;
+ }
+
+ /**
+ * Getter for the template content.
+ *
+ * @method getTemplateContent
+ * @return {$}
+ */
+ getTemplateContent() {
+ return $(this.templateContent).clone();
+ }
+
+ /**
+ * Helper to add a step to the page.
+ *
+ * @method addStepToPage
+ * @param {Object} stepConfig The step configuration of the step
+ * @chainable
+ * @return {Object} this.
+ */
+ addStepToPage(stepConfig) {
+ // Create the stepNode from the template data.
+ let currentStepNode = $('')
+ .html(stepConfig.template)
+ .hide();
+
+ // The scroll animation occurs on the body or html.
+ let animationTarget = $('body, html')
+ .stop(true, true);
+
+ if (this.isStepActuallyVisible(stepConfig)) {
+ let targetNode = this.getStepTarget(stepConfig);
+
+ targetNode.data('flexitour', 'target');
+
+ let zIndex = this.calculateZIndex(targetNode);
+ if (zIndex) {
+ stepConfig.zIndex = zIndex + 1;
}
- background.css({
- width: targetNode.outerWidth() + buffer + buffer,
- height: targetNode.outerHeight() + buffer + buffer,
- left: targetNode.offset().left - buffer,
- top: targetNode.offset().top - buffer,
- backgroundColor: this.calculateInherittedBackgroundColor(colorNode)
+ if (stepConfig.zIndex) {
+ currentStepNode.css('zIndex', stepConfig.zIndex + 1);
+ }
+
+ // Add the backdrop.
+ this.positionBackdrop(stepConfig);
+
+ $(document.body).append(currentStepNode);
+ this.currentStepNode = currentStepNode;
+
+ // Ensure that the step node is positioned.
+ // Some situations mean that the value is not properly calculated without this step.
+ this.currentStepNode.css({
+ top: 0,
+ left: 0,
});
- if (targetNode.offset().left < buffer) {
- background.css({
- width: targetNode.outerWidth() + targetNode.offset().left + buffer,
- left: targetNode.offset().left
- });
+ animationTarget
+ .animate({
+ scrollTop: this.calculateScrollTop(stepConfig),
+ }).promise().then(function() {
+ this.positionStep(stepConfig);
+ this.revealStep(stepConfig);
+ return;
+ }.bind(this))
+ .catch(function() {
+ // Silently fail.
+ });
+
+ } else if (stepConfig.orphan) {
+ stepConfig.isOrphan = true;
+
+ // This will be appended to the body instead.
+ stepConfig.attachTo = $('body').first();
+ stepConfig.attachPoint = 'append';
+
+ // Add the backdrop.
+ this.positionBackdrop(stepConfig);
+
+ // This is an orphaned step.
+ currentStepNode.addClass('orphan');
+
+ // It lives in the body.
+ $(document.body).append(currentStepNode);
+ this.currentStepNode = currentStepNode;
+
+ this.currentStepNode.offset(this.calculateStepPositionInPage());
+ this.currentStepNode.css('position', 'fixed');
+
+ this.currentStepPopper = new Popper(
+ $('body'),
+ this.currentStepNode[0], {
+ removeOnDestroy: true,
+ placement: stepConfig.placement + '-start',
+ arrowElement: '[data-role="arrow"]',
+ // Empty the modifiers. We've already placed the step and don't want it moved.
+ modifiers: {
+ hide: {
+ enabled: false,
+ },
+ applyStyle: {
+ onLoad: null,
+ enabled: false,
+ },
+ }
+ }
+ );
+
+ this.revealStep(stepConfig);
+ }
+
+ return this;
+ }
+
+ /**
+ * Make the given step visible.
+ *
+ * @method revealStep
+ * @param {Object} stepConfig The step configuration of the step
+ * @chainable
+ * @return {Object} this.
+ */
+ revealStep(stepConfig) {
+ // Fade the step in.
+ this.currentStepNode.fadeIn('', $.proxy(function() {
+ // Announce via ARIA.
+ this.announceStep(stepConfig);
+
+ // Focus on the current step Node.
+ this.currentStepNode.focus();
+ window.setTimeout($.proxy(function() {
+ // After a brief delay, focus again.
+ // There seems to be an issue with Jaws where it only reads the dialogue title initially.
+ // This second focus helps it to read the full dialogue.
+ if (this.currentStepNode) {
+ this.currentStepNode.focus();
+ }
+ }, this), 100);
+
+ }, this));
+
+ return this;
+ }
+
+ /**
+ * Helper to announce the step on the page.
+ *
+ * @method announceStep
+ * @param {Object} stepConfig The step configuration of the step
+ * @chainable
+ * @return {Object} this.
+ */
+ announceStep(stepConfig) {
+ // Setup the step Dialogue as per:
+ // * https://www.w3.org/TR/wai-aria-practices/#dialog_nonmodal
+ // * https://www.w3.org/TR/wai-aria-practices/#dialog_modal
+
+ // Generate an ID for the current step node.
+ let stepId = 'tour-step-' + this.tourName + '-' + stepConfig.stepNumber;
+ this.currentStepNode.attr('id', stepId);
+
+ let bodyRegion = this.currentStepNode.find('[data-placeholder="body"]').first();
+ bodyRegion.attr('id', stepId + '-body');
+ bodyRegion.attr('role', 'document');
+
+ let headerRegion = this.currentStepNode.find('[data-placeholder="title"]').first();
+ headerRegion.attr('id', stepId + '-title');
+ headerRegion.attr('aria-labelledby', stepId + '-body');
+
+ // Generally, a modal dialog has a role of dialog.
+ this.currentStepNode.attr('role', 'dialog');
+ this.currentStepNode.attr('tabindex', 0);
+ this.currentStepNode.attr('aria-labelledby', stepId + '-title');
+ this.currentStepNode.attr('aria-describedby', stepId + '-body');
+
+ // Configure ARIA attributes on the target.
+ let target = this.getStepTarget(stepConfig);
+ if (target) {
+ if (!target.attr('tabindex')) {
+ target.attr('tabindex', 0);
}
- if (targetNode.offset().top < buffer) {
- background.css({
- height: targetNode.outerHeight() + targetNode.offset().top + buffer,
- top: targetNode.offset().top
- });
+ target
+ .data('original-describedby', target.attr('aria-describedby'))
+ .attr('aria-describedby', stepId + '-body')
+ ;
+ }
+
+ this.accessibilityShow(stepConfig);
+
+ return this;
+ }
+
+ /**
+ * Handle key down events.
+ *
+ * @method handleKeyDown
+ * @param {EventFacade} e
+ */
+ handleKeyDown(e) {
+ let tabbableSelector = 'a[href], link[href], [draggable=true], [contenteditable=true], ';
+ tabbableSelector += ':input:enabled, [tabindex], button:enabled';
+ switch (e.keyCode) {
+ case 27:
+ this.endTour();
+ break;
+
+ // 9 == Tab - trap focus for items with a backdrop.
+ case 9:
+ // Tab must be handled on key up only in this instance.
+ (function() {
+ if (!this.currentStepConfig.hasBackdrop) {
+ // Trapping tab focus is only handled for those steps with a backdrop.
+ return;
+ }
+
+ // Find all tabbable locations.
+ let activeElement = $(document.activeElement);
+ let stepTarget = this.getStepTarget(this.currentStepConfig);
+ let tabbableNodes = $(tabbableSelector);
+ let dialogContainer = $('span[data-flexitour="container"]');
+ let currentIndex;
+ // Filter out element which is not belong to target section or dialogue.
+ if (stepTarget) {
+ tabbableNodes = tabbableNodes.filter(function(index, element) {
+ return stepTarget !== null
+ && (stepTarget.has(element).length
+ || dialogContainer.has(element).length
+ || stepTarget.is(element)
+ || dialogContainer.is(element));
+ });
+ }
+
+ // Find index of focusing element.
+ tabbableNodes.each(function(index, element) {
+ if (activeElement.is(element)) {
+ currentIndex = index;
+ return false;
+ }
+ // Keep looping.
+ return true;
+ });
+
+ let nextIndex;
+ let nextNode;
+ let focusRelevant;
+ if (currentIndex != void 0) {
+ let direction = 1;
+ if (e.shiftKey) {
+ direction = -1;
+ }
+ nextIndex = currentIndex;
+ do {
+ nextIndex += direction;
+ nextNode = $(tabbableNodes[nextIndex]);
+ } while (nextNode.length && nextNode.is(':disabled') || nextNode.is(':hidden'));
+ if (nextNode.length) {
+ // A new f
+ focusRelevant = nextNode.closest(stepTarget).length;
+ focusRelevant = focusRelevant || nextNode.closest(this.currentStepNode).length;
+ } else {
+ // Unable to find the target somehow.
+ focusRelevant = false;
+ }
+ }
+
+ if (focusRelevant) {
+ nextNode.focus();
+ } else {
+ if (e.shiftKey) {
+ // Focus on the last tabbable node in the step.
+ this.currentStepNode.find(tabbableSelector).last().focus();
+ } else {
+ if (this.currentStepConfig.isOrphan) {
+ // Focus on the step - there is no target.
+ this.currentStepNode.focus();
+ } else {
+ // Focus on the step target.
+ stepTarget.focus();
+ }
+ }
+ }
+ e.preventDefault();
+ }).call(this);
+ break;
+ }
+ }
+
+ /**
+ * Start the current tour.
+ *
+ * @method startTour
+ * @param {Integer} startAt Which step number to start at. If not specified, starts at the last point.
+ * @chainable
+ * @return {Object} this.
+ */
+ startTour(startAt) {
+ if (this.storage && typeof startAt === 'undefined') {
+ let storageStartValue = this.storage.getItem(this.storageKey);
+ if (storageStartValue) {
+ let storageStartAt = parseInt(storageStartValue, 10);
+ if (storageStartAt <= this.steps.length) {
+ startAt = storageStartAt;
+ }
+ }
+ }
+
+ if (typeof startAt === 'undefined') {
+ startAt = this.getCurrentStepNumber();
+ }
+
+ this.fireEventHandlers('beforeStart', startAt);
+ this.gotoStep(startAt);
+ this.fireEventHandlers('afterStart', startAt);
+
+ return this;
+ }
+
+ /**
+ * Restart the tour from the beginning, resetting the completionlag.
+ *
+ * @method restartTour
+ * @chainable
+ * @return {Object} this.
+ */
+ restartTour() {
+ return this.startTour(0);
+ }
+
+ /**
+ * End the current tour.
+ *
+ * @method endTour
+ * @chainable
+ * @return {Object} this.
+ */
+ endTour() {
+ this.fireEventHandlers('beforeEnd');
+
+ if (this.currentStepConfig) {
+ let previousTarget = this.getStepTarget(this.currentStepConfig);
+ if (previousTarget) {
+ if (!previousTarget.attr('tabindex')) {
+ previousTarget.attr('tabindex', '-1');
+ }
+ previousTarget.focus();
+ }
+ }
+
+ this.hide(true);
+
+ this.fireEventHandlers('afterEnd');
+
+ return this;
+ }
+
+ /**
+ * Hide any currently visible steps.
+ *
+ * @method hide
+ * @param {Bool} transition Animate the visibility change
+ * @chainable
+ * @return {Object} this.
+ */
+ hide(transition) {
+ this.fireEventHandlers('beforeHide');
+
+ if (this.currentStepNode && this.currentStepNode.length) {
+ this.currentStepNode.hide();
+ if (this.currentStepPopper) {
+ this.currentStepPopper.destroy();
+ }
+ }
+
+ // Restore original target configuration.
+ if (this.currentStepConfig) {
+ let target = this.getStepTarget(this.currentStepConfig);
+ if (target) {
+ if (target.data('original-labelledby')) {
+ target.attr('aria-labelledby', target.data('original-labelledby'));
+ }
+
+ if (target.data('original-describedby')) {
+ target.attr('aria-describedby', target.data('original-describedby'));
+ }
+
+ if (target.data('original-tabindex')) {
+ target.attr('tabindex', target.data('tabindex'));
+ }
}
- var targetRadius = targetNode.css('borderRadius');
- if (targetRadius && targetRadius !== $('body').css('borderRadius')) {
- background.css('borderRadius', targetRadius);
- }
+ // Clear the step configuration.
+ this.currentStepConfig = null;
+ }
- var targetPosition = this.calculatePosition(targetNode);
- if (targetPosition === 'fixed') {
- background.css('top', 0);
- } else if (targetPosition === 'absolute') {
- background.css('position', 'fixed');
- }
+ let fadeTime = 0;
+ if (transition) {
+ fadeTime = 400;
+ }
- var fader = background.clone();
- fader.css({
- backgroundColor: backdrop.css('backgroundColor'),
- opacity: backdrop.css('opacity')
- });
- fader.attr('data-flexitour', 'step-background-fader');
+ // Remove the backdrop features.
+ $('[data-flexitour="step-background"]').remove();
+ $('[data-flexitour="step-backdrop"]').removeAttr('data-flexitour');
+ $('[data-flexitour="backdrop"]').fadeOut(fadeTime, function() {
+ $(this).remove();
+ });
+
+ // Remove aria-describedby and tabindex attributes.
+ if (this.currentStepNode && this.currentStepNode.length) {
+ let stepId = this.currentStepNode.attr('id');
+ if (stepId) {
+ let currentStepElement = '[aria-describedby="' + stepId + '-body"]';
+ $(currentStepElement).removeAttr('tabindex');
+ $(currentStepElement).removeAttr('aria-describedby');
+ }
+ }
+
+ // Reset the listeners.
+ this.resetStepListeners();
+
+ this.accessibilityHide();
+
+ this.fireEventHandlers('afterHide');
+
+ this.currentStepNode = null;
+ this.currentStepPopper = null;
+ return this;
+ }
+
+ /**
+ * Show the current steps.
+ *
+ * @method show
+ * @chainable
+ * @return {Object} this.
+ */
+ show() {
+ // Show the current step.
+ let startAt = this.getCurrentStepNumber();
+
+ return this.gotoStep(startAt);
+ }
+
+ /**
+ * Return the current step node.
+ *
+ * @method getStepContainer
+ * @return {jQuery}
+ */
+ getStepContainer() {
+ return $(this.currentStepNode);
+ }
+
+ /**
+ * Calculate scrollTop.
+ *
+ * @method calculateScrollTop
+ * @param {Object} stepConfig The step configuration of the step
+ * @return {Number}
+ */
+ calculateScrollTop(stepConfig) {
+ let scrollTop = $(window).scrollTop();
+ let viewportHeight = $(window).height();
+ let targetNode = this.getStepTarget(stepConfig);
+
+ if (stepConfig.placement === 'top') {
+ // If the placement is top, center scroll at the top of the target.
+ scrollTop = targetNode.offset().top - (viewportHeight / 2);
+ } else if (stepConfig.placement === 'bottom') {
+ // If the placement is bottom, center scroll at the bottom of the target.
+ scrollTop = targetNode.offset().top + targetNode.height() - (viewportHeight / 2);
+ } else if (targetNode.height() <= (viewportHeight * 0.8)) {
+ // If the placement is left/right, and the target fits in the viewport, centre screen on the target
+ scrollTop = targetNode.offset().top - ((viewportHeight - targetNode.height()) / 2);
+ } else {
+ // If the placement is left/right, and the target is bigger than the viewport, set scrollTop to target.top + buffer
+ // and change step attachmentTarget to top+.
+ scrollTop = targetNode.offset().top - (viewportHeight * 0.2);
+ }
+
+ // Never scroll over the top.
+ scrollTop = Math.max(0, scrollTop);
+
+ // Never scroll beyond the bottom.
+ scrollTop = Math.min($(document).height() - viewportHeight, scrollTop);
+
+ return Math.ceil(scrollTop);
+ }
+
+ /**
+ * Calculate dialogue position for page middle.
+ *
+ * @method calculateScrollTop
+ * @return {Number}
+ */
+ calculateStepPositionInPage() {
+ let viewportHeight = $(window).height();
+ let stepHeight = this.currentStepNode.height();
+
+ let viewportWidth = $(window).width();
+ let stepWidth = this.currentStepNode.width();
+
+ return {
+ top: Math.ceil((viewportHeight - stepHeight) / 2),
+ left: Math.ceil((viewportWidth - stepWidth) / 2)
+ };
+ }
+
+ /**
+ * Position the step on the page.
+ *
+ * @method positionStep
+ * @param {Object} stepConfig The step configuration of the step
+ * @chainable
+ * @return {Object} this.
+ */
+ positionStep(stepConfig) {
+ let content = this.currentStepNode;
+ if (!content || !content.length) {
+ // Unable to find the step node.
+ return this;
+ }
+
+ let flipBehavior;
+ switch (stepConfig.placement) {
+ case 'left':
+ flipBehavior = ['left', 'right', 'top', 'bottom'];
+ break;
+ case 'right':
+ flipBehavior = ['right', 'left', 'top', 'bottom'];
+ break;
+ case 'top':
+ flipBehavior = ['top', 'bottom', 'right', 'left'];
+ break;
+ case 'bottom':
+ flipBehavior = ['bottom', 'top', 'right', 'left'];
+ break;
+ default:
+ flipBehavior = 'flip';
+ break;
+ }
+
+ let target = this.getStepTarget(stepConfig);
+ var config = {
+ placement: stepConfig.placement + '-start',
+ removeOnDestroy: true,
+ modifiers: {
+ flip: {
+ behaviour: flipBehavior,
+ },
+ arrow: {
+ element: '[data-role="arrow"]',
+ },
+ },
+ onCreate: function(data) {
+ recalculateArrowPosition(data);
+ },
+ onUpdate: function(data) {
+ recalculateArrowPosition(data);
+ },
+ };
+
+ let recalculateArrowPosition = function(data) {
+ let placement = data.placement.split('-')[0];
+ const isVertical = ['left', 'right'].indexOf(placement) !== -1;
+ const arrowElement = data.instance.popper.querySelector('[data-role="arrow"]');
+ const stepElement = $(data.instance.popper.querySelector('[data-role="flexitour-step"]'));
+ if (isVertical) {
+ let arrowHeight = parseFloat(window.getComputedStyle(arrowElement).height);
+ let arrowOffset = parseFloat(window.getComputedStyle(arrowElement).top);
+ let popperHeight = parseFloat(window.getComputedStyle(data.instance.popper).height);
+ let popperOffset = parseFloat(window.getComputedStyle(data.instance.popper).top);
+ let popperBorderWidth = parseFloat(stepElement.css('borderTopWidth'));
+ let popperBorderRadiusWidth = parseFloat(stepElement.css('borderTopLeftRadius')) * 2;
+ let arrowPos = arrowOffset + (arrowHeight / 2);
+ let maxPos = popperHeight + popperOffset - popperBorderWidth - popperBorderRadiusWidth;
+ let minPos = popperOffset + popperBorderWidth + popperBorderRadiusWidth;
+ if (arrowPos >= maxPos || arrowPos <= minPos) {
+ let newArrowPos = 0;
+ if (arrowPos > (popperHeight / 2)) {
+ newArrowPos = maxPos - arrowHeight;
+ } else {
+ newArrowPos = minPos + arrowHeight;
+ }
+ $(arrowElement).css('top', newArrowPos);
+ }
+ } else {
+ let arrowWidth = parseFloat(window.getComputedStyle(arrowElement).width);
+ let arrowOffset = parseFloat(window.getComputedStyle(arrowElement).left);
+ let popperWidth = parseFloat(window.getComputedStyle(data.instance.popper).width);
+ let popperOffset = parseFloat(window.getComputedStyle(data.instance.popper).left);
+ let popperBorderWidth = parseFloat(stepElement.css('borderTopWidth'));
+ let popperBorderRadiusWidth = parseFloat(stepElement.css('borderTopLeftRadius')) * 2;
+ let arrowPos = arrowOffset + (arrowWidth / 2);
+ let maxPos = popperWidth + popperOffset - popperBorderWidth - popperBorderRadiusWidth;
+ let minPos = popperOffset + popperBorderWidth + popperBorderRadiusWidth;
+ if (arrowPos >= maxPos || arrowPos <= minPos) {
+ let newArrowPos = 0;
+ if (arrowPos > (popperWidth / 2)) {
+ newArrowPos = maxPos - arrowWidth;
+ } else {
+ newArrowPos = minPos + arrowWidth;
+ }
+ $(arrowElement).css('left', newArrowPos);
+ }
+ }
+ };
+
+ let background = $('[data-flexitour="step-background"]');
+ if (background.length) {
+ target = background;
+ }
+ this.currentStepPopper = new Popper(target, content[0], config);
+
+ return this;
+ }
+
+ /**
+ * Add the backdrop.
+ *
+ * @method positionBackdrop
+ * @param {Object} stepConfig The step configuration of the step
+ * @chainable
+ * @return {Object} this.
+ */
+ positionBackdrop(stepConfig) {
+ if (stepConfig.backdrop) {
+ this.currentStepConfig.hasBackdrop = true;
+ let backdrop = $('');
if (stepConfig.zIndex) {
if (stepConfig.attachPoint === 'append') {
- stepConfig.attachTo.append(background);
+ stepConfig.attachTo.append(backdrop);
} else {
- fader.insertAfter(stepConfig.attachTo);
- background.insertAfter(stepConfig.attachTo);
+ backdrop.insertAfter(stepConfig.attachTo);
}
} else {
- $('body').append(fader);
- $('body').append(background);
+ $('body').append(backdrop);
}
- // Add the backdrop data to the actual target.
- // This is the part which actually does the work.
- targetNode.attr('data-flexitour', 'step-backdrop');
+ if (this.isStepActuallyVisible(stepConfig)) {
+ // The step has a visible target.
+ // Punch a hole through the backdrop.
+ let background = $('');
- if (stepConfig.zIndex) {
- backdrop.css('zIndex', stepConfig.zIndex);
- background.css('zIndex', stepConfig.zIndex + 1);
- targetNode.css('zIndex', stepConfig.zIndex + 2);
- }
+ let targetNode = this.getStepTarget(stepConfig);
- fader.fadeOut('2000', function () {
- $(this).remove();
- });
- }
- }
- return this;
-};
+ let buffer = 10;
-/**
- * Calculate the inheritted z-index.
- *
- * @method calculateZIndex
- * @param {jQuery} elem The element to calculate z-index for
- * @return {Number} Calculated z-index
- */
-Tour.prototype.calculateZIndex = function (elem) {
- elem = $(elem);
- while (elem.length && elem[0] !== document) {
- // Ignore z-index if position is set to a value where z-index is ignored by the browser
- // This makes behavior of this function consistent across browsers
- // WebKit always returns auto if the element is positioned.
- var position = elem.css("position");
- if (position === "absolute" || position === "relative" || position === "fixed") {
- // IE returns 0 when zIndex is not specified
- // other browsers return a string
- // we ignore the case of nested elements with an explicit value of 0
- //
- var value = parseInt(elem.css("zIndex"), 10);
- if (!isNaN(value) && value !== 0) {
- return value;
+ let colorNode = targetNode;
+ if (buffer) {
+ colorNode = $('body');
+ }
+
+ background.css({
+ width: targetNode.outerWidth() + buffer + buffer,
+ height: targetNode.outerHeight() + buffer + buffer,
+ left: targetNode.offset().left - buffer,
+ top: targetNode.offset().top - buffer,
+ backgroundColor: this.calculateInherittedBackgroundColor(colorNode),
+ });
+
+ if (targetNode.offset().left < buffer) {
+ background.css({
+ width: targetNode.outerWidth() + targetNode.offset().left + buffer,
+ left: targetNode.offset().left,
+ });
+ }
+
+ if (targetNode.offset().top < buffer) {
+ background.css({
+ height: targetNode.outerHeight() + targetNode.offset().top + buffer,
+ top: targetNode.offset().top,
+ });
+ }
+
+ let targetRadius = targetNode.css('borderRadius');
+ if (targetRadius && targetRadius !== $('body').css('borderRadius')) {
+ background.css('borderRadius', targetRadius);
+ }
+
+ let targetPosition = this.calculatePosition(targetNode);
+ if (targetPosition === 'fixed') {
+ background.css('top', 0);
+ } else if (targetPosition === 'absolute') {
+ background.css('position', 'fixed');
+ }
+
+ let fader = background.clone();
+ fader.css({
+ backgroundColor: backdrop.css('backgroundColor'),
+ opacity: backdrop.css('opacity'),
+ });
+ fader.attr('data-flexitour', 'step-background-fader');
+
+ if (stepConfig.zIndex) {
+ if (stepConfig.attachPoint === 'append') {
+ stepConfig.attachTo.append(background);
+ } else {
+ fader.insertAfter(stepConfig.attachTo);
+ background.insertAfter(stepConfig.attachTo);
+ }
+ } else {
+ $('body').append(fader);
+ $('body').append(background);
+ }
+
+ // Add the backdrop data to the actual target.
+ // This is the part which actually does the work.
+ targetNode.attr('data-flexitour', 'step-backdrop');
+
+ if (stepConfig.zIndex) {
+ backdrop.css('zIndex', stepConfig.zIndex);
+ background.css('zIndex', stepConfig.zIndex + 1);
+ targetNode.css('zIndex', stepConfig.zIndex + 2);
+ }
+
+ fader.fadeOut('2000', function() {
+ $(this).remove();
+ });
}
}
- elem = elem.parent();
+ return this;
}
- return 0;
-};
-
-/**
- * Calculate the inheritted background colour.
- *
- * @method calculateInherittedBackgroundColor
- * @param {jQuery} elem The element to calculate colour for
- * @return {String} Calculated background colour
- */
-Tour.prototype.calculateInherittedBackgroundColor = function (elem) {
- // Use a fake node to compare each element against.
- var fakeNode = $('').hide();
- $('body').append(fakeNode);
- var fakeElemColor = fakeNode.css('backgroundColor');
- fakeNode.remove();
-
- elem = $(elem);
- while (elem.length && elem[0] !== document) {
- var color = elem.css('backgroundColor');
- if (color !== fakeElemColor) {
- return color;
- }
- elem = elem.parent();
- }
-
- return null;
-};
-
-/**
- * Calculate the inheritted position.
- *
- * @method calculatePosition
- * @param {jQuery} elem The element to calculate position for
- * @return {String} Calculated position
- */
-Tour.prototype.calculatePosition = function (elem) {
- elem = $(elem);
- while (elem.length && elem[0] !== document) {
- var position = elem.css('position');
- if (position !== 'static') {
- return position;
- }
- elem = elem.parent();
- }
-
- return null;
-};
-
-/**
- * Perform accessibility changes for step shown.
- *
- * This will add aria-hidden="true" to all siblings and parent siblings.
- *
- * @method accessibilityShow
- */
-Tour.prototype.accessibilityShow = function () {
- var stateHolder = 'data-has-hidden';
- var attrName = 'aria-hidden';
- var hideFunction = function hideFunction(child) {
- var flexitourRole = child.data('flexitour');
- if (flexitourRole) {
- switch (flexitourRole) {
- case 'container':
- case 'target':
- return;
+ /**
+ * Calculate the inheritted z-index.
+ *
+ * @method calculateZIndex
+ * @param {jQuery} elem The element to calculate z-index for
+ * @return {Number} Calculated z-index
+ */
+ calculateZIndex(elem) {
+ elem = $(elem);
+ while (elem.length && elem[0] !== document) {
+ // Ignore z-index if position is set to a value where z-index is ignored by the browser
+ // This makes behavior of this function consistent across browsers
+ // WebKit always returns auto if the element is positioned.
+ let position = elem.css("position");
+ if (position === "absolute" || position === "relative" || position === "fixed") {
+ // IE returns 0 when zIndex is not specified
+ // other browsers return a string
+ // we ignore the case of nested elements with an explicit value of 0
+ //
+ let value = parseInt(elem.css("zIndex"), 10);
+ if (!isNaN(value) && value !== 0) {
+ return value;
+ }
}
+ elem = elem.parent();
}
- var hidden = child.attr(attrName);
- if (!hidden) {
- child.attr(stateHolder, true);
- child.attr(attrName, true);
+ return 0;
+ }
+
+ /**
+ * Calculate the inheritted background colour.
+ *
+ * @method calculateInherittedBackgroundColor
+ * @param {jQuery} elem The element to calculate colour for
+ * @return {String} Calculated background colour
+ */
+ calculateInherittedBackgroundColor(elem) {
+ // Use a fake node to compare each element against.
+ let fakeNode = $('
').hide();
+ $('body').append(fakeNode);
+ let fakeElemColor = fakeNode.css('backgroundColor');
+ fakeNode.remove();
+
+ elem = $(elem);
+ while (elem.length && elem[0] !== document) {
+ let color = elem.css('backgroundColor');
+ if (color !== fakeElemColor) {
+ return color;
+ }
+ elem = elem.parent();
}
- };
- this.currentStepNode.siblings().each(function (index, node) {
- hideFunction($(node));
- });
- this.currentStepNode.parentsUntil('body').siblings().each(function (index, node) {
- hideFunction($(node));
- });
-};
+ return null;
+ }
-/**
- * Perform accessibility changes for step hidden.
- *
- * This will remove any newly added aria-hidden="true".
- *
- * @method accessibilityHide
- */
-Tour.prototype.accessibilityHide = function () {
- var stateHolder = 'data-has-hidden';
- var attrName = 'aria-hidden';
- var showFunction = function showFunction(child) {
- var hidden = child.attr(stateHolder);
- if (typeof hidden !== 'undefined') {
- child.removeAttr(stateHolder);
- child.removeAttr(attrName);
+ /**
+ * Calculate the inheritted position.
+ *
+ * @method calculatePosition
+ * @param {jQuery} elem The element to calculate position for
+ * @return {String} Calculated position
+ */
+ calculatePosition(elem) {
+ elem = $(elem);
+ while (elem.length && elem[0] !== document) {
+ let position = elem.css('position');
+ if (position !== 'static') {
+ return position;
+ }
+ elem = elem.parent();
}
- };
- $('[' + stateHolder + ']').each(function (index, node) {
- showFunction($(node));
- });
-};
+ return null;
+ }
-if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') {
- module.exports = Tour;
+ /**
+ * Perform accessibility changes for step shown.
+ *
+ * This will add aria-hidden="true" to all siblings and parent siblings.
+ *
+ * @method accessibilityShow
+ */
+ accessibilityShow() {
+ let stateHolder = 'data-has-hidden';
+ let attrName = 'aria-hidden';
+ let hideFunction = function(child) {
+ let flexitourRole = child.data('flexitour');
+ if (flexitourRole) {
+ switch (flexitourRole) {
+ case 'container':
+ case 'target':
+ return;
+ }
+ }
+
+ let hidden = child.attr(attrName);
+ if (!hidden) {
+ child.attr(stateHolder, true);
+ child.attr(attrName, true);
+ }
+ };
+
+ this.currentStepNode.siblings().each(function(index, node) {
+ hideFunction($(node));
+ });
+ this.currentStepNode.parentsUntil('body').siblings().each(function(index, node) {
+ hideFunction($(node));
+ });
+ }
+
+ /**
+ * Perform accessibility changes for step hidden.
+ *
+ * This will remove any newly added aria-hidden="true".
+ *
+ * @method accessibilityHide
+ */
+ accessibilityHide() {
+ let stateHolder = 'data-has-hidden';
+ let attrName = 'aria-hidden';
+ let showFunction = function(child) {
+ let hidden = child.attr(stateHolder);
+ if (typeof hidden !== 'undefined') {
+ child.removeAttr(stateHolder);
+ child.removeAttr(attrName);
+ }
+ };
+
+ $('[' + stateHolder + ']').each(function(index, node) {
+ showFunction($(node));
+ });
+ }
}
-
-return Tour;
-
-}));
diff --git a/admin/tool/usertours/thirdpartylibs.xml b/admin/tool/usertours/thirdpartylibs.xml
deleted file mode 100644
index 8f1c4f5b389..00000000000
--- a/admin/tool/usertours/thirdpartylibs.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- amd/src/tour.js
- Flexitour
- GPLv3
- 0.12.3
- 3
-
-