shell bypass 403
/*! shepherd.js 11.2.0 */ var isMergeableObject = function isMergeableObject(value) { return isNonNullObject(value) && !isSpecial(value); }; function isNonNullObject(value) { return !!value && typeof value === 'object'; } function isSpecial(value) { var stringValue = Object.prototype.toString.call(value); return stringValue === '[object RegExp]' || stringValue === '[object Date]' || isReactElement(value); } // see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25 var canUseSymbol = typeof Symbol === 'function' && Symbol.for; var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7; function isReactElement(value) { return value.$$typeof === REACT_ELEMENT_TYPE; } function emptyTarget(val) { return Array.isArray(val) ? [] : {}; } function cloneUnlessOtherwiseSpecified(value, options) { return options.clone !== false && options.isMergeableObject(value) ? deepmerge(emptyTarget(value), value, options) : value; } function defaultArrayMerge(target, source, options) { return target.concat(source).map(function (element) { return cloneUnlessOtherwiseSpecified(element, options); }); } function getMergeFunction(key, options) { if (!options.customMerge) { return deepmerge; } var customMerge = options.customMerge(key); return typeof customMerge === 'function' ? customMerge : deepmerge; } function getEnumerableOwnPropertySymbols(target) { return Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols(target).filter(function (symbol) { return Object.propertyIsEnumerable.call(target, symbol); }) : []; } function getKeys(target) { return Object.keys(target).concat(getEnumerableOwnPropertySymbols(target)); } function propertyIsOnObject(object, property) { try { return property in object; } catch (_) { return false; } } // Protects from prototype poisoning and unexpected merging up the prototype chain. function propertyIsUnsafe(target, key) { return propertyIsOnObject(target, key) // Properties are safe to merge if they don't exist in the target yet, && !(Object.hasOwnProperty.call(target, key) // unsafe if they exist up the prototype chain, && Object.propertyIsEnumerable.call(target, key)); // and also unsafe if they're nonenumerable. } function mergeObject(target, source, options) { var destination = {}; if (options.isMergeableObject(target)) { getKeys(target).forEach(function (key) { destination[key] = cloneUnlessOtherwiseSpecified(target[key], options); }); } getKeys(source).forEach(function (key) { if (propertyIsUnsafe(target, key)) { return; } if (propertyIsOnObject(target, key) && options.isMergeableObject(source[key])) { destination[key] = getMergeFunction(key, options)(target[key], source[key], options); } else { destination[key] = cloneUnlessOtherwiseSpecified(source[key], options); } }); return destination; } function deepmerge(target, source, options) { options = options || {}; options.arrayMerge = options.arrayMerge || defaultArrayMerge; options.isMergeableObject = options.isMergeableObject || isMergeableObject; // cloneUnlessOtherwiseSpecified is added to `options` so that custom arrayMerge() // implementations can use it. The caller may not replace it. options.cloneUnlessOtherwiseSpecified = cloneUnlessOtherwiseSpecified; var sourceIsArray = Array.isArray(source); var targetIsArray = Array.isArray(target); var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray; if (!sourceAndTargetTypesMatch) { return cloneUnlessOtherwiseSpecified(source, options); } else if (sourceIsArray) { return options.arrayMerge(target, source, options); } else { return mergeObject(target, source, options); } } deepmerge.all = function deepmergeAll(array, options) { if (!Array.isArray(array)) { throw new Error('first argument should be an array'); } return array.reduce(function (prev, next) { return deepmerge(prev, next, options); }, {}); }; var deepmerge_1 = deepmerge; var cjs = deepmerge_1; /** * Checks if `value` is classified as an `Element`. * @param {*} value The param to check if it is an Element */ function isElement$1(value) { return value instanceof Element; } /** * Checks if `value` is classified as an `HTMLElement`. * @param {*} value The param to check if it is an HTMLElement */ function isHTMLElement$1(value) { return value instanceof HTMLElement; } /** * Checks if `value` is classified as a `Function` object. * @param {*} value The param to check if it is a function */ function isFunction(value) { return typeof value === 'function'; } /** * Checks if `value` is classified as a `String` object. * @param {*} value The param to check if it is a string */ function isString(value) { return typeof value === 'string'; } /** * Checks if `value` is undefined. * @param {*} value The param to check if it is undefined */ function isUndefined(value) { return value === undefined; } class Evented { on(event, handler, ctx, once = false) { if (isUndefined(this.bindings)) { this.bindings = {}; } if (isUndefined(this.bindings[event])) { this.bindings[event] = []; } this.bindings[event].push({ handler, ctx, once }); return this; } once(event, handler, ctx) { return this.on(event, handler, ctx, true); } off(event, handler) { if (isUndefined(this.bindings) || isUndefined(this.bindings[event])) { return this; } if (isUndefined(handler)) { delete this.bindings[event]; } else { this.bindings[event].forEach((binding, index) => { if (binding.handler === handler) { this.bindings[event].splice(index, 1); } }); } return this; } trigger(event, ...args) { if (!isUndefined(this.bindings) && this.bindings[event]) { this.bindings[event].forEach((binding, index) => { const { ctx, handler, once } = binding; const context = ctx || this; handler.apply(context, args); if (once) { this.bindings[event].splice(index, 1); } }); } return this; } } /** * Binds all the methods on a JS Class to the `this` context of the class. * Adapted from https://github.com/sindresorhus/auto-bind * @param {object} self The `this` context of the class * @return {object} The `this` context of the class */ function autoBind(self) { const keys = Object.getOwnPropertyNames(self.constructor.prototype); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const val = self[key]; if (key !== 'constructor' && typeof val === 'function') { self[key] = val.bind(self); } } return self; } /** * Sets up the handler to determine if we should advance the tour * @param {string} selector * @param {Step} step The step instance * @return {Function} * @private */ function _setupAdvanceOnHandler(selector, step) { return event => { if (step.isOpen()) { const targetIsEl = step.el && event.currentTarget === step.el; const targetIsSelector = !isUndefined(selector) && event.currentTarget.matches(selector); if (targetIsSelector || targetIsEl) { step.tour.next(); } } }; } /** * Bind the event handler for advanceOn * @param {Step} step The step instance */ function bindAdvance(step) { // An empty selector matches the step element const { event, selector } = step.options.advanceOn || {}; if (event) { const handler = _setupAdvanceOnHandler(selector, step); // TODO: this should also bind/unbind on show/hide let el; try { el = document.querySelector(selector); } catch (e) { // TODO } if (!isUndefined(selector) && !el) { return console.error(`No element was found for the selector supplied to advanceOn: ${selector}`); } else if (el) { el.addEventListener(event, handler); step.on('destroy', () => { return el.removeEventListener(event, handler); }); } else { document.body.addEventListener(event, handler, true); step.on('destroy', () => { return document.body.removeEventListener(event, handler, true); }); } } else { return console.error('advanceOn was defined, but no event name was passed.'); } } /** * Ensure class prefix ends in `-` * @param {string} prefix The prefix to prepend to the class names generated by nano-css * @return {string} The prefix ending in `-` */ function normalizePrefix(prefix) { if (!isString(prefix) || prefix === '') { return ''; } return prefix.charAt(prefix.length - 1) !== '-' ? `${prefix}-` : prefix; } /** * Resolves attachTo options, converting element option value to a qualified HTMLElement. * @param {Step} step The step instance * @returns {{}|{element, on}} * `element` is a qualified HTML Element * `on` is a string position value */ function parseAttachTo(step) { const options = step.options.attachTo || {}; const returnOpts = Object.assign({}, options); if (isFunction(returnOpts.element)) { // Bind the callback to step so that it has access to the object, to enable running additional logic returnOpts.element = returnOpts.element.call(step); } if (isString(returnOpts.element)) { // Can't override the element in user opts reference because we can't // guarantee that the element will exist in the future. try { returnOpts.element = document.querySelector(returnOpts.element); } catch (e) { // TODO } if (!returnOpts.element) { console.error(`The element for this Shepherd step was not found ${options.element}`); } } return returnOpts; } /** * Checks if the step should be centered or not. Does not trigger attachTo.element evaluation, making it a pure * alternative for the deprecated step.isCentered() method. * @param resolvedAttachToOptions * @returns {boolean} */ function shouldCenterStep(resolvedAttachToOptions) { if (resolvedAttachToOptions === undefined || resolvedAttachToOptions === null) { return true; } return !resolvedAttachToOptions.element || !resolvedAttachToOptions.on; } /** * Create a unique id for steps, tours, modals, etc * @return {string} */ function uuid() { let d = Date.now(); return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { const r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c == 'x' ? r : r & 0x3 | 0x8).toString(16); }); } function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } const min = Math.min; const max = Math.max; const round = Math.round; const floor = Math.floor; const createCoords = v => ({ x: v, y: v }); const oppositeSideMap = { left: 'right', right: 'left', bottom: 'top', top: 'bottom' }; const oppositeAlignmentMap = { start: 'end', end: 'start' }; function clamp(start, value, end) { return max(start, min(value, end)); } function evaluate(value, param) { return typeof value === 'function' ? value(param) : value; } function getSide(placement) { return placement.split('-')[0]; } function getAlignment(placement) { return placement.split('-')[1]; } function getOppositeAxis(axis) { return axis === 'x' ? 'y' : 'x'; } function getAxisLength(axis) { return axis === 'y' ? 'height' : 'width'; } function getSideAxis(placement) { return ['top', 'bottom'].includes(getSide(placement)) ? 'y' : 'x'; } function getAlignmentAxis(placement) { return getOppositeAxis(getSideAxis(placement)); } function getAlignmentSides(placement, rects, rtl) { if (rtl === void 0) { rtl = false; } const alignment = getAlignment(placement); const alignmentAxis = getAlignmentAxis(placement); const length = getAxisLength(alignmentAxis); let mainAlignmentSide = alignmentAxis === 'x' ? alignment === (rtl ? 'end' : 'start') ? 'right' : 'left' : alignment === 'start' ? 'bottom' : 'top'; if (rects.reference[length] > rects.floating[length]) { mainAlignmentSide = getOppositePlacement(mainAlignmentSide); } return [mainAlignmentSide, getOppositePlacement(mainAlignmentSide)]; } function getExpandedPlacements(placement) { const oppositePlacement = getOppositePlacement(placement); return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)]; } function getOppositeAlignmentPlacement(placement) { return placement.replace(/start|end/g, alignment => oppositeAlignmentMap[alignment]); } function getSideList(side, isStart, rtl) { const lr = ['left', 'right']; const rl = ['right', 'left']; const tb = ['top', 'bottom']; const bt = ['bottom', 'top']; switch (side) { case 'top': case 'bottom': if (rtl) return isStart ? rl : lr; return isStart ? lr : rl; case 'left': case 'right': return isStart ? tb : bt; default: return []; } } function getOppositeAxisPlacements(placement, flipAlignment, direction, rtl) { const alignment = getAlignment(placement); let list = getSideList(getSide(placement), direction === 'start', rtl); if (alignment) { list = list.map(side => side + "-" + alignment); if (flipAlignment) { list = list.concat(list.map(getOppositeAlignmentPlacement)); } } return list; } function getOppositePlacement(placement) { return placement.replace(/left|right|bottom|top/g, side => oppositeSideMap[side]); } function expandPaddingObject(padding) { return _extends({ top: 0, right: 0, bottom: 0, left: 0 }, padding); } function getPaddingObject(padding) { return typeof padding !== 'number' ? expandPaddingObject(padding) : { top: padding, right: padding, bottom: padding, left: padding }; } function rectToClientRect(rect) { return _extends({}, rect, { top: rect.y, left: rect.x, right: rect.x + rect.width, bottom: rect.y + rect.height }); } const _excluded2 = ["mainAxis", "crossAxis", "fallbackPlacements", "fallbackStrategy", "fallbackAxisSideDirection", "flipAlignment"], _excluded4 = ["mainAxis", "crossAxis", "limiter"]; function computeCoordsFromPlacement(_ref, placement, rtl) { let { reference, floating } = _ref; const sideAxis = getSideAxis(placement); const alignmentAxis = getAlignmentAxis(placement); const alignLength = getAxisLength(alignmentAxis); const side = getSide(placement); const isVertical = sideAxis === 'y'; const commonX = reference.x + reference.width / 2 - floating.width / 2; const commonY = reference.y + reference.height / 2 - floating.height / 2; const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2; let coords; switch (side) { case 'top': coords = { x: commonX, y: reference.y - floating.height }; break; case 'bottom': coords = { x: commonX, y: reference.y + reference.height }; break; case 'right': coords = { x: reference.x + reference.width, y: commonY }; break; case 'left': coords = { x: reference.x - floating.width, y: commonY }; break; default: coords = { x: reference.x, y: reference.y }; } switch (getAlignment(placement)) { case 'start': coords[alignmentAxis] -= commonAlign * (rtl && isVertical ? -1 : 1); break; case 'end': coords[alignmentAxis] += commonAlign * (rtl && isVertical ? -1 : 1); break; } return coords; } /** * Computes the `x` and `y` coordinates that will place the floating element * next to a reference element when it is given a certain positioning strategy. * * This export does not have any `platform` interface logic. You will need to * write one for the platform you are using Floating UI with. */ const computePosition$1 = async (reference, floating, config) => { const { placement = 'bottom', strategy = 'absolute', middleware = [], platform } = config; const validMiddleware = middleware.filter(Boolean); const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(floating)); let rects = await platform.getElementRects({ reference, floating, strategy }); let { x, y } = computeCoordsFromPlacement(rects, placement, rtl); let statefulPlacement = placement; let middlewareData = {}; let resetCount = 0; for (let i = 0; i < validMiddleware.length; i++) { const { name, fn } = validMiddleware[i]; const { x: nextX, y: nextY, data, reset } = await fn({ x, y, initialPlacement: placement, placement: statefulPlacement, strategy, middlewareData, rects, platform, elements: { reference, floating } }); x = nextX != null ? nextX : x; y = nextY != null ? nextY : y; middlewareData = _extends({}, middlewareData, { [name]: _extends({}, middlewareData[name], data) }); if (reset && resetCount <= 50) { resetCount++; if (typeof reset === 'object') { if (reset.placement) { statefulPlacement = reset.placement; } if (reset.rects) { rects = reset.rects === true ? await platform.getElementRects({ reference, floating, strategy }) : reset.rects; } ({ x, y } = computeCoordsFromPlacement(rects, statefulPlacement, rtl)); } i = -1; continue; } } return { x, y, placement: statefulPlacement, strategy, middlewareData }; }; /** * Resolves with an object of overflow side offsets that determine how much the * element is overflowing a given clipping boundary on each side. * - positive = overflowing the boundary by that number of pixels * - negative = how many pixels left before it will overflow * - 0 = lies flush with the boundary * @see https://floating-ui.com/docs/detectOverflow */ async function detectOverflow(state, options) { var _await$platform$isEle; if (options === void 0) { options = {}; } const { x, y, platform, rects, elements, strategy } = state; const { boundary = 'clippingAncestors', rootBoundary = 'viewport', elementContext = 'floating', altBoundary = false, padding = 0 } = evaluate(options, state); const paddingObject = getPaddingObject(padding); const altContext = elementContext === 'floating' ? 'reference' : 'floating'; const element = elements[altBoundary ? altContext : elementContext]; const clippingClientRect = rectToClientRect(await platform.getClippingRect({ element: ((_await$platform$isEle = await (platform.isElement == null ? void 0 : platform.isElement(element))) != null ? _await$platform$isEle : true) ? element : element.contextElement || (await (platform.getDocumentElement == null ? void 0 : platform.getDocumentElement(elements.floating))), boundary, rootBoundary, strategy })); const rect = elementContext === 'floating' ? _extends({}, rects.floating, { x, y }) : rects.reference; const offsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(elements.floating)); const offsetScale = (await (platform.isElement == null ? void 0 : platform.isElement(offsetParent))) ? (await (platform.getScale == null ? void 0 : platform.getScale(offsetParent))) || { x: 1, y: 1 } : { x: 1, y: 1 }; const elementClientRect = rectToClientRect(platform.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform.convertOffsetParentRelativeRectToViewportRelativeRect({ rect, offsetParent, strategy }) : rect); return { top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y, bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y, left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x, right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x }; } /** * Provides data to position an inner element of the floating element so that it * appears centered to the reference element. * @see https://floating-ui.com/docs/arrow */ const arrow = options => ({ name: 'arrow', options, async fn(state) { const { x, y, placement, rects, platform, elements } = state; // Since `element` is required, we don't Partial<> the type. const { element, padding = 0 } = evaluate(options, state) || {}; if (element == null) { return {}; } const paddingObject = getPaddingObject(padding); const coords = { x, y }; const axis = getAlignmentAxis(placement); const length = getAxisLength(axis); const arrowDimensions = await platform.getDimensions(element); const isYAxis = axis === 'y'; const minProp = isYAxis ? 'top' : 'left'; const maxProp = isYAxis ? 'bottom' : 'right'; const clientProp = isYAxis ? 'clientHeight' : 'clientWidth'; const endDiff = rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length]; const startDiff = coords[axis] - rects.reference[axis]; const arrowOffsetParent = await (platform.getOffsetParent == null ? void 0 : platform.getOffsetParent(element)); let clientSize = arrowOffsetParent ? arrowOffsetParent[clientProp] : 0; // DOM platform can return `window` as the `offsetParent`. if (!clientSize || !(await (platform.isElement == null ? void 0 : platform.isElement(arrowOffsetParent)))) { clientSize = elements.floating[clientProp] || rects.floating[length]; } const centerToReference = endDiff / 2 - startDiff / 2; // If the padding is large enough that it causes the arrow to no longer be // centered, modify the padding so that it is centered. const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1; const minPadding = min(paddingObject[minProp], largestPossiblePadding); const maxPadding = min(paddingObject[maxProp], largestPossiblePadding); // Make sure the arrow doesn't overflow the floating element if the center // point is outside the floating element's bounds. const min$1 = minPadding; const max = clientSize - arrowDimensions[length] - maxPadding; const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference; const offset = clamp(min$1, center, max); // If the reference is small enough that the arrow's padding causes it to // to point to nothing for an aligned placement, adjust the offset of the // floating element itself. This stops `shift()` from taking action, but can // be worked around by calling it again after the `arrow()` if desired. const shouldAddOffset = getAlignment(placement) != null && center != offset && rects.reference[length] / 2 - (center < min$1 ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0; const alignmentOffset = shouldAddOffset ? center < min$1 ? min$1 - center : max - center : 0; return { [axis]: coords[axis] - alignmentOffset, data: { [axis]: offset, centerOffset: center - offset + alignmentOffset } }; } }); /** * Optimizes the visibility of the floating element by flipping the `placement` * in order to keep it in view when the preferred placement(s) will overflow the * clipping boundary. Alternative to `autoPlacement`. * @see https://floating-ui.com/docs/flip */ const flip = function flip(options) { if (options === void 0) { options = {}; } return { name: 'flip', options, async fn(state) { var _middlewareData$flip; const { placement, middlewareData, rects, initialPlacement, platform, elements } = state; const _evaluate2 = evaluate(options, state), { mainAxis: checkMainAxis = true, crossAxis: checkCrossAxis = true, fallbackPlacements: specifiedFallbackPlacements, fallbackStrategy = 'bestFit', fallbackAxisSideDirection = 'none', flipAlignment = true } = _evaluate2, detectOverflowOptions = _objectWithoutPropertiesLoose(_evaluate2, _excluded2); const side = getSide(placement); const isBasePlacement = getSide(initialPlacement) === initialPlacement; const rtl = await (platform.isRTL == null ? void 0 : platform.isRTL(elements.floating)); const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement)); if (!specifiedFallbackPlacements && fallbackAxisSideDirection !== 'none') { fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl)); } const placements = [initialPlacement, ...fallbackPlacements]; const overflow = await detectOverflow(state, detectOverflowOptions); const overflows = []; let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || []; if (checkMainAxis) { overflows.push(overflow[side]); } if (checkCrossAxis) { const sides = getAlignmentSides(placement, rects, rtl); overflows.push(overflow[sides[0]], overflow[sides[1]]); } overflowsData = [...overflowsData, { placement, overflows }]; // One or more sides is overflowing. if (!overflows.every(side => side <= 0)) { var _middlewareData$flip2, _overflowsData$filter; const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1; const nextPlacement = placements[nextIndex]; if (nextPlacement) { // Try next placement and re-run the lifecycle. return { data: { index: nextIndex, overflows: overflowsData }, reset: { placement: nextPlacement } }; } // First, find the candidates that fit on the mainAxis side of overflow, // then find the placement that fits the best on the main crossAxis side. let resetPlacement = (_overflowsData$filter = overflowsData.filter(d => d.overflows[0] <= 0).sort((a, b) => a.overflows[1] - b.overflows[1])[0]) == null ? void 0 : _overflowsData$filter.placement; // Otherwise fallback. if (!resetPlacement) { switch (fallbackStrategy) { case 'bestFit': { var _overflowsData$map$so; const placement = (_overflowsData$map$so = overflowsData.map(d => [d.placement, d.overflows.filter(overflow => overflow > 0).reduce((acc, overflow) => acc + overflow, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$map$so[0]; if (placement) { resetPlacement = placement; } break; } case 'initialPlacement': resetPlacement = initialPlacement; break; } } if (placement !== resetPlacement) { return { reset: { placement: resetPlacement } }; } } return {}; } }; }; /** * Optimizes the visibility of the floating element by shifting it in order to * keep it in view when it will overflow the clipping boundary. * @see https://floating-ui.com/docs/shift */ const shift = function shift(options) { if (options === void 0) { options = {}; } return { name: 'shift', options, async fn(state) { const { x, y, placement } = state; const _evaluate4 = evaluate(options, state), { mainAxis: checkMainAxis = true, crossAxis: checkCrossAxis = false, limiter = { fn: _ref => { let { x, y } = _ref; return { x, y }; } } } = _evaluate4, detectOverflowOptions = _objectWithoutPropertiesLoose(_evaluate4, _excluded4); const coords = { x, y }; const overflow = await detectOverflow(state, detectOverflowOptions); const crossAxis = getSideAxis(getSide(placement)); const mainAxis = getOppositeAxis(crossAxis); let mainAxisCoord = coords[mainAxis]; let crossAxisCoord = coords[crossAxis]; if (checkMainAxis) { const minSide = mainAxis === 'y' ? 'top' : 'left'; const maxSide = mainAxis === 'y' ? 'bottom' : 'right'; const min = mainAxisCoord + overflow[minSide]; const max = mainAxisCoord - overflow[maxSide]; mainAxisCoord = clamp(min, mainAxisCoord, max); } if (checkCrossAxis) { const minSide = crossAxis === 'y' ? 'top' : 'left'; const maxSide = crossAxis === 'y' ? 'bottom' : 'right'; const min = crossAxisCoord + overflow[minSide]; const max = crossAxisCoord - overflow[maxSide]; crossAxisCoord = clamp(min, crossAxisCoord, max); } const limitedCoords = limiter.fn(_extends({}, state, { [mainAxis]: mainAxisCoord, [crossAxis]: crossAxisCoord })); return _extends({}, limitedCoords, { data: { x: limitedCoords.x - x, y: limitedCoords.y - y } }); } }; }; /** * Built-in `limiter` that will stop `shift()` at a certain point. */ const limitShift = function limitShift(options) { if (options === void 0) { options = {}; } return { options, fn(state) { const { x, y, placement, rects, middlewareData } = state; const { offset = 0, mainAxis: checkMainAxis = true, crossAxis: checkCrossAxis = true } = evaluate(options, state); const coords = { x, y }; const crossAxis = getSideAxis(placement); const mainAxis = getOppositeAxis(crossAxis); let mainAxisCoord = coords[mainAxis]; let crossAxisCoord = coords[crossAxis]; const rawOffset = evaluate(offset, state); const computedOffset = typeof rawOffset === 'number' ? { mainAxis: rawOffset, crossAxis: 0 } : _extends({ mainAxis: 0, crossAxis: 0 }, rawOffset); if (checkMainAxis) { const len = mainAxis === 'y' ? 'height' : 'width'; const limitMin = rects.reference[mainAxis] - rects.floating[len] + computedOffset.mainAxis; const limitMax = rects.reference[mainAxis] + rects.reference[len] - computedOffset.mainAxis; if (mainAxisCoord < limitMin) { mainAxisCoord = limitMin; } else if (mainAxisCoord > limitMax) { mainAxisCoord = limitMax; } } if (checkCrossAxis) { var _middlewareData$offse, _middlewareData$offse2; const len = mainAxis === 'y' ? 'width' : 'height'; const isOriginSide = ['top', 'left'].includes(getSide(placement)); const limitMin = rects.reference[crossAxis] - rects.floating[len] + (isOriginSide ? ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse[crossAxis]) || 0 : 0) + (isOriginSide ? 0 : computedOffset.crossAxis); const limitMax = rects.reference[crossAxis] + rects.reference[len] + (isOriginSide ? 0 : ((_middlewareData$offse2 = middlewareData.offset) == null ? void 0 : _middlewareData$offse2[crossAxis]) || 0) - (isOriginSide ? computedOffset.crossAxis : 0); if (crossAxisCoord < limitMin) { crossAxisCoord = limitMin; } else if (crossAxisCoord > limitMax) { crossAxisCoord = limitMax; } } return { [mainAxis]: mainAxisCoord, [crossAxis]: crossAxisCoord }; } }; }; function getNodeName(node) { if (isNode(node)) { return (node.nodeName || '').toLowerCase(); } // Mocked nodes in testing environments may not be instances of Node. By // returning `#document` an infinite loop won't occur. // https://github.com/floating-ui/floating-ui/issues/2317 return '#document'; } function getWindow(node) { var _node$ownerDocument; return (node == null ? void 0 : (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window; } function getDocumentElement(node) { var _ref; return (_ref = (isNode(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement; } function isNode(value) { return value instanceof Node || value instanceof getWindow(value).Node; } function isElement(value) { return value instanceof Element || value instanceof getWindow(value).Element; } function isHTMLElement(value) { return value instanceof HTMLElement || value instanceof getWindow(value).HTMLElement; } function isShadowRoot(value) { // Browsers without `ShadowRoot` support. if (typeof ShadowRoot === 'undefined') { return false; } return value instanceof ShadowRoot || value instanceof getWindow(value).ShadowRoot; } function isOverflowElement(element) { const { overflow, overflowX, overflowY, display } = getComputedStyle(element); return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !['inline', 'contents'].includes(display); } function isTableElement(element) { return ['table', 'td', 'th'].includes(getNodeName(element)); } function isContainingBlock(element) { const webkit = isWebKit(); const css = getComputedStyle(element); // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block return css.transform !== 'none' || css.perspective !== 'none' || (css.containerType ? css.containerType !== 'normal' : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== 'none' : false) || !webkit && (css.filter ? css.filter !== 'none' : false) || ['transform', 'perspective', 'filter'].some(value => (css.willChange || '').includes(value)) || ['paint', 'layout', 'strict', 'content'].some(value => (css.contain || '').includes(value)); } function getContainingBlock(element) { let currentNode = getParentNode(element); while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) { if (isContainingBlock(currentNode)) { return currentNode; } else { currentNode = getParentNode(currentNode); } } return null; } function isWebKit() { if (typeof CSS === 'undefined' || !CSS.supports) return false; return CSS.supports('-webkit-backdrop-filter', 'none'); } function isLastTraversableNode(node) { return ['html', 'body', '#document'].includes(getNodeName(node)); } function getComputedStyle(element) { return getWindow(element).getComputedStyle(element); } function getNodeScroll(element) { if (isElement(element)) { return { scrollLeft: element.scrollLeft, scrollTop: element.scrollTop }; } return { scrollLeft: element.pageXOffset, scrollTop: element.pageYOffset }; } function getParentNode(node) { if (getNodeName(node) === 'html') { return node; } const result = // Step into the shadow DOM of the parent of a slotted node. node.assignedSlot || // DOM Element detected. node.parentNode || // ShadowRoot detected. isShadowRoot(node) && node.host || // Fallback. getDocumentElement(node); return isShadowRoot(result) ? result.host : result; } function getNearestOverflowAncestor(node) { const parentNode = getParentNode(node); if (isLastTraversableNode(parentNode)) { return node.ownerDocument ? node.ownerDocument.body : node.body; } if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) { return parentNode; } return getNearestOverflowAncestor(parentNode); } function getOverflowAncestors(node, list) { var _node$ownerDocument2; if (list === void 0) { list = []; } const scrollableAncestor = getNearestOverflowAncestor(node); const isBody = scrollableAncestor === ((_node$ownerDocument2 = node.ownerDocument) == null ? void 0 : _node$ownerDocument2.body); const win = getWindow(scrollableAncestor); if (isBody) { return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : []); } return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor)); } function getCssDimensions(element) { const css = getComputedStyle(element); // In testing environments, the `width` and `height` properties are empty // strings for SVG elements, returning NaN. Fallback to `0` in this case. let width = parseFloat(css.width) || 0; let height = parseFloat(css.height) || 0; const hasOffset = isHTMLElement(element); const offsetWidth = hasOffset ? element.offsetWidth : width; const offsetHeight = hasOffset ? element.offsetHeight : height; const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight; if (shouldFallback) { width = offsetWidth; height = offsetHeight; } return { width, height, $: shouldFallback }; } function unwrapElement(element) { return !isElement(element) ? element.contextElement : element; } function getScale(element) { const domElement = unwrapElement(element); if (!isHTMLElement(domElement)) { return createCoords(1); } const rect = domElement.getBoundingClientRect(); const { width, height, $ } = getCssDimensions(domElement); let x = ($ ? round(rect.width) : rect.width) / width; let y = ($ ? round(rect.height) : rect.height) / height; // 0, NaN, or Infinity should always fallback to 1. if (!x || !Number.isFinite(x)) { x = 1; } if (!y || !Number.isFinite(y)) { y = 1; } return { x, y }; } const noOffsets = /*#__PURE__*/createCoords(0); function getVisualOffsets(element) { const win = getWindow(element); if (!isWebKit() || !win.visualViewport) { return noOffsets; } return { x: win.visualViewport.offsetLeft, y: win.visualViewport.offsetTop }; } function shouldAddVisualOffsets(element, isFixed, floatingOffsetParent) { if (isFixed === void 0) { isFixed = false; } if (!floatingOffsetParent || isFixed && floatingOffsetParent !== getWindow(element)) { return false; } return isFixed; } function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) { if (includeScale === void 0) { includeScale = false; } if (isFixedStrategy === void 0) { isFixedStrategy = false; } const clientRect = element.getBoundingClientRect(); const domElement = unwrapElement(element); let scale = createCoords(1); if (includeScale) { if (offsetParent) { if (isElement(offsetParent)) { scale = getScale(offsetParent); } } else { scale = getScale(element); } } const visualOffsets = shouldAddVisualOffsets(domElement, isFixedStrategy, offsetParent) ? getVisualOffsets(domElement) : createCoords(0); let x = (clientRect.left + visualOffsets.x) / scale.x; let y = (clientRect.top + visualOffsets.y) / scale.y; let width = clientRect.width / scale.x; let height = clientRect.height / scale.y; if (domElement) { const win = getWindow(domElement); const offsetWin = offsetParent && isElement(offsetParent) ? getWindow(offsetParent) : offsetParent; let currentIFrame = win.frameElement; while (currentIFrame && offsetParent && offsetWin !== win) { const iframeScale = getScale(currentIFrame); const iframeRect = currentIFrame.getBoundingClientRect(); const css = getComputedStyle(currentIFrame); const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; x *= iframeScale.x; y *= iframeScale.y; width *= iframeScale.x; height *= iframeScale.y; x += left; y += top; currentIFrame = getWindow(currentIFrame).frameElement; } } return rectToClientRect({ width, height, x, y }); } function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) { let { rect, offsetParent, strategy } = _ref; const isOffsetParentAnElement = isHTMLElement(offsetParent); const documentElement = getDocumentElement(offsetParent); if (offsetParent === documentElement) { return rect; } let scroll = { scrollLeft: 0, scrollTop: 0 }; let scale = createCoords(1); const offsets = createCoords(0); if (isOffsetParentAnElement || !isOffsetParentAnElement && strategy !== 'fixed') { if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) { scroll = getNodeScroll(offsetParent); } if (isHTMLElement(offsetParent)) { const offsetRect = getBoundingClientRect(offsetParent); scale = getScale(offsetParent); offsets.x = offsetRect.x + offsetParent.clientLeft; offsets.y = offsetRect.y + offsetParent.clientTop; } } return { width: rect.width * scale.x, height: rect.height * scale.y, x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x, y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y }; } function getClientRects(element) { return Array.from(element.getClientRects()); } function getWindowScrollBarX(element) { // If <html> has a CSS width greater than the viewport, then this will be // incorrect for RTL. return getBoundingClientRect(getDocumentElement(element)).left + getNodeScroll(element).scrollLeft; } // Gets the entire size of the scrollable document area, even extending outside // of the `<html>` and `<body>` rect bounds if horizontally scrollable. function getDocumentRect(element) { const html = getDocumentElement(element); const scroll = getNodeScroll(element); const body = element.ownerDocument.body; const width = max(html.scrollWidth, html.clientWidth, body.scrollWidth, body.clientWidth); const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight); let x = -scroll.scrollLeft + getWindowScrollBarX(element); const y = -scroll.scrollTop; if (getComputedStyle(body).direction === 'rtl') { x += max(html.clientWidth, body.clientWidth) - width; } return { width, height, x, y }; } function getViewportRect(element, strategy) { const win = getWindow(element); const html = getDocumentElement(element); const visualViewport = win.visualViewport; let width = html.clientWidth; let height = html.clientHeight; let x = 0; let y = 0; if (visualViewport) { width = visualViewport.width; height = visualViewport.height; const visualViewportBased = isWebKit(); if (!visualViewportBased || visualViewportBased && strategy === 'fixed') { x = visualViewport.offsetLeft; y = visualViewport.offsetTop; } } return { width, height, x, y }; } // Returns the inner client rect, subtracting scrollbars if present. function getInnerBoundingClientRect(element, strategy) { const clientRect = getBoundingClientRect(element, true, strategy === 'fixed'); const top = clientRect.top + element.clientTop; const left = clientRect.left + element.clientLeft; const scale = isHTMLElement(element) ? getScale(element) : createCoords(1); const width = element.clientWidth * scale.x; const height = element.clientHeight * scale.y; const x = left * scale.x; const y = top * scale.y; return { width, height, x, y }; } function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) { let rect; if (clippingAncestor === 'viewport') { rect = getViewportRect(element, strategy); } else if (clippingAncestor === 'document') { rect = getDocumentRect(getDocumentElement(element)); } else if (isElement(clippingAncestor)) { rect = getInnerBoundingClientRect(clippingAncestor, strategy); } else { const visualOffsets = getVisualOffsets(element); rect = _extends({}, clippingAncestor, { x: clippingAncestor.x - visualOffsets.x, y: clippingAncestor.y - visualOffsets.y }); } return rectToClientRect(rect); } function hasFixedPositionAncestor(element, stopNode) { const parentNode = getParentNode(element); if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) { return false; } return getComputedStyle(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); } // A "clipping ancestor" is an `overflow` element with the characteristic of // clipping (or hiding) child elements. This returns all clipping ancestors // of the given element up the tree. function getClippingElementAncestors(element, cache) { const cachedResult = cache.get(element); if (cachedResult) { return cachedResult; } let result = getOverflowAncestors(element).filter(el => isElement(el) && getNodeName(el) !== 'body'); let currentContainingBlockComputedStyle = null; const elementIsFixed = getComputedStyle(element).position === 'fixed'; let currentNode = elementIsFixed ? getParentNode(element) : element; // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block while (isElement(currentNode) && !isLastTraversableNode(currentNode)) { const computedStyle = getComputedStyle(currentNode); const currentNodeIsContaining = isContainingBlock(currentNode); if (!currentNodeIsContaining && computedStyle.position === 'fixed') { currentContainingBlockComputedStyle = null; } const shouldDropCurrentNode = elementIsFixed ? !currentNodeIsContaining && !currentContainingBlockComputedStyle : !currentNodeIsContaining && computedStyle.position === 'static' && !!currentContainingBlockComputedStyle && ['absolute', 'fixed'].includes(currentContainingBlockComputedStyle.position) || isOverflowElement(currentNode) && !currentNodeIsContaining && hasFixedPositionAncestor(element, currentNode); if (shouldDropCurrentNode) { // Drop non-containing blocks. result = result.filter(ancestor => ancestor !== currentNode); } else { // Record last containing block for next iteration. currentContainingBlockComputedStyle = computedStyle; } currentNode = getParentNode(currentNode); } cache.set(element, result); return result; } // Gets the maximum area that the element is visible in due to any number of // clipping ancestors. function getClippingRect(_ref) { let { element, boundary, rootBoundary, strategy } = _ref; const elementClippingAncestors = boundary === 'clippingAncestors' ? getClippingElementAncestors(element, this._c) : [].concat(boundary); const clippingAncestors = [...elementClippingAncestors, rootBoundary]; const firstClippingAncestor = clippingAncestors[0]; const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => { const rect = getClientRectFromClippingAncestor(element, clippingAncestor, strategy); accRect.top = max(rect.top, accRect.top); accRect.right = min(rect.right, accRect.right); accRect.bottom = min(rect.bottom, accRect.bottom); accRect.left = max(rect.left, accRect.left); return accRect; }, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy)); return { width: clippingRect.right - clippingRect.left, height: clippingRect.bottom - clippingRect.top, x: clippingRect.left, y: clippingRect.top }; } function getDimensions(element) { return getCssDimensions(element); } function getRectRelativeToOffsetParent(element, offsetParent, strategy) { const isOffsetParentAnElement = isHTMLElement(offsetParent); const documentElement = getDocumentElement(offsetParent); const isFixed = strategy === 'fixed'; const rect = getBoundingClientRect(element, true, isFixed, offsetParent); let scroll = { scrollLeft: 0, scrollTop: 0 }; const offsets = createCoords(0); if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) { if (getNodeName(offsetParent) !== 'body' || isOverflowElement(documentElement)) { scroll = getNodeScroll(offsetParent); } if (isOffsetParentAnElement) { const offsetRect = getBoundingClientRect(offsetParent, true, isFixed, offsetParent); offsets.x = offsetRect.x + offsetParent.clientLeft; offsets.y = offsetRect.y + offsetParent.clientTop; } else if (documentElement) { offsets.x = getWindowScrollBarX(documentElement); } } return { x: rect.left + scroll.scrollLeft - offsets.x, y: rect.top + scroll.scrollTop - offsets.y, width: rect.width, height: rect.height }; } function getTrueOffsetParent(element, polyfill) { if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') { return null; } if (polyfill) { return polyfill(element); } return element.offsetParent; } // Gets the closest ancestor positioned element. Handles some edge cases, // such as table ancestors and cross browser bugs. function getOffsetParent(element, polyfill) { const window = getWindow(element); if (!isHTMLElement(element)) { return window; } let offsetParent = getTrueOffsetParent(element, polyfill); while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') { offsetParent = getTrueOffsetParent(offsetParent, polyfill); } if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static' && !isContainingBlock(offsetParent))) { return window; } return offsetParent || getContainingBlock(element) || window; } const getElementRects = async function getElementRects(_ref) { let { reference, floating, strategy } = _ref; const getOffsetParentFn = this.getOffsetParent || getOffsetParent; const getDimensionsFn = this.getDimensions; return { reference: getRectRelativeToOffsetParent(reference, await getOffsetParentFn(floating), strategy), floating: _extends({ x: 0, y: 0 }, await getDimensionsFn(floating)) }; }; function isRTL(element) { return getComputedStyle(element).direction === 'rtl'; } const platform = { convertOffsetParentRelativeRectToViewportRelativeRect, getDocumentElement, getClippingRect, getOffsetParent, getElementRects, getClientRects, getDimensions, getScale, isElement, isRTL }; // https://samthor.au/2021/observing-dom/ function observeMove(element, onMove) { let io = null; let timeoutId; const root = getDocumentElement(element); function cleanup() { clearTimeout(timeoutId); io && io.disconnect(); io = null; } function refresh(skip, threshold) { if (skip === void 0) { skip = false; } if (threshold === void 0) { threshold = 1; } cleanup(); const { left, top, width, height } = element.getBoundingClientRect(); if (!skip) { onMove(); } if (!width || !height) { return; } const insetTop = floor(top); const insetRight = floor(root.clientWidth - (left + width)); const insetBottom = floor(root.clientHeight - (top + height)); const insetLeft = floor(left); const rootMargin = -insetTop + "px " + -insetRight + "px " + -insetBottom + "px " + -insetLeft + "px"; const options = { rootMargin, threshold: max(0, min(1, threshold)) || 1 }; let isFirstUpdate = true; function handleObserve(entries) { const ratio = entries[0].intersectionRatio; if (ratio !== threshold) { if (!isFirstUpdate) { return refresh(); } if (!ratio) { timeoutId = setTimeout(() => { refresh(false, 1e-7); }, 100); } else { refresh(false, ratio); } } isFirstUpdate = false; } // Older browsers don't support a `document` as the root and will throw an // error. try { io = new IntersectionObserver(handleObserve, _extends({}, options, { // Handle <iframe>s root: root.ownerDocument })); } catch (e) { io = new IntersectionObserver(handleObserve, options); } io.observe(element); } refresh(true); return cleanup; } /** * Automatically updates the position of the floating element when necessary. * Should only be called when the floating element is mounted on the DOM or * visible on the screen. * @returns cleanup function that should be invoked when the floating element is * removed from the DOM or hidden from the screen. * @see https://floating-ui.com/docs/autoUpdate */ function autoUpdate(reference, floating, update, options) { if (options === void 0) { options = {}; } const { ancestorScroll = true, ancestorResize = true, elementResize = typeof ResizeObserver === 'function', layoutShift = typeof IntersectionObserver === 'function', animationFrame = false } = options; const referenceEl = unwrapElement(reference); const ancestors = ancestorScroll || ancestorResize ? [...(referenceEl ? getOverflowAncestors(referenceEl) : []), ...getOverflowAncestors(floating)] : []; ancestors.forEach(ancestor => { ancestorScroll && ancestor.addEventListener('scroll', update, { passive: true }); ancestorResize && ancestor.addEventListener('resize', update); }); const cleanupIo = referenceEl && layoutShift ? observeMove(referenceEl, update) : null; let reobserveFrame = -1; let resizeObserver = null; if (elementResize) { resizeObserver = new ResizeObserver(_ref => { let [firstEntry] = _ref; if (firstEntry && firstEntry.target === referenceEl && resizeObserver) { // Prevent update loops when using the `size` middleware. // https://github.com/floating-ui/floating-ui/issues/1740 resizeObserver.unobserve(floating); cancelAnimationFrame(reobserveFrame); reobserveFrame = requestAnimationFrame(() => { resizeObserver && resizeObserver.observe(floating); }); } update(); }); if (referenceEl && !animationFrame) { resizeObserver.observe(referenceEl); } resizeObserver.observe(floating); } let frameId; let prevRefRect = animationFrame ? getBoundingClientRect(reference) : null; if (animationFrame) { frameLoop(); } function frameLoop() { const nextRefRect = getBoundingClientRect(reference); if (prevRefRect && (nextRefRect.x !== prevRefRect.x || nextRefRect.y !== prevRefRect.y || nextRefRect.width !== prevRefRect.width || nextRefRect.height !== prevRefRect.height)) { update(); } prevRefRect = nextRefRect; frameId = requestAnimationFrame(frameLoop); } update(); return () => { ancestors.forEach(ancestor => { ancestorScroll && ancestor.removeEventListener('scroll', update); ancestorResize && ancestor.removeEventListener('resize', update); }); cleanupIo && cleanupIo(); resizeObserver && resizeObserver.disconnect(); resizeObserver = null; if (animationFrame) { cancelAnimationFrame(frameId); } }; } /** * Computes the `x` and `y` coordinates that will place the floating element * next to a reference element when it is given a certain CSS positioning * strategy. */ const computePosition = (reference, floating, options) => { // This caches the expensive `getClippingElementAncestors` function so that // multiple lifecycle resets re-use the same result. It only lives for a // single call. If other functions become expensive, we can add them as well. const cache = new Map(); const mergedOptions = _extends({ platform }, options); const platformWithCache = _extends({}, mergedOptions.platform, { _c: cache }); return computePosition$1(reference, floating, _extends({}, mergedOptions, { platform: platformWithCache })); }; /** * Floating UI Options * * @typedef {object} FloatingUIOptions */ /** * Determines options for the tooltip and initializes event listeners. * * @param {Step} step The step instance * * @return {FloatingUIOptions} */ function setupTooltip(step) { if (step.cleanup) { step.cleanup(); } const attachToOptions = step._getResolvedAttachToOptions(); let target = attachToOptions.element; const floatingUIOptions = getFloatingUIOptions(attachToOptions, step); const shouldCenter = shouldCenterStep(attachToOptions); if (shouldCenter) { target = document.body; const content = step.shepherdElementComponent.getElement(); content.classList.add('shepherd-centered'); } step.cleanup = autoUpdate(target, step.el, () => { // The element might have already been removed by the end of the tour. if (!step.el) { step.cleanup(); return; } setPosition(target, step, floatingUIOptions, shouldCenter); }); step.target = attachToOptions.element; return floatingUIOptions; } /** * Merge tooltip options handling nested keys. * * @param tourOptions - The default tour options. * @param options - Step specific options. * * @return {floatingUIOptions: FloatingUIOptions} */ function mergeTooltipConfig(tourOptions, options) { return { floatingUIOptions: cjs(tourOptions.floatingUIOptions || {}, options.floatingUIOptions || {}) }; } /** * Cleanup function called when the step is closed/destroyed. * * @param {Step} step */ function destroyTooltip(step) { if (step.cleanup) { step.cleanup(); } step.cleanup = null; } /** * * @return {Promise<*>} */ function setPosition(target, step, floatingUIOptions, shouldCenter) { return computePosition(target, step.el, floatingUIOptions).then(floatingUIposition(step, shouldCenter)) // Wait before forcing focus. .then(step => new Promise(resolve => { setTimeout(() => resolve(step), 300); })) // Replaces focusAfterRender modifier. .then(step => { if (step && step.el) { step.el.focus({ preventScroll: true }); } }); } /** * * @param step * @param shouldCenter * @return {function({x: *, y: *, placement: *, middlewareData: *}): Promise<unknown>} */ function floatingUIposition(step, shouldCenter) { return ({ x, y, placement, middlewareData }) => { if (!step.el) { return step; } if (shouldCenter) { Object.assign(step.el.style, { position: 'fixed', left: '50%', top: '50%', transform: 'translate(-50%, -50%)' }); } else { Object.assign(step.el.style, { position: 'absolute', left: `${x}px`, top: `${y}px` }); } step.el.dataset.popperPlacement = placement; placeArrow(step.el, middlewareData); return step; }; } /** * * @param el * @param middlewareData */ function placeArrow(el, middlewareData) { const arrowEl = el.querySelector('.shepherd-arrow'); if (arrowEl && middlewareData.arrow) { const { x: arrowX, y: arrowY } = middlewareData.arrow; Object.assign(arrowEl.style, { left: arrowX != null ? `${arrowX}px` : '', top: arrowY != null ? `${arrowY}px` : '' }); } } /** * Gets the `Floating UI` options from a set of base `attachTo` options * @param attachToOptions * @param {Step} step The step instance * @return {Object} * @private */ function getFloatingUIOptions(attachToOptions, step) { const options = { strategy: 'absolute', middleware: [] }; const arrowEl = addArrow(step); const shouldCenter = shouldCenterStep(attachToOptions); if (!shouldCenter) { options.middleware.push(flip(), // Replicate PopperJS default behavior. shift({ limiter: limitShift(), crossAxis: true })); if (arrowEl) { options.middleware.push(arrow({ element: arrowEl })); } options.placement = attachToOptions.on; } return cjs(step.options.floatingUIOptions || {}, options); } /** * @param {Step} step * @return {HTMLElement|false|null} */ function addArrow(step) { if (step.options.arrow && step.el) { return step.el.querySelector('.shepherd-arrow'); } return false; } function noop() {} function assign(tar, src) { // @ts-ignore for (const k in src) tar[k] = src[k]; return tar; } function run(fn) { return fn(); } function blank_object() { return Object.create(null); } function run_all(fns) { fns.forEach(run); } function is_function(thing) { return typeof thing === 'function'; } function safe_not_equal(a, b) { return a != a ? b == b : a !== b || a && typeof a === 'object' || typeof a === 'function'; } function is_empty(obj) { return Object.keys(obj).length === 0; } function append(target, node) { target.appendChild(node); } function insert(target, node, anchor) { target.insertBefore(node, anchor || null); } function detach(node) { if (node.parentNode) { node.parentNode.removeChild(node); } } function destroy_each(iterations, detaching) { for (let i = 0; i < iterations.length; i += 1) { if (iterations[i]) iterations[i].d(detaching); } } function element(name) { return document.createElement(name); } function svg_element(name) { return document.createElementNS('http://www.w3.org/2000/svg', name); } function text(data) { return document.createTextNode(data); } function space() { return text(' '); } function empty() { return text(''); } function listen(node, event, handler, options) { node.addEventListener(event, handler, options); return () => node.removeEventListener(event, handler, options); } function attr(node, attribute, value) { if (value == null) node.removeAttribute(attribute);else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value); } /** * List of attributes that should always be set through the attr method, * because updating them through the property setter doesn't work reliably. * In the example of `width`/`height`, the problem is that the setter only * accepts numeric values, but the attribute can also be set to a string like `50%`. * If this list becomes too big, rethink this approach. */ const always_set_through_set_attribute = ['width', 'height']; function set_attributes(node, attributes) { // @ts-ignore const descriptors = Object.getOwnPropertyDescriptors(node.__proto__); for (const key in attributes) { if (attributes[key] == null) { node.removeAttribute(key); } else if (key === 'style') { node.style.cssText = attributes[key]; } else if (key === '__value') { node.value = node[key] = attributes[key]; } else if (descriptors[key] && descriptors[key].set && always_set_through_set_attribute.indexOf(key) === -1) { node[key] = attributes[key]; } else { attr(node, key, attributes[key]); } } } function children(element) { return Array.from(element.childNodes); } function toggle_class(element, name, toggle) { element.classList[toggle ? 'add' : 'remove'](name); } let current_component; function set_current_component(component) { current_component = component; } function get_current_component() { if (!current_component) throw new Error('Function called outside component initialization'); return current_component; } /** * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM. * It must be called during the component's initialisation (but doesn't need to live *inside* the component; * it can be called from an external module). * * `onMount` does not run inside a [server-side component](/docs#run-time-server-side-component-api). * * https://svelte.dev/docs#run-time-svelte-onmount */ function onMount(fn) { get_current_component().$$.on_mount.push(fn); } /** * Schedules a callback to run immediately after the component has been updated. * * The first time the callback runs will be after the initial `onMount` */ function afterUpdate(fn) { get_current_component().$$.after_update.push(fn); } const dirty_components = []; const binding_callbacks = []; let render_callbacks = []; const flush_callbacks = []; const resolved_promise = /* @__PURE__ */Promise.resolve(); let update_scheduled = false; function schedule_update() { if (!update_scheduled) { update_scheduled = true; resolved_promise.then(flush); } } function add_render_callback(fn) { render_callbacks.push(fn); } // flush() calls callbacks in this order: // 1. All beforeUpdate callbacks, in order: parents before children // 2. All bind:this callbacks, in reverse order: children before parents. // 3. All afterUpdate callbacks, in order: parents before children. EXCEPT // for afterUpdates called during the initial onMount, which are called in // reverse order: children before parents. // Since callbacks might update component values, which could trigger another // call to flush(), the following steps guard against this: // 1. During beforeUpdate, any updated components will be added to the // dirty_components array and will cause a reentrant call to flush(). Because // the flush index is kept outside the function, the reentrant call will pick // up where the earlier call left off and go through all dirty components. The // current_component value is saved and restored so that the reentrant call will // not interfere with the "parent" flush() call. // 2. bind:this callbacks cannot trigger new flush() calls. // 3. During afterUpdate, any updated components will NOT have their afterUpdate // callback called a second time; the seen_callbacks set, outside the flush() // function, guarantees this behavior. const seen_callbacks = new Set(); let flushidx = 0; // Do *not* move this inside the flush() function function flush() { // Do not reenter flush while dirty components are updated, as this can // result in an infinite loop. Instead, let the inner flush handle it. // Reentrancy is ok afterwards for bindings etc. if (flushidx !== 0) { return; } const saved_component = current_component; do { // first, call beforeUpdate functions // and update components try { while (flushidx < dirty_components.length) { const component = dirty_components[flushidx]; flushidx++; set_current_component(component); update(component.$$); } } catch (e) { // reset dirty state to not end up in a deadlocked state and then rethrow dirty_components.length = 0; flushidx = 0; throw e; } set_current_component(null); dirty_components.length = 0; flushidx = 0; while (binding_callbacks.length) binding_callbacks.pop()(); // then, once components are updated, call // afterUpdate functions. This may cause // subsequent updates... for (let i = 0; i < render_callbacks.length; i += 1) { const callback = render_callbacks[i]; if (!seen_callbacks.has(callback)) { // ...so guard against infinite loops seen_callbacks.add(callback); callback(); } } render_callbacks.length = 0; } while (dirty_components.length); while (flush_callbacks.length) { flush_callbacks.pop()(); } update_scheduled = false; seen_callbacks.clear(); set_current_component(saved_component); } function update($$) { if ($$.fragment !== null) { $$.update(); run_all($$.before_update); const dirty = $$.dirty; $$.dirty = [-1]; $$.fragment && $$.fragment.p($$.ctx, dirty); $$.after_update.forEach(add_render_callback); } } /** * Useful for example to execute remaining `afterUpdate` callbacks before executing `destroy`. */ function flush_render_callbacks(fns) { const filtered = []; const targets = []; render_callbacks.forEach(c => fns.indexOf(c) === -1 ? filtered.push(c) : targets.push(c)); targets.forEach(c => c()); render_callbacks = filtered; } const outroing = new Set(); let outros; function group_outros() { outros = { r: 0, c: [], p: outros // parent group }; } function check_outros() { if (!outros.r) { run_all(outros.c); } outros = outros.p; } function transition_in(block, local) { if (block && block.i) { outroing.delete(block); block.i(local); } } function transition_out(block, local, detach, callback) { if (block && block.o) { if (outroing.has(block)) return; outroing.add(block); outros.c.push(() => { outroing.delete(block); if (callback) { if (detach) block.d(1); callback(); } }); block.o(local); } else if (callback) { callback(); } } function get_spread_update(levels, updates) { const update = {}; const to_null_out = {}; const accounted_for = { $$scope: 1 }; let i = levels.length; while (i--) { const o = levels[i]; const n = updates[i]; if (n) { for (const key in o) { if (!(key in n)) to_null_out[key] = 1; } for (const key in n) { if (!accounted_for[key]) { update[key] = n[key]; accounted_for[key] = 1; } } levels[i] = n; } else { for (const key in o) { accounted_for[key] = 1; } } } for (const key in to_null_out) { if (!(key in update)) update[key] = undefined; } return update; } function create_component(block) { block && block.c(); } function mount_component(component, target, anchor, customElement) { const { fragment, after_update } = component.$$; fragment && fragment.m(target, anchor); if (!customElement) { // onMount happens before the initial afterUpdate add_render_callback(() => { const new_on_destroy = component.$$.on_mount.map(run).filter(is_function); // if the component was destroyed immediately // it will update the `$$.on_destroy` reference to `null`. // the destructured on_destroy may still reference to the old array if (component.$$.on_destroy) { component.$$.on_destroy.push(...new_on_destroy); } else { // Edge case - component was destroyed immediately, // most likely as a result of a binding initialising run_all(new_on_destroy); } component.$$.on_mount = []; }); } after_update.forEach(add_render_callback); } function destroy_component(component, detaching) { const $$ = component.$$; if ($$.fragment !== null) { flush_render_callbacks($$.after_update); run_all($$.on_destroy); $$.fragment && $$.fragment.d(detaching); // TODO null out other refs, including component.$$ (but need to // preserve final state?) $$.on_destroy = $$.fragment = null; $$.ctx = []; } } function make_dirty(component, i) { if (component.$$.dirty[0] === -1) { dirty_components.push(component); schedule_update(); component.$$.dirty.fill(0); } component.$$.dirty[i / 31 | 0] |= 1 << i % 31; } function init(component, options, instance, create_fragment, not_equal, props, append_styles, dirty = [-1]) { const parent_component = current_component; set_current_component(component); const $$ = component.$$ = { fragment: null, ctx: [], // state props, update: noop, not_equal, bound: blank_object(), // lifecycle on_mount: [], on_destroy: [], on_disconnect: [], before_update: [], after_update: [], context: new Map(options.context || (parent_component ? parent_component.$$.context : [])), // everything else callbacks: blank_object(), dirty, skip_bound: false, root: options.target || parent_component.$$.root }; append_styles && append_styles($$.root); let ready = false; $$.ctx = instance ? instance(component, options.props || {}, (i, ret, ...rest) => { const value = rest.length ? rest[0] : ret; if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value); if (ready) make_dirty(component, i); } return ret; }) : []; $$.update(); ready = true; run_all($$.before_update); // `false` as a special case of no DOM component $$.fragment = create_fragment ? create_fragment($$.ctx) : false; if (options.target) { if (options.hydrate) { const nodes = children(options.target); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion $$.fragment && $$.fragment.l(nodes); nodes.forEach(detach); } else { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion $$.fragment && $$.fragment.c(); } if (options.intro) transition_in(component.$$.fragment); mount_component(component, options.target, options.anchor, options.customElement); flush(); } set_current_component(parent_component); } /** * Base class for Svelte components. Used when dev=false. */ class SvelteComponent { $destroy() { destroy_component(this, 1); this.$destroy = noop; } $on(type, callback) { if (!is_function(callback)) { return noop; } const callbacks = this.$$.callbacks[type] || (this.$$.callbacks[type] = []); callbacks.push(callback); return () => { const index = callbacks.indexOf(callback); if (index !== -1) callbacks.splice(index, 1); }; } $set($$props) { if (this.$$set && !is_empty($$props)) { this.$$.skip_bound = true; this.$$set($$props); this.$$.skip_bound = false; } } } /* src/js/components/shepherd-button.svelte generated by Svelte v3.59.2 */ function create_fragment$8(ctx) { let button; let button_aria_label_value; let button_class_value; let mounted; let dispose; return { c() { button = element("button"); attr(button, "aria-label", button_aria_label_value = /*label*/ctx[3] ? /*label*/ctx[3] : null); attr(button, "class", button_class_value = `${/*classes*/ctx[1] || ''} shepherd-button ${/*secondary*/ctx[4] ? 'shepherd-button-secondary' : ''}`); button.disabled = /*disabled*/ctx[2]; attr(button, "tabindex", "0"); }, m(target, anchor) { insert(target, button, anchor); button.innerHTML = /*text*/ctx[5]; if (!mounted) { dispose = listen(button, "click", function () { if (is_function( /*action*/ctx[0])) /*action*/ctx[0].apply(this, arguments); }); mounted = true; } }, p(new_ctx, [dirty]) { ctx = new_ctx; if (dirty & /*text*/32) button.innerHTML = /*text*/ctx[5]; if (dirty & /*label*/8 && button_aria_label_value !== (button_aria_label_value = /*label*/ctx[3] ? /*label*/ctx[3] : null)) { attr(button, "aria-label", button_aria_label_value); } if (dirty & /*classes, secondary*/18 && button_class_value !== (button_class_value = `${/*classes*/ctx[1] || ''} shepherd-button ${/*secondary*/ctx[4] ? 'shepherd-button-secondary' : ''}`)) { attr(button, "class", button_class_value); } if (dirty & /*disabled*/4) { button.disabled = /*disabled*/ctx[2]; } }, i: noop, o: noop, d(detaching) { if (detaching) detach(button); mounted = false; dispose(); } }; } function instance$8($$self, $$props, $$invalidate) { let { config, step } = $$props; let action, classes, disabled, label, secondary, text; function getConfigOption(option) { if (isFunction(option)) { return option = option.call(step); } return option; } $$self.$$set = $$props => { if ('config' in $$props) $$invalidate(6, config = $$props.config); if ('step' in $$props) $$invalidate(7, step = $$props.step); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*config, step*/192) { { $$invalidate(0, action = config.action ? config.action.bind(step.tour) : null); $$invalidate(1, classes = config.classes); $$invalidate(2, disabled = config.disabled ? getConfigOption(config.disabled) : false); $$invalidate(3, label = config.label ? getConfigOption(config.label) : null); $$invalidate(4, secondary = config.secondary); $$invalidate(5, text = config.text ? getConfigOption(config.text) : null); } } }; return [action, classes, disabled, label, secondary, text, config, step]; } class Shepherd_button extends SvelteComponent { constructor(options) { super(); init(this, options, instance$8, create_fragment$8, safe_not_equal, { config: 6, step: 7 }); } } /* src/js/components/shepherd-footer.svelte generated by Svelte v3.59.2 */ function get_each_context(ctx, list, i) { const child_ctx = ctx.slice(); child_ctx[2] = list[i]; return child_ctx; } // (24:4) {#if buttons} function create_if_block$3(ctx) { let each_1_anchor; let current; let each_value = /*buttons*/ctx[1]; let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i)); } const out = i => transition_out(each_blocks[i], 1, 1, () => { each_blocks[i] = null; }); return { c() { for (let i = 0; i < each_blocks.length; i += 1) { each_blocks[i].c(); } each_1_anchor = empty(); }, m(target, anchor) { for (let i = 0; i < each_blocks.length; i += 1) { if (each_blocks[i]) { each_blocks[i].m(target, anchor); } } insert(target, each_1_anchor, anchor); current = true; }, p(ctx, dirty) { if (dirty & /*buttons, step*/3) { each_value = /*buttons*/ctx[1]; let i; for (i = 0; i < each_value.length; i += 1) { const child_ctx = get_each_context(ctx, each_value, i); if (each_blocks[i]) { each_blocks[i].p(child_ctx, dirty); transition_in(each_blocks[i], 1); } else { each_blocks[i] = create_each_block(child_ctx); each_blocks[i].c(); transition_in(each_blocks[i], 1); each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor); } } group_outros(); for (i = each_value.length; i < each_blocks.length; i += 1) { out(i); } check_outros(); } }, i(local) { if (current) return; for (let i = 0; i < each_value.length; i += 1) { transition_in(each_blocks[i]); } current = true; }, o(local) { each_blocks = each_blocks.filter(Boolean); for (let i = 0; i < each_blocks.length; i += 1) { transition_out(each_blocks[i]); } current = false; }, d(detaching) { destroy_each(each_blocks, detaching); if (detaching) detach(each_1_anchor); } }; } // (25:8) {#each buttons as config} function create_each_block(ctx) { let shepherdbutton; let current; shepherdbutton = new Shepherd_button({ props: { config: /*config*/ctx[2], step: /*step*/ctx[0] } }); return { c() { create_component(shepherdbutton.$$.fragment); }, m(target, anchor) { mount_component(shepherdbutton, target, anchor); current = true; }, p(ctx, dirty) { const shepherdbutton_changes = {}; if (dirty & /*buttons*/2) shepherdbutton_changes.config = /*config*/ctx[2]; if (dirty & /*step*/1) shepherdbutton_changes.step = /*step*/ctx[0]; shepherdbutton.$set(shepherdbutton_changes); }, i(local) { if (current) return; transition_in(shepherdbutton.$$.fragment, local); current = true; }, o(local) { transition_out(shepherdbutton.$$.fragment, local); current = false; }, d(detaching) { destroy_component(shepherdbutton, detaching); } }; } function create_fragment$7(ctx) { let footer; let current; let if_block = /*buttons*/ctx[1] && create_if_block$3(ctx); return { c() { footer = element("footer"); if (if_block) if_block.c(); attr(footer, "class", "shepherd-footer"); }, m(target, anchor) { insert(target, footer, anchor); if (if_block) if_block.m(footer, null); current = true; }, p(ctx, [dirty]) { if ( /*buttons*/ctx[1]) { if (if_block) { if_block.p(ctx, dirty); if (dirty & /*buttons*/2) { transition_in(if_block, 1); } } else { if_block = create_if_block$3(ctx); if_block.c(); transition_in(if_block, 1); if_block.m(footer, null); } } else if (if_block) { group_outros(); transition_out(if_block, 1, 1, () => { if_block = null; }); check_outros(); } }, i(local) { if (current) return; transition_in(if_block); current = true; }, o(local) { transition_out(if_block); current = false; }, d(detaching) { if (detaching) detach(footer); if (if_block) if_block.d(); } }; } function instance$7($$self, $$props, $$invalidate) { let buttons; let { step } = $$props; $$self.$$set = $$props => { if ('step' in $$props) $$invalidate(0, step = $$props.step); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*step*/1) { $$invalidate(1, buttons = step.options.buttons); } }; return [step, buttons]; } class Shepherd_footer extends SvelteComponent { constructor(options) { super(); init(this, options, instance$7, create_fragment$7, safe_not_equal, { step: 0 }); } } /* src/js/components/shepherd-cancel-icon.svelte generated by Svelte v3.59.2 */ function create_fragment$6(ctx) { let button; let span; let button_aria_label_value; let mounted; let dispose; return { c() { button = element("button"); span = element("span"); span.textContent = "×"; attr(span, "aria-hidden", "true"); attr(button, "aria-label", button_aria_label_value = /*cancelIcon*/ctx[0].label ? /*cancelIcon*/ctx[0].label : 'Close Tour'); attr(button, "class", "shepherd-cancel-icon"); attr(button, "type", "button"); }, m(target, anchor) { insert(target, button, anchor); append(button, span); if (!mounted) { dispose = listen(button, "click", /*handleCancelClick*/ctx[1]); mounted = true; } }, p(ctx, [dirty]) { if (dirty & /*cancelIcon*/1 && button_aria_label_value !== (button_aria_label_value = /*cancelIcon*/ctx[0].label ? /*cancelIcon*/ctx[0].label : 'Close Tour')) { attr(button, "aria-label", button_aria_label_value); } }, i: noop, o: noop, d(detaching) { if (detaching) detach(button); mounted = false; dispose(); } }; } function instance$6($$self, $$props, $$invalidate) { let { cancelIcon, step } = $$props; /** * Add a click listener to the cancel link that cancels the tour */ const handleCancelClick = e => { e.preventDefault(); step.cancel(); }; $$self.$$set = $$props => { if ('cancelIcon' in $$props) $$invalidate(0, cancelIcon = $$props.cancelIcon); if ('step' in $$props) $$invalidate(2, step = $$props.step); }; return [cancelIcon, handleCancelClick, step]; } class Shepherd_cancel_icon extends SvelteComponent { constructor(options) { super(); init(this, options, instance$6, create_fragment$6, safe_not_equal, { cancelIcon: 0, step: 2 }); } } /* src/js/components/shepherd-title.svelte generated by Svelte v3.59.2 */ function create_fragment$5(ctx) { let h3; return { c() { h3 = element("h3"); attr(h3, "id", /*labelId*/ctx[1]); attr(h3, "class", "shepherd-title"); }, m(target, anchor) { insert(target, h3, anchor); /*h3_binding*/ ctx[3](h3); }, p(ctx, [dirty]) { if (dirty & /*labelId*/2) { attr(h3, "id", /*labelId*/ctx[1]); } }, i: noop, o: noop, d(detaching) { if (detaching) detach(h3); /*h3_binding*/ ctx[3](null); } }; } function instance$5($$self, $$props, $$invalidate) { let { labelId, element, title } = $$props; afterUpdate(() => { if (isFunction(title)) { $$invalidate(2, title = title()); } $$invalidate(0, element.innerHTML = title, element); }); function h3_binding($$value) { binding_callbacks[$$value ? 'unshift' : 'push'](() => { element = $$value; $$invalidate(0, element); }); } $$self.$$set = $$props => { if ('labelId' in $$props) $$invalidate(1, labelId = $$props.labelId); if ('element' in $$props) $$invalidate(0, element = $$props.element); if ('title' in $$props) $$invalidate(2, title = $$props.title); }; return [element, labelId, title, h3_binding]; } class Shepherd_title extends SvelteComponent { constructor(options) { super(); init(this, options, instance$5, create_fragment$5, safe_not_equal, { labelId: 1, element: 0, title: 2 }); } } /* src/js/components/shepherd-header.svelte generated by Svelte v3.59.2 */ function create_if_block_1$1(ctx) { let shepherdtitle; let current; shepherdtitle = new Shepherd_title({ props: { labelId: /*labelId*/ctx[0], title: /*title*/ctx[2] } }); return { c() { create_component(shepherdtitle.$$.fragment); }, m(target, anchor) { mount_component(shepherdtitle, target, anchor); current = true; }, p(ctx, dirty) { const shepherdtitle_changes = {}; if (dirty & /*labelId*/1) shepherdtitle_changes.labelId = /*labelId*/ctx[0]; if (dirty & /*title*/4) shepherdtitle_changes.title = /*title*/ctx[2]; shepherdtitle.$set(shepherdtitle_changes); }, i(local) { if (current) return; transition_in(shepherdtitle.$$.fragment, local); current = true; }, o(local) { transition_out(shepherdtitle.$$.fragment, local); current = false; }, d(detaching) { destroy_component(shepherdtitle, detaching); } }; } // (39:4) {#if cancelIcon && cancelIcon.enabled} function create_if_block$2(ctx) { let shepherdcancelicon; let current; shepherdcancelicon = new Shepherd_cancel_icon({ props: { cancelIcon: /*cancelIcon*/ctx[3], step: /*step*/ctx[1] } }); return { c() { create_component(shepherdcancelicon.$$.fragment); }, m(target, anchor) { mount_component(shepherdcancelicon, target, anchor); current = true; }, p(ctx, dirty) { const shepherdcancelicon_changes = {}; if (dirty & /*cancelIcon*/8) shepherdcancelicon_changes.cancelIcon = /*cancelIcon*/ctx[3]; if (dirty & /*step*/2) shepherdcancelicon_changes.step = /*step*/ctx[1]; shepherdcancelicon.$set(shepherdcancelicon_changes); }, i(local) { if (current) return; transition_in(shepherdcancelicon.$$.fragment, local); current = true; }, o(local) { transition_out(shepherdcancelicon.$$.fragment, local); current = false; }, d(detaching) { destroy_component(shepherdcancelicon, detaching); } }; } function create_fragment$4(ctx) { let header; let t; let current; let if_block0 = /*title*/ctx[2] && create_if_block_1$1(ctx); let if_block1 = /*cancelIcon*/ctx[3] && /*cancelIcon*/ctx[3].enabled && create_if_block$2(ctx); return { c() { header = element("header"); if (if_block0) if_block0.c(); t = space(); if (if_block1) if_block1.c(); attr(header, "class", "shepherd-header"); }, m(target, anchor) { insert(target, header, anchor); if (if_block0) if_block0.m(header, null); append(header, t); if (if_block1) if_block1.m(header, null); current = true; }, p(ctx, [dirty]) { if ( /*title*/ctx[2]) { if (if_block0) { if_block0.p(ctx, dirty); if (dirty & /*title*/4) { transition_in(if_block0, 1); } } else { if_block0 = create_if_block_1$1(ctx); if_block0.c(); transition_in(if_block0, 1); if_block0.m(header, t); } } else if (if_block0) { group_outros(); transition_out(if_block0, 1, 1, () => { if_block0 = null; }); check_outros(); } if ( /*cancelIcon*/ctx[3] && /*cancelIcon*/ctx[3].enabled) { if (if_block1) { if_block1.p(ctx, dirty); if (dirty & /*cancelIcon*/8) { transition_in(if_block1, 1); } } else { if_block1 = create_if_block$2(ctx); if_block1.c(); transition_in(if_block1, 1); if_block1.m(header, null); } } else if (if_block1) { group_outros(); transition_out(if_block1, 1, 1, () => { if_block1 = null; }); check_outros(); } }, i(local) { if (current) return; transition_in(if_block0); transition_in(if_block1); current = true; }, o(local) { transition_out(if_block0); transition_out(if_block1); current = false; }, d(detaching) { if (detaching) detach(header); if (if_block0) if_block0.d(); if (if_block1) if_block1.d(); } }; } function instance$4($$self, $$props, $$invalidate) { let { labelId, step } = $$props; let title, cancelIcon; $$self.$$set = $$props => { if ('labelId' in $$props) $$invalidate(0, labelId = $$props.labelId); if ('step' in $$props) $$invalidate(1, step = $$props.step); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*step*/2) { { $$invalidate(2, title = step.options.title); $$invalidate(3, cancelIcon = step.options.cancelIcon); } } }; return [labelId, step, title, cancelIcon]; } class Shepherd_header extends SvelteComponent { constructor(options) { super(); init(this, options, instance$4, create_fragment$4, safe_not_equal, { labelId: 0, step: 1 }); } } /* src/js/components/shepherd-text.svelte generated by Svelte v3.59.2 */ function create_fragment$3(ctx) { let div; return { c() { div = element("div"); attr(div, "class", "shepherd-text"); attr(div, "id", /*descriptionId*/ctx[1]); }, m(target, anchor) { insert(target, div, anchor); /*div_binding*/ ctx[3](div); }, p(ctx, [dirty]) { if (dirty & /*descriptionId*/2) { attr(div, "id", /*descriptionId*/ctx[1]); } }, i: noop, o: noop, d(detaching) { if (detaching) detach(div); /*div_binding*/ ctx[3](null); } }; } function instance$3($$self, $$props, $$invalidate) { let { descriptionId, element, step } = $$props; afterUpdate(() => { let { text } = step.options; if (isFunction(text)) { text = text.call(step); } if (isHTMLElement$1(text)) { element.appendChild(text); } else { $$invalidate(0, element.innerHTML = text, element); } }); function div_binding($$value) { binding_callbacks[$$value ? 'unshift' : 'push'](() => { element = $$value; $$invalidate(0, element); }); } $$self.$$set = $$props => { if ('descriptionId' in $$props) $$invalidate(1, descriptionId = $$props.descriptionId); if ('element' in $$props) $$invalidate(0, element = $$props.element); if ('step' in $$props) $$invalidate(2, step = $$props.step); }; return [element, descriptionId, step, div_binding]; } class Shepherd_text extends SvelteComponent { constructor(options) { super(); init(this, options, instance$3, create_fragment$3, safe_not_equal, { descriptionId: 1, element: 0, step: 2 }); } } /* src/js/components/shepherd-content.svelte generated by Svelte v3.59.2 */ function create_if_block_2(ctx) { let shepherdheader; let current; shepherdheader = new Shepherd_header({ props: { labelId: /*labelId*/ctx[1], step: /*step*/ctx[2] } }); return { c() { create_component(shepherdheader.$$.fragment); }, m(target, anchor) { mount_component(shepherdheader, target, anchor); current = true; }, p(ctx, dirty) { const shepherdheader_changes = {}; if (dirty & /*labelId*/2) shepherdheader_changes.labelId = /*labelId*/ctx[1]; if (dirty & /*step*/4) shepherdheader_changes.step = /*step*/ctx[2]; shepherdheader.$set(shepherdheader_changes); }, i(local) { if (current) return; transition_in(shepherdheader.$$.fragment, local); current = true; }, o(local) { transition_out(shepherdheader.$$.fragment, local); current = false; }, d(detaching) { destroy_component(shepherdheader, detaching); } }; } // (28:2) {#if !isUndefined(step.options.text)} function create_if_block_1(ctx) { let shepherdtext; let current; shepherdtext = new Shepherd_text({ props: { descriptionId: /*descriptionId*/ctx[0], step: /*step*/ctx[2] } }); return { c() { create_component(shepherdtext.$$.fragment); }, m(target, anchor) { mount_component(shepherdtext, target, anchor); current = true; }, p(ctx, dirty) { const shepherdtext_changes = {}; if (dirty & /*descriptionId*/1) shepherdtext_changes.descriptionId = /*descriptionId*/ctx[0]; if (dirty & /*step*/4) shepherdtext_changes.step = /*step*/ctx[2]; shepherdtext.$set(shepherdtext_changes); }, i(local) { if (current) return; transition_in(shepherdtext.$$.fragment, local); current = true; }, o(local) { transition_out(shepherdtext.$$.fragment, local); current = false; }, d(detaching) { destroy_component(shepherdtext, detaching); } }; } // (35:2) {#if Array.isArray(step.options.buttons) && step.options.buttons.length} function create_if_block$1(ctx) { let shepherdfooter; let current; shepherdfooter = new Shepherd_footer({ props: { step: /*step*/ctx[2] } }); return { c() { create_component(shepherdfooter.$$.fragment); }, m(target, anchor) { mount_component(shepherdfooter, target, anchor); current = true; }, p(ctx, dirty) { const shepherdfooter_changes = {}; if (dirty & /*step*/4) shepherdfooter_changes.step = /*step*/ctx[2]; shepherdfooter.$set(shepherdfooter_changes); }, i(local) { if (current) return; transition_in(shepherdfooter.$$.fragment, local); current = true; }, o(local) { transition_out(shepherdfooter.$$.fragment, local); current = false; }, d(detaching) { destroy_component(shepherdfooter, detaching); } }; } function create_fragment$2(ctx) { let div; let show_if_2 = !isUndefined( /*step*/ctx[2].options.title) || /*step*/ctx[2].options.cancelIcon && /*step*/ctx[2].options.cancelIcon.enabled; let t0; let show_if_1 = !isUndefined( /*step*/ctx[2].options.text); let t1; let show_if = Array.isArray( /*step*/ctx[2].options.buttons) && /*step*/ctx[2].options.buttons.length; let current; let if_block0 = show_if_2 && create_if_block_2(ctx); let if_block1 = show_if_1 && create_if_block_1(ctx); let if_block2 = show_if && create_if_block$1(ctx); return { c() { div = element("div"); if (if_block0) if_block0.c(); t0 = space(); if (if_block1) if_block1.c(); t1 = space(); if (if_block2) if_block2.c(); attr(div, "class", "shepherd-content"); }, m(target, anchor) { insert(target, div, anchor); if (if_block0) if_block0.m(div, null); append(div, t0); if (if_block1) if_block1.m(div, null); append(div, t1); if (if_block2) if_block2.m(div, null); current = true; }, p(ctx, [dirty]) { if (dirty & /*step*/4) show_if_2 = !isUndefined( /*step*/ctx[2].options.title) || /*step*/ctx[2].options.cancelIcon && /*step*/ctx[2].options.cancelIcon.enabled; if (show_if_2) { if (if_block0) { if_block0.p(ctx, dirty); if (dirty & /*step*/4) { transition_in(if_block0, 1); } } else { if_block0 = create_if_block_2(ctx); if_block0.c(); transition_in(if_block0, 1); if_block0.m(div, t0); } } else if (if_block0) { group_outros(); transition_out(if_block0, 1, 1, () => { if_block0 = null; }); check_outros(); } if (dirty & /*step*/4) show_if_1 = !isUndefined( /*step*/ctx[2].options.text); if (show_if_1) { if (if_block1) { if_block1.p(ctx, dirty); if (dirty & /*step*/4) { transition_in(if_block1, 1); } } else { if_block1 = create_if_block_1(ctx); if_block1.c(); transition_in(if_block1, 1); if_block1.m(div, t1); } } else if (if_block1) { group_outros(); transition_out(if_block1, 1, 1, () => { if_block1 = null; }); check_outros(); } if (dirty & /*step*/4) show_if = Array.isArray( /*step*/ctx[2].options.buttons) && /*step*/ctx[2].options.buttons.length; if (show_if) { if (if_block2) { if_block2.p(ctx, dirty); if (dirty & /*step*/4) { transition_in(if_block2, 1); } } else { if_block2 = create_if_block$1(ctx); if_block2.c(); transition_in(if_block2, 1); if_block2.m(div, null); } } else if (if_block2) { group_outros(); transition_out(if_block2, 1, 1, () => { if_block2 = null; }); check_outros(); } }, i(local) { if (current) return; transition_in(if_block0); transition_in(if_block1); transition_in(if_block2); current = true; }, o(local) { transition_out(if_block0); transition_out(if_block1); transition_out(if_block2); current = false; }, d(detaching) { if (detaching) detach(div); if (if_block0) if_block0.d(); if (if_block1) if_block1.d(); if (if_block2) if_block2.d(); } }; } function instance$2($$self, $$props, $$invalidate) { let { descriptionId, labelId, step } = $$props; $$self.$$set = $$props => { if ('descriptionId' in $$props) $$invalidate(0, descriptionId = $$props.descriptionId); if ('labelId' in $$props) $$invalidate(1, labelId = $$props.labelId); if ('step' in $$props) $$invalidate(2, step = $$props.step); }; return [descriptionId, labelId, step]; } class Shepherd_content extends SvelteComponent { constructor(options) { super(); init(this, options, instance$2, create_fragment$2, safe_not_equal, { descriptionId: 0, labelId: 1, step: 2 }); } } /* src/js/components/shepherd-element.svelte generated by Svelte v3.59.2 */ function create_if_block(ctx) { let div; return { c() { div = element("div"); attr(div, "class", "shepherd-arrow"); attr(div, "data-popper-arrow", ""); }, m(target, anchor) { insert(target, div, anchor); }, d(detaching) { if (detaching) detach(div); } }; } function create_fragment$1(ctx) { let div; let t; let shepherdcontent; let div_aria_describedby_value; let div_aria_labelledby_value; let current; let mounted; let dispose; let if_block = /*step*/ctx[4].options.arrow && /*step*/ctx[4].options.attachTo && /*step*/ctx[4].options.attachTo.element && /*step*/ctx[4].options.attachTo.on && create_if_block(); shepherdcontent = new Shepherd_content({ props: { descriptionId: /*descriptionId*/ctx[2], labelId: /*labelId*/ctx[3], step: /*step*/ctx[4] } }); let div_levels = [{ "aria-describedby": div_aria_describedby_value = !isUndefined( /*step*/ctx[4].options.text) ? /*descriptionId*/ctx[2] : null }, { "aria-labelledby": div_aria_labelledby_value = /*step*/ctx[4].options.title ? /*labelId*/ctx[3] : null }, /*dataStepId*/ctx[1], { role: "dialog" }, { tabindex: "0" }]; let div_data = {}; for (let i = 0; i < div_levels.length; i += 1) { div_data = assign(div_data, div_levels[i]); } return { c() { div = element("div"); if (if_block) if_block.c(); t = space(); create_component(shepherdcontent.$$.fragment); set_attributes(div, div_data); toggle_class(div, "shepherd-has-cancel-icon", /*hasCancelIcon*/ctx[5]); toggle_class(div, "shepherd-has-title", /*hasTitle*/ctx[6]); toggle_class(div, "shepherd-element", true); }, m(target, anchor) { insert(target, div, anchor); if (if_block) if_block.m(div, null); append(div, t); mount_component(shepherdcontent, div, null); /*div_binding*/ ctx[13](div); current = true; if (!mounted) { dispose = listen(div, "keydown", /*handleKeyDown*/ctx[7]); mounted = true; } }, p(ctx, [dirty]) { if ( /*step*/ctx[4].options.arrow && /*step*/ctx[4].options.attachTo && /*step*/ctx[4].options.attachTo.element && /*step*/ctx[4].options.attachTo.on) { if (if_block) ;else { if_block = create_if_block(); if_block.c(); if_block.m(div, t); } } else if (if_block) { if_block.d(1); if_block = null; } const shepherdcontent_changes = {}; if (dirty & /*descriptionId*/4) shepherdcontent_changes.descriptionId = /*descriptionId*/ctx[2]; if (dirty & /*labelId*/8) shepherdcontent_changes.labelId = /*labelId*/ctx[3]; if (dirty & /*step*/16) shepherdcontent_changes.step = /*step*/ctx[4]; shepherdcontent.$set(shepherdcontent_changes); set_attributes(div, div_data = get_spread_update(div_levels, [(!current || dirty & /*step, descriptionId*/20 && div_aria_describedby_value !== (div_aria_describedby_value = !isUndefined( /*step*/ctx[4].options.text) ? /*descriptionId*/ctx[2] : null)) && { "aria-describedby": div_aria_describedby_value }, (!current || dirty & /*step, labelId*/24 && div_aria_labelledby_value !== (div_aria_labelledby_value = /*step*/ctx[4].options.title ? /*labelId*/ctx[3] : null)) && { "aria-labelledby": div_aria_labelledby_value }, dirty & /*dataStepId*/2 && /*dataStepId*/ctx[1], { role: "dialog" }, { tabindex: "0" }])); toggle_class(div, "shepherd-has-cancel-icon", /*hasCancelIcon*/ctx[5]); toggle_class(div, "shepherd-has-title", /*hasTitle*/ctx[6]); toggle_class(div, "shepherd-element", true); }, i(local) { if (current) return; transition_in(shepherdcontent.$$.fragment, local); current = true; }, o(local) { transition_out(shepherdcontent.$$.fragment, local); current = false; }, d(detaching) { if (detaching) detach(div); if (if_block) if_block.d(); destroy_component(shepherdcontent); /*div_binding*/ ctx[13](null); mounted = false; dispose(); } }; } const KEY_TAB = 9; const KEY_ESC = 27; const LEFT_ARROW = 37; const RIGHT_ARROW = 39; function getClassesArray(classes) { return classes.split(' ').filter(className => !!className.length); } function instance$1($$self, $$props, $$invalidate) { let { classPrefix, element, descriptionId, firstFocusableElement, focusableElements, labelId, lastFocusableElement, step, dataStepId } = $$props; let hasCancelIcon, hasTitle, classes; const getElement = () => element; onMount(() => { // Get all elements that are focusable $$invalidate(1, dataStepId = { [`data-${classPrefix}shepherd-step-id`]: step.id }); $$invalidate(9, focusableElements = element.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]')); $$invalidate(8, firstFocusableElement = focusableElements[0]); $$invalidate(10, lastFocusableElement = focusableElements[focusableElements.length - 1]); }); afterUpdate(() => { if (classes !== step.options.classes) { updateDynamicClasses(); } }); function updateDynamicClasses() { removeClasses(classes); classes = step.options.classes; addClasses(classes); } function removeClasses(classes) { if (isString(classes)) { const oldClasses = getClassesArray(classes); if (oldClasses.length) { element.classList.remove(...oldClasses); } } } function addClasses(classes) { if (isString(classes)) { const newClasses = getClassesArray(classes); if (newClasses.length) { element.classList.add(...newClasses); } } } /** * Setup keydown events to allow closing the modal with ESC * * Borrowed from this great post! https://bitsofco.de/accessible-modal-dialog/ * * @private */ const handleKeyDown = e => { const { tour } = step; switch (e.keyCode) { case KEY_TAB: if (focusableElements.length === 0) { e.preventDefault(); break; } // Backward tab if (e.shiftKey) { if (document.activeElement === firstFocusableElement || document.activeElement.classList.contains('shepherd-element')) { e.preventDefault(); lastFocusableElement.focus(); } } else { if (document.activeElement === lastFocusableElement) { e.preventDefault(); firstFocusableElement.focus(); } } break; case KEY_ESC: if (tour.options.exitOnEsc) { e.stopPropagation(); step.cancel(); } break; case LEFT_ARROW: if (tour.options.keyboardNavigation) { e.stopPropagation(); tour.back(); } break; case RIGHT_ARROW: if (tour.options.keyboardNavigation) { e.stopPropagation(); tour.next(); } break; } }; function div_binding($$value) { binding_callbacks[$$value ? 'unshift' : 'push'](() => { element = $$value; $$invalidate(0, element); }); } $$self.$$set = $$props => { if ('classPrefix' in $$props) $$invalidate(11, classPrefix = $$props.classPrefix); if ('element' in $$props) $$invalidate(0, element = $$props.element); if ('descriptionId' in $$props) $$invalidate(2, descriptionId = $$props.descriptionId); if ('firstFocusableElement' in $$props) $$invalidate(8, firstFocusableElement = $$props.firstFocusableElement); if ('focusableElements' in $$props) $$invalidate(9, focusableElements = $$props.focusableElements); if ('labelId' in $$props) $$invalidate(3, labelId = $$props.labelId); if ('lastFocusableElement' in $$props) $$invalidate(10, lastFocusableElement = $$props.lastFocusableElement); if ('step' in $$props) $$invalidate(4, step = $$props.step); if ('dataStepId' in $$props) $$invalidate(1, dataStepId = $$props.dataStepId); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*step*/16) { { $$invalidate(5, hasCancelIcon = step.options && step.options.cancelIcon && step.options.cancelIcon.enabled); $$invalidate(6, hasTitle = step.options && step.options.title); } } }; return [element, dataStepId, descriptionId, labelId, step, hasCancelIcon, hasTitle, handleKeyDown, firstFocusableElement, focusableElements, lastFocusableElement, classPrefix, getElement, div_binding]; } class Shepherd_element extends SvelteComponent { constructor(options) { super(); init(this, options, instance$1, create_fragment$1, safe_not_equal, { classPrefix: 11, element: 0, descriptionId: 2, firstFocusableElement: 8, focusableElements: 9, labelId: 3, lastFocusableElement: 10, step: 4, dataStepId: 1, getElement: 12 }); } get getElement() { return this.$$.ctx[12]; } } /** * A class representing steps to be added to a tour. * @extends {Evented} */ class Step extends Evented { /** * Create a step * @param {Tour} tour The tour for the step * @param {object} options The options for the step * @param {boolean} options.arrow Whether to display the arrow for the tooltip or not. Defaults to `true`. * @param {object} options.attachTo The element the step should be attached to on the page. * An object with properties `element` and `on`. * * ```js * const step = new Step(tour, { * attachTo: { element: '.some .selector-path', on: 'left' }, * ...moreOptions * }); * ``` * * If you don’t specify an `attachTo` the element will appear in the middle of the screen. The same will happen if your `attachTo.element` callback returns `null`, `undefined`, or a selector that does not exist in the DOM. * If you omit the `on` portion of `attachTo`, the element will still be highlighted, but the tooltip will appear * in the middle of the screen, without an arrow pointing to the target. * If the element to highlight does not yet exist while instantiating tour steps, you may use lazy evaluation by supplying a function to `attachTo.element`. The function will be called in the `before-show` phase. * @param {string|HTMLElement|function} options.attachTo.element An element selector string, DOM element, or a function (returning a selector, a DOM element, `null` or `undefined`). * @param {string} options.attachTo.on The optional direction to place the FloatingUI tooltip relative to the element. * - Possible string values: 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end' * @param {Object} options.advanceOn An action on the page which should advance shepherd to the next step. * It should be an object with a string `selector` and an `event` name * ```js * const step = new Step(tour, { * advanceOn: { selector: '.some .selector-path', event: 'click' }, * ...moreOptions * }); * ``` * `event` doesn’t have to be an event inside the tour, it can be any event fired on any element on the page. * You can also always manually advance the Tour by calling `myTour.next()`. * @param {function} options.beforeShowPromise A function that returns a promise. * When the promise resolves, the rest of the `show` code for the step will execute. * @param {Object[]} options.buttons An array of buttons to add to the step. These will be rendered in a * footer below the main body text. * @param {function} options.buttons.button.action A function executed when the button is clicked on. * It is automatically bound to the `tour` the step is associated with, so things like `this.next` will * work inside the action. * You can use action to skip steps or navigate to specific steps, with something like: * ```js * action() { * return this.show('some_step_name'); * } * ``` * @param {string} options.buttons.button.classes Extra classes to apply to the `<a>` * @param {boolean} options.buttons.button.disabled Should the button be disabled? * @param {string} options.buttons.button.label The aria-label text of the button * @param {boolean} options.buttons.button.secondary If true, a shepherd-button-secondary class is applied to the button * @param {string} options.buttons.button.text The HTML text of the button * @param {boolean} options.canClickTarget A boolean, that when set to false, will set `pointer-events: none` on the target * @param {object} options.cancelIcon Options for the cancel icon * @param {boolean} options.cancelIcon.enabled Should a cancel “✕” be shown in the header of the step? * @param {string} options.cancelIcon.label The label to add for `aria-label` * @param {string} options.classes A string of extra classes to add to the step's content element. * @param {string} options.highlightClass An extra class to apply to the `attachTo` element when it is * highlighted (that is, when its step is active). You can then target that selector in your CSS. * @param {string} options.id The string to use as the `id` for the step. * @param {number} options.modalOverlayOpeningPadding An amount of padding to add around the modal overlay opening * @param {number | { topLeft: number, bottomLeft: number, bottomRight: number, topRight: number }} options.modalOverlayOpeningRadius An amount of border radius to add around the modal overlay opening * @param {object} options.floatingUIOptions Extra options to pass to FloatingUI * @param {boolean|Object} options.scrollTo Should the element be scrolled to when this step is shown? If true, uses the default `scrollIntoView`, * if an object, passes that object as the params to `scrollIntoView` i.e. `{behavior: 'smooth', block: 'center'}` * @param {function} options.scrollToHandler A function that lets you override the default scrollTo behavior and * define a custom action to do the scrolling, and possibly other logic. * @param {function} options.showOn A function that, when it returns `true`, will show the step. * If it returns false, the step will be skipped. * @param {string} options.text The text in the body of the step. It can be one of three types: * ``` * - HTML string * - `HTMLElement` object * - `Function` to be executed when the step is built. It must return one the two options above. * ``` * @param {string} options.title The step's title. It becomes an `h3` at the top of the step. It can be one of two types: * ``` * - HTML string * - `Function` to be executed when the step is built. It must return HTML string. * ``` * @param {object} options.when You can define `show`, `hide`, etc events inside `when`. For example: * ```js * when: { * show: function() { * window.scrollTo(0, 0); * } * } * ``` * @return {Step} The newly created Step instance */ constructor(tour, options = {}) { super(tour, options); this.tour = tour; this.classPrefix = this.tour.options ? normalizePrefix(this.tour.options.classPrefix) : ''; this.styles = tour.styles; /** * Resolved attachTo options. Due to lazy evaluation, we only resolve the options during `before-show` phase. * Do not use this directly, use the _getResolvedAttachToOptions method instead. * @type {null|{}|{element, to}} * @private */ this._resolvedAttachTo = null; autoBind(this); this._setOptions(options); return this; } /** * Cancel the tour * Triggers the `cancel` event */ cancel() { this.tour.cancel(); this.trigger('cancel'); } /** * Complete the tour * Triggers the `complete` event */ complete() { this.tour.complete(); this.trigger('complete'); } /** * Remove the step, delete the step's element, and destroy the FloatingUI instance for the step. * Triggers `destroy` event */ destroy() { destroyTooltip(this); if (isHTMLElement$1(this.el)) { this.el.remove(); this.el = null; } this._updateStepTargetOnHide(); this.trigger('destroy'); } /** * Returns the tour for the step * @return {Tour} The tour instance */ getTour() { return this.tour; } /** * Hide the step */ hide() { this.tour.modal.hide(); this.trigger('before-hide'); if (this.el) { this.el.hidden = true; } this._updateStepTargetOnHide(); this.trigger('hide'); } /** * Resolves attachTo options. * @returns {{}|{element, on}} * @private */ _resolveAttachToOptions() { this._resolvedAttachTo = parseAttachTo(this); return this._resolvedAttachTo; } /** * A selector for resolved attachTo options. * @returns {{}|{element, on}} * @private */ _getResolvedAttachToOptions() { if (this._resolvedAttachTo === null) { return this._resolveAttachToOptions(); } return this._resolvedAttachTo; } /** * Check if the step is open and visible * @return {boolean} True if the step is open and visible */ isOpen() { return Boolean(this.el && !this.el.hidden); } /** * Wraps `_show` and ensures `beforeShowPromise` resolves before calling show * @return {*|Promise} */ show() { if (isFunction(this.options.beforeShowPromise)) { return Promise.resolve(this.options.beforeShowPromise()).then(() => this._show()); } return Promise.resolve(this._show()); } /** * Updates the options of the step. * * @param {Object} options The options for the step */ updateStepOptions(options) { Object.assign(this.options, options); if (this.shepherdElementComponent) { this.shepherdElementComponent.$set({ step: this }); } } /** * Returns the element for the step * @return {HTMLElement|null|undefined} The element instance. undefined if it has never been shown, null if it has been destroyed */ getElement() { return this.el; } /** * Returns the target for the step * @return {HTMLElement|null|undefined} The element instance. undefined if it has never been shown, null if query string has not been found */ getTarget() { return this.target; } /** * Creates Shepherd element for step based on options * * @return {Element} The DOM element for the step tooltip * @private */ _createTooltipContent() { const descriptionId = `${this.id}-description`; const labelId = `${this.id}-label`; this.shepherdElementComponent = new Shepherd_element({ target: this.tour.options.stepsContainer || document.body, props: { classPrefix: this.classPrefix, descriptionId, labelId, step: this, styles: this.styles } }); return this.shepherdElementComponent.getElement(); } /** * If a custom scrollToHandler is defined, call that, otherwise do the generic * scrollIntoView call. * * @param {boolean|Object} scrollToOptions If true, uses the default `scrollIntoView`, * if an object, passes that object as the params to `scrollIntoView` i.e. `{ behavior: 'smooth', block: 'center' }` * @private */ _scrollTo(scrollToOptions) { const { element } = this._getResolvedAttachToOptions(); if (isFunction(this.options.scrollToHandler)) { this.options.scrollToHandler(element); } else if (isElement$1(element) && typeof element.scrollIntoView === 'function') { element.scrollIntoView(scrollToOptions); } } /** * _getClassOptions gets all possible classes for the step * @param {Object} stepOptions The step specific options * @returns {String} unique string from array of classes * @private */ _getClassOptions(stepOptions) { const defaultStepOptions = this.tour && this.tour.options && this.tour.options.defaultStepOptions; const stepClasses = stepOptions.classes ? stepOptions.classes : ''; const defaultStepOptionsClasses = defaultStepOptions && defaultStepOptions.classes ? defaultStepOptions.classes : ''; const allClasses = [...stepClasses.split(' '), ...defaultStepOptionsClasses.split(' ')]; const uniqClasses = new Set(allClasses); return Array.from(uniqClasses).join(' ').trim(); } /** * Sets the options for the step, maps `when` to events, sets up buttons * @param {Object} options The options for the step * @private */ _setOptions(options = {}) { let tourOptions = this.tour && this.tour.options && this.tour.options.defaultStepOptions; tourOptions = cjs({}, tourOptions || {}); this.options = Object.assign({ arrow: true }, tourOptions, options, mergeTooltipConfig(tourOptions, options)); const { when } = this.options; this.options.classes = this._getClassOptions(options); this.destroy(); this.id = this.options.id || `step-${uuid()}`; if (when) { Object.keys(when).forEach(event => { this.on(event, when[event], this); }); } } /** * Create the element and set up the FloatingUI instance * @private */ _setupElements() { if (!isUndefined(this.el)) { this.destroy(); } this.el = this._createTooltipContent(); if (this.options.advanceOn) { bindAdvance(this); } // The tooltip implementation details are handled outside of the Step // object. setupTooltip(this); } /** * Triggers `before-show`, generates the tooltip DOM content, * sets up a FloatingUI instance for the tooltip, then triggers `show`. * @private */ _show() { this.trigger('before-show'); // Force resolve to make sure the options are updated on subsequent shows. this._resolveAttachToOptions(); this._setupElements(); if (!this.tour.modal) { this.tour._setupModal(); } this.tour.modal.setupForStep(this); this._styleTargetElementForStep(this); this.el.hidden = false; // start scrolling to target before showing the step if (this.options.scrollTo) { setTimeout(() => { this._scrollTo(this.options.scrollTo); }); } this.el.hidden = false; const content = this.shepherdElementComponent.getElement(); const target = this.target || document.body; target.classList.add(`${this.classPrefix}shepherd-enabled`); target.classList.add(`${this.classPrefix}shepherd-target`); content.classList.add('shepherd-enabled'); this.trigger('show'); } /** * Modulates the styles of the passed step's target element, based on the step's options and * the tour's `modal` option, to visually emphasize the element * * @param step The step object that attaches to the element * @private */ _styleTargetElementForStep(step) { const targetElement = step.target; if (!targetElement) { return; } if (step.options.highlightClass) { targetElement.classList.add(step.options.highlightClass); } targetElement.classList.remove('shepherd-target-click-disabled'); if (step.options.canClickTarget === false) { targetElement.classList.add('shepherd-target-click-disabled'); } } /** * When a step is hidden, remove the highlightClass and 'shepherd-enabled' * and 'shepherd-target' classes * @private */ _updateStepTargetOnHide() { const target = this.target || document.body; if (this.options.highlightClass) { target.classList.remove(this.options.highlightClass); } target.classList.remove('shepherd-target-click-disabled', `${this.classPrefix}shepherd-enabled`, `${this.classPrefix}shepherd-target`); } } /** * Cleanup the steps and set pointerEvents back to 'auto' * @param tour The tour object */ function cleanupSteps(tour) { if (tour) { const { steps } = tour; steps.forEach(step => { if (step.options && step.options.canClickTarget === false && step.options.attachTo) { if (step.target instanceof HTMLElement) { step.target.classList.remove('shepherd-target-click-disabled'); } } }); } } /** * Generates the svg path data for a rounded rectangle overlay * @param {Object} dimension - Dimensions of rectangle. * @param {number} width - Width. * @param {number} height - Height. * @param {number} [x=0] - Offset from top left corner in x axis. default 0. * @param {number} [y=0] - Offset from top left corner in y axis. default 0. * @param {number | { topLeft: number, topRight: number, bottomRight: number, bottomLeft: number }} [r=0] - Corner Radius. Keep this smaller than half of width or height. * @returns {string} - Rounded rectangle overlay path data. */ function makeOverlayPath({ width, height, x = 0, y = 0, r = 0 }) { const { innerWidth: w, innerHeight: h } = window; const { topLeft = 0, topRight = 0, bottomRight = 0, bottomLeft = 0 } = typeof r === 'number' ? { topLeft: r, topRight: r, bottomRight: r, bottomLeft: r } : r; return `M${w},${h}\ H0\ V0\ H${w}\ V${h}\ Z\ M${x + topLeft},${y}\ a${topLeft},${topLeft},0,0,0-${topLeft},${topLeft}\ V${height + y - bottomLeft}\ a${bottomLeft},${bottomLeft},0,0,0,${bottomLeft},${bottomLeft}\ H${width + x - bottomRight}\ a${bottomRight},${bottomRight},0,0,0,${bottomRight}-${bottomRight}\ V${y + topRight}\ a${topRight},${topRight},0,0,0-${topRight}-${topRight}\ Z`; } /* src/js/components/shepherd-modal.svelte generated by Svelte v3.59.2 */ function create_fragment(ctx) { let svg; let path; let svg_class_value; let mounted; let dispose; return { c() { svg = svg_element("svg"); path = svg_element("path"); attr(path, "d", /*pathDefinition*/ctx[2]); attr(svg, "class", svg_class_value = `${/*modalIsVisible*/ctx[1] ? 'shepherd-modal-is-visible' : ''} shepherd-modal-overlay-container`); }, m(target, anchor) { insert(target, svg, anchor); append(svg, path); /*svg_binding*/ ctx[11](svg); if (!mounted) { dispose = listen(svg, "touchmove", /*_preventModalOverlayTouch*/ctx[3]); mounted = true; } }, p(ctx, [dirty]) { if (dirty & /*pathDefinition*/4) { attr(path, "d", /*pathDefinition*/ctx[2]); } if (dirty & /*modalIsVisible*/2 && svg_class_value !== (svg_class_value = `${/*modalIsVisible*/ctx[1] ? 'shepherd-modal-is-visible' : ''} shepherd-modal-overlay-container`)) { attr(svg, "class", svg_class_value); } }, i: noop, o: noop, d(detaching) { if (detaching) detach(svg); /*svg_binding*/ ctx[11](null); mounted = false; dispose(); } }; } function _getScrollParent(element) { if (!element) { return null; } const isHtmlElement = element instanceof HTMLElement; const overflowY = isHtmlElement && window.getComputedStyle(element).overflowY; const isScrollable = overflowY !== 'hidden' && overflowY !== 'visible'; if (isScrollable && element.scrollHeight >= element.clientHeight) { return element; } return _getScrollParent(element.parentElement); } /** * Get the visible height of the target element relative to its scrollParent. * If there is no scroll parent, the height of the element is returned. * * @param {HTMLElement} element The target element * @param {HTMLElement} [scrollParent] The scrollable parent element * @returns {{y: number, height: number}} * @private */ function _getVisibleHeight(element, scrollParent) { const elementRect = element.getBoundingClientRect(); let top = elementRect.y || elementRect.top; let bottom = elementRect.bottom || top + elementRect.height; if (scrollParent) { const scrollRect = scrollParent.getBoundingClientRect(); const scrollTop = scrollRect.y || scrollRect.top; const scrollBottom = scrollRect.bottom || scrollTop + scrollRect.height; top = Math.max(top, scrollTop); bottom = Math.min(bottom, scrollBottom); } const height = Math.max(bottom - top, 0); // Default to 0 if height is negative return { y: top, height }; } function instance($$self, $$props, $$invalidate) { let { element, openingProperties } = $$props; uuid(); let modalIsVisible = false; let rafId = undefined; let pathDefinition; closeModalOpening(); const getElement = () => element; function closeModalOpening() { $$invalidate(4, openingProperties = { width: 0, height: 0, x: 0, y: 0, r: 0 }); } function hide() { $$invalidate(1, modalIsVisible = false); // Ensure we cleanup all event listeners when we hide the modal _cleanupStepEventListeners(); } function positionModal(modalOverlayOpeningPadding = 0, modalOverlayOpeningRadius = 0, scrollParent, targetElement) { if (targetElement) { const { y, height } = _getVisibleHeight(targetElement, scrollParent); const { x, width, left } = targetElement.getBoundingClientRect(); // getBoundingClientRect is not consistent. Some browsers use x and y, while others use left and top $$invalidate(4, openingProperties = { width: width + modalOverlayOpeningPadding * 2, height: height + modalOverlayOpeningPadding * 2, x: (x || left) - modalOverlayOpeningPadding, y: y - modalOverlayOpeningPadding, r: modalOverlayOpeningRadius }); } else { closeModalOpening(); } } function setupForStep(step) { // Ensure we move listeners from the previous step, before we setup new ones _cleanupStepEventListeners(); if (step.tour.options.useModalOverlay) { _styleForStep(step); show(); } else { hide(); } } function show() { $$invalidate(1, modalIsVisible = true); } const _preventModalBodyTouch = e => { e.preventDefault(); }; const _preventModalOverlayTouch = e => { e.stopPropagation(); }; /** * Add touchmove event listener * @private */ function _addStepEventListeners() { // Prevents window from moving on touch. window.addEventListener('touchmove', _preventModalBodyTouch, { passive: false }); } /** * Cancel the requestAnimationFrame loop and remove touchmove event listeners * @private */ function _cleanupStepEventListeners() { if (rafId) { cancelAnimationFrame(rafId); rafId = undefined; } window.removeEventListener('touchmove', _preventModalBodyTouch, { passive: false }); } /** * Style the modal for the step * @param {Step} step The step to style the opening for * @private */ function _styleForStep(step) { const { modalOverlayOpeningPadding, modalOverlayOpeningRadius } = step.options; const scrollParent = _getScrollParent(step.target); // Setup recursive function to call requestAnimationFrame to update the modal opening position const rafLoop = () => { rafId = undefined; positionModal(modalOverlayOpeningPadding, modalOverlayOpeningRadius, scrollParent, step.target); rafId = requestAnimationFrame(rafLoop); }; rafLoop(); _addStepEventListeners(); } function svg_binding($$value) { binding_callbacks[$$value ? 'unshift' : 'push'](() => { element = $$value; $$invalidate(0, element); }); } $$self.$$set = $$props => { if ('element' in $$props) $$invalidate(0, element = $$props.element); if ('openingProperties' in $$props) $$invalidate(4, openingProperties = $$props.openingProperties); }; $$self.$$.update = () => { if ($$self.$$.dirty & /*openingProperties*/16) { $$invalidate(2, pathDefinition = makeOverlayPath(openingProperties)); } }; return [element, modalIsVisible, pathDefinition, _preventModalOverlayTouch, openingProperties, getElement, closeModalOpening, hide, positionModal, setupForStep, show, svg_binding]; } class Shepherd_modal extends SvelteComponent { constructor(options) { super(); init(this, options, instance, create_fragment, safe_not_equal, { element: 0, openingProperties: 4, getElement: 5, closeModalOpening: 6, hide: 7, positionModal: 8, setupForStep: 9, show: 10 }); } get getElement() { return this.$$.ctx[5]; } get closeModalOpening() { return this.$$.ctx[6]; } get hide() { return this.$$.ctx[7]; } get positionModal() { return this.$$.ctx[8]; } get setupForStep() { return this.$$.ctx[9]; } get show() { return this.$$.ctx[10]; } } const Shepherd = new Evented(); /** * Class representing the site tour * @extends {Evented} */ class Tour extends Evented { /** * @param {Object} options The options for the tour * @param {boolean | function(): boolean | Promise<boolean> | function(): Promise<boolean>} options.confirmCancel If true, will issue a `window.confirm` before cancelling. * If it is a function(support Async Function), it will be called and wait for the return value, and will only be cancelled if the value returned is true * @param {string} options.confirmCancelMessage The message to display in the `window.confirm` dialog * @param {string} options.classPrefix The prefix to add to the `shepherd-enabled` and `shepherd-target` class names as well as the `data-shepherd-step-id`. * @param {Object} options.defaultStepOptions Default options for Steps ({@link Step#constructor}), created through `addStep` * @param {boolean} options.exitOnEsc Exiting the tour with the escape key will be enabled unless this is explicitly * set to false. * @param {boolean} options.keyboardNavigation Navigating the tour via left and right arrow keys will be enabled * unless this is explicitly set to false. * @param {HTMLElement} options.stepsContainer An optional container element for the steps. * If not set, the steps will be appended to `document.body`. * @param {HTMLElement} options.modalContainer An optional container element for the modal. * If not set, the modal will be appended to `document.body`. * @param {object[] | Step[]} options.steps An array of step options objects or Step instances to initialize the tour with * @param {string} options.tourName An optional "name" for the tour. This will be appended to the the tour's * dynamically generated `id` property. * @param {boolean} options.useModalOverlay Whether or not steps should be placed above a darkened * modal overlay. If true, the overlay will create an opening around the target element so that it * can remain interactive * @returns {Tour} */ constructor(options = {}) { super(options); autoBind(this); const defaultTourOptions = { exitOnEsc: true, keyboardNavigation: true }; this.options = Object.assign({}, defaultTourOptions, options); this.classPrefix = normalizePrefix(this.options.classPrefix); this.steps = []; this.addSteps(this.options.steps); // Pass these events onto the global Shepherd object const events = ['active', 'cancel', 'complete', 'inactive', 'show', 'start']; events.map(event => { (e => { this.on(e, opts => { opts = opts || {}; opts.tour = this; Shepherd.trigger(e, opts); }); })(event); }); this._setTourID(); return this; } /** * Adds a new step to the tour * @param {Object|Step} options An object containing step options or a Step instance * @param {number} index The optional index to insert the step at. If undefined, the step * is added to the end of the array. * @return {Step} The newly added step */ addStep(options, index) { let step = options; if (!(step instanceof Step)) { step = new Step(this, step); } else { step.tour = this; } if (!isUndefined(index)) { this.steps.splice(index, 0, step); } else { this.steps.push(step); } return step; } /** * Add multiple steps to the tour * @param {Array<object> | Array<Step>} steps The steps to add to the tour */ addSteps(steps) { if (Array.isArray(steps)) { steps.forEach(step => { this.addStep(step); }); } return this; } /** * Go to the previous step in the tour */ back() { const index = this.steps.indexOf(this.currentStep); this.show(index - 1, false); } /** * Calls _done() triggering the 'cancel' event * If `confirmCancel` is true, will show a window.confirm before cancelling * If `confirmCancel` is a function, will call it and wait for the return value, * and only cancel when the value returned is true */ async cancel() { if (this.options.confirmCancel) { const confirmCancelIsFunction = typeof this.options.confirmCancel === 'function'; const cancelMessage = this.options.confirmCancelMessage || 'Are you sure you want to stop the tour?'; const stopTour = confirmCancelIsFunction ? await this.options.confirmCancel() : window.confirm(cancelMessage); if (stopTour) { this._done('cancel'); } } else { this._done('cancel'); } } /** * Calls _done() triggering the `complete` event */ complete() { this._done('complete'); } /** * Gets the step from a given id * @param {Number|String} id The id of the step to retrieve * @return {Step} The step corresponding to the `id` */ getById(id) { return this.steps.find(step => { return step.id === id; }); } /** * Gets the current step * @returns {Step|null} */ getCurrentStep() { return this.currentStep; } /** * Hide the current step */ hide() { const currentStep = this.getCurrentStep(); if (currentStep) { return currentStep.hide(); } } /** * Check if the tour is active * @return {boolean} */ isActive() { return Shepherd.activeTour === this; } /** * Go to the next step in the tour * If we are at the end, call `complete` */ next() { const index = this.steps.indexOf(this.currentStep); if (index === this.steps.length - 1) { this.complete(); } else { this.show(index + 1, true); } } /** * Removes the step from the tour * @param {String} name The id for the step to remove */ removeStep(name) { const current = this.getCurrentStep(); // Find the step, destroy it and remove it from this.steps this.steps.some((step, i) => { if (step.id === name) { if (step.isOpen()) { step.hide(); } step.destroy(); this.steps.splice(i, 1); return true; } }); if (current && current.id === name) { this.currentStep = undefined; // If we have steps left, show the first one, otherwise just cancel the tour this.steps.length ? this.show(0) : this.cancel(); } } /** * Show a specific step in the tour * @param {Number|String} key The key to look up the step by * @param {Boolean} forward True if we are going forward, false if backward */ show(key = 0, forward = true) { const step = isString(key) ? this.getById(key) : this.steps[key]; if (step) { this._updateStateBeforeShow(); const shouldSkipStep = isFunction(step.options.showOn) && !step.options.showOn(); // If `showOn` returns false, we want to skip the step, otherwise, show the step like normal if (shouldSkipStep) { this._skipStep(step, forward); } else { this.trigger('show', { step, previous: this.currentStep }); this.currentStep = step; step.show(); } } } /** * Start the tour */ start() { this.trigger('start'); // Save the focused element before the tour opens this.focusedElBeforeOpen = document.activeElement; this.currentStep = null; this._setupModal(); this._setupActiveTour(); this.next(); } /** * Called whenever the tour is cancelled or completed, basically anytime we exit the tour * @param {String} event The event name to trigger * @private */ _done(event) { const index = this.steps.indexOf(this.currentStep); if (Array.isArray(this.steps)) { this.steps.forEach(step => step.destroy()); } cleanupSteps(this); this.trigger(event, { index }); Shepherd.activeTour = null; this.trigger('inactive', { tour: this }); if (this.modal) { this.modal.hide(); } if (event === 'cancel' || event === 'complete') { if (this.modal) { const modalContainer = document.querySelector('.shepherd-modal-overlay-container'); if (modalContainer) { modalContainer.remove(); } } } // Focus the element that was focused before the tour started if (isHTMLElement$1(this.focusedElBeforeOpen)) { this.focusedElBeforeOpen.focus(); } } /** * Make this tour "active" * @private */ _setupActiveTour() { this.trigger('active', { tour: this }); Shepherd.activeTour = this; } /** * _setupModal create the modal container and instance * @private */ _setupModal() { this.modal = new Shepherd_modal({ target: this.options.modalContainer || document.body, props: { classPrefix: this.classPrefix, styles: this.styles } }); } /** * Called when `showOn` evaluates to false, to skip the step or complete the tour if it's the last step * @param {Step} step The step to skip * @param {Boolean} forward True if we are going forward, false if backward * @private */ _skipStep(step, forward) { const index = this.steps.indexOf(step); if (index === this.steps.length - 1) { this.complete(); } else { const nextIndex = forward ? index + 1 : index - 1; this.show(nextIndex, forward); } } /** * Before showing, hide the current step and if the tour is not * already active, call `this._setupActiveTour`. * @private */ _updateStateBeforeShow() { if (this.currentStep) { this.currentStep.hide(); } if (!this.isActive()) { this._setupActiveTour(); } } /** * Sets this.id to `${tourName}--${uuid}` * @private */ _setTourID() { const tourName = this.options.tourName || 'tour'; this.id = `${tourName}--${uuid()}`; } } const isServerSide = typeof window === 'undefined'; class NoOp { constructor() {} } if (isServerSide) { Object.assign(Shepherd, { Tour: NoOp, Step: NoOp }); } else { Object.assign(Shepherd, { Tour, Step }); } /** * @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org> * @license GNU General Public License version 2 or later; see LICENSE.txt */ if (!Joomla) { throw new Error('Joomla API is not properly initialised'); } function emptyStorage() { sessionStorage.removeItem('currentStepId'); sessionStorage.removeItem('stepCount'); sessionStorage.removeItem('tourId'); sessionStorage.removeItem('tourToken'); sessionStorage.removeItem('previousStepUrl'); } function getTourInstance() { const tour = new Shepherd.Tour({ defaultStepOptions: { cancelIcon: { enabled: true, label: Joomla.Text._('JCANCEL') }, classes: 'shepherd-theme-arrows', scrollTo: { behavior: 'smooth', block: 'center' } }, useModalOverlay: true, keyboardNavigation: true }); tour.on('cancel', () => { emptyStorage(); tour.steps = []; }); return tour; } function addProgressIndicator(stepElement, index, total) { const header = stepElement.querySelector('.shepherd-header'); const progress = document.createElement('div'); progress.classList.add('shepherd-progress'); progress.setAttribute('role', 'status'); progress.setAttribute('aria-label', Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_STEP_NUMBER_OF').replace('{number}', index).replace('{total}', total)); const progressText = document.createElement('span'); progressText.setAttribute('aria-hidden', true); progressText.innerText = `${index}/${total}`; progress.appendChild(progressText); header.insertBefore(progress, stepElement.querySelector('.shepherd-cancel-icon')); } function setFocus(primaryButton, secondaryButton, cancelButton) { if (primaryButton && !primaryButton.disabled) { primaryButton.focus(); } else if (secondaryButton && !secondaryButton.disabled) { secondaryButton.focus(); } else { cancelButton.focus(); } } function addStepToTourButton(tour, stepObj, buttons) { const step = new Shepherd.Step(tour, { title: stepObj.title, text: stepObj.description, classes: 'shepherd-theme-arrows', buttons, id: stepObj.id, arrow: true, beforeShowPromise() { return new Promise(resolve => { // Set graceful fallbacks in case there is an issue with the target. // Possibility to use comma-separated selectors. if (tour.currentStep.options.attachTo.element) { const targets = tour.currentStep.options.attachTo.element.split(','); const position = tour.currentStep.options.attachTo.on; tour.currentStep.options.attachTo.element = ''; tour.currentStep.options.attachTo.on = 'center'; for (let i = 0; i < targets.length; i += 1) { const t = document.querySelector(targets[i]); if (t != null) { if (!t.disabled && !t.readonly && t.style.display !== 'none') { tour.currentStep.options.attachTo.element = targets[i]; tour.currentStep.options.attachTo.on = position; break; } } } } if (tour.currentStep.options.attachTo.type === 'redirect') { const stepUrl = Joomla.getOptions('system.paths').rootFull + tour.currentStep.options.attachTo.url; if (window.location.href !== stepUrl) { sessionStorage.setItem('currentStepId', tour.currentStep.id); sessionStorage.setItem('previousStepUrl', window.location.href); window.location.href = stepUrl; } else { resolve(); } } else { resolve(); } }).catch(() => { // Ignore }); }, when: { show() { const element = this.getElement(); const target = this.getTarget(); // Force the screen reader to only read the content of the popup after a refresh element.setAttribute('aria-live', 'assertive'); sessionStorage.setItem('currentStepId', this.id); addProgressIndicator(element, this.id + 1, sessionStorage.getItem('stepCount')); if (target && this.options.attachTo.type === 'interactive') { const cancelButton = element.querySelector('.shepherd-cancel-icon'); const primaryButton = element.querySelector('.shepherd-button-primary'); const secondaryButton = element.querySelector('.shepherd-button-secondary'); // The 'next' button should always be enabled if the target input field of type 'text' has a value if (target.tagName.toLowerCase() === 'input' && target.hasAttribute('required') && ['email', 'password', 'search', 'tel', 'text', 'url'].includes(target.type)) { if (target.value.trim().length) { primaryButton.removeAttribute('disabled'); primaryButton.classList.remove('disabled'); } else { primaryButton.setAttribute('disabled', 'disabled'); primaryButton.classList.add('disabled'); } } cancelButton.addEventListener('keydown', event => { if (event.key === 'Tab') { if (target.tagName.toLowerCase() === 'joomla-field-fancy-select') { target.querySelector('.choices').click(); target.querySelector('.choices input').focus(); } else if (target.parentElement.tagName.toLowerCase() === 'joomla-field-fancy-select') { target.click(); target.querySelector('input').focus(); } else { target.focus(); event.preventDefault(); } } }); if (target.tagName.toLowerCase() === 'iframe') { // Give blur to the content of the iframe, as iframes don't have blur events target.contentWindow.document.body.addEventListener('blur', event => { if (!sessionStorage.getItem('tourId')) { return; } setTimeout(() => { setFocus(primaryButton, secondaryButton, cancelButton); }, 1); event.preventDefault(); }); } else if (target.tagName.toLowerCase() === 'joomla-field-fancy-select') { target.querySelector('.choices input').addEventListener('blur', event => { if (!sessionStorage.getItem('tourId')) { return; } setFocus(primaryButton, secondaryButton, cancelButton); event.preventDefault(); }); } else if (target.parentElement.tagName.toLowerCase() === 'joomla-field-fancy-select') { target.querySelector('input').addEventListener('blur', event => { if (!sessionStorage.getItem('tourId')) { return; } setFocus(primaryButton, secondaryButton, cancelButton); event.preventDefault(); }); } else { target.addEventListener('blur', event => { if (!sessionStorage.getItem('tourId')) { return; } setFocus(primaryButton, secondaryButton, cancelButton); event.preventDefault(); }); } } } } }); if (stepObj.target) { step.updateStepOptions({ attachTo: { element: stepObj.target, on: stepObj.position, url: stepObj.url, type: stepObj.type, interactive_type: stepObj.interactive_type } }); } else { step.updateStepOptions({ attachTo: { url: stepObj.url, type: stepObj.type, interactive_type: stepObj.interactive_type } }); } if (stepObj.type !== 'next') { // Remove stored key to prevent pages to open in the wrong tab const storageKey = `${Joomla.getOptions('system.paths').root}/${stepObj.url}`; if (sessionStorage.getItem(storageKey)) { sessionStorage.removeItem(storageKey); } } tour.addStep(step); } function showTourInfo(tour, stepObj) { tour.addStep({ title: stepObj.title, text: stepObj.description, classes: 'shepherd-theme-arrows', buttons: [{ classes: 'btn btn-primary shepherd-button-primary', action() { return this.next(); }, text: Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_START') }], id: 'tourinfo', when: { show() { sessionStorage.setItem('currentStepId', 'tourinfo'); addProgressIndicator(this.getElement(), 1, sessionStorage.getItem('stepCount')); } } }); } function pushCompleteButton(buttons) { buttons.push({ text: Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_COMPLETE'), classes: 'btn btn-primary shepherd-button-primary', action() { return this.cancel(); } }); } function pushNextButton(buttons, step, disabled = false, disabledClass = '') { buttons.push({ text: Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_NEXT'), classes: `btn btn-primary shepherd-button-primary step-next-button-${step.id} ${disabledClass}`, action() { return this.next(); }, disabled }); } function addBackButton(buttons, step) { buttons.push({ text: Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_BACK'), classes: 'btn btn-secondary shepherd-button-secondary', action() { if (step.type === 'redirect') { sessionStorage.setItem('currentStepId', step.id - 1); const previousStepUrl = sessionStorage.getItem('previousStepUrl'); if (previousStepUrl) { sessionStorage.removeItem('previousStepUrl'); window.location.href = previousStepUrl; } } return this.back(); } }); } function enableButton(event) { const element = document.querySelector(`.step-next-button-${event.currentTarget.step_id}`); element.removeAttribute('disabled'); element.classList.remove('disabled'); } function disableButton(event) { const element = document.querySelector(`.step-next-button-${event.currentTarget.step_id}`); element.setAttribute('disabled', 'disabled'); element.classList.add('disabled'); } function startTour(obj) { // We store the tour id to restart on site refresh sessionStorage.setItem('tourId', obj.id); sessionStorage.setItem('stepCount', String(obj.steps.length)); // Try to continue const currentStepId = sessionStorage.getItem('currentStepId'); let prevStep = null; let ind = -1; if (currentStepId != null && Number(currentStepId) > -1) { ind = typeof obj.steps[currentStepId] !== 'undefined' ? Number(currentStepId) : -1; // When we have more than one step, we save the previous step if (ind > 0) { prevStep = obj.steps[ind - 1]; } } // Start tour building const tour = getTourInstance(); // No step found, let's start from the beginning if (ind < 0) { // First check for redirect const uri = Joomla.getOptions('system.paths').rootFull; const currentUrl = window.location.href; if (currentUrl !== uri + obj.steps[0].url) { window.location.href = uri + obj.steps[0].url; return; } // Show info showTourInfo(tour, obj.steps[0]); ind = 1; } // Now let's add all follow up steps const len = obj.steps.length; let buttons; for (let index = ind; index < len; index += 1) { buttons = []; // If we have at least done one step, let's allow a back step // - if after the start step // - if not the first step after a form redirect // - if after a simple redirect if (prevStep === null || index > ind || obj.steps[index].type === 'redirect') { addBackButton(buttons, obj.steps[index]); } if (obj && obj.steps[index].target && obj.steps[index].type === 'interactive') { const ele = document.querySelector(obj.steps[index].target); if (ele) { if (obj && obj.steps && obj.steps[index] && obj.steps[index].interactive_type) { switch (obj.steps[index].interactive_type) { case 'submit': ele.addEventListener('click', () => { if (!sessionStorage.getItem('tourId')) { return; } sessionStorage.setItem('currentStepId', obj.steps[index].id + 1); }); break; case 'text': ele.step_id = index; if (ele.hasAttribute('required') && ['email', 'password', 'search', 'tel', 'text', 'url'].includes(ele.type)) { ['input', 'focus'].forEach(eventName => ele.addEventListener(eventName, event => { if (!sessionStorage.getItem('tourId')) { return; } if (event.target.value.trim().length) { enableButton(event); } else { disableButton(event); } })); } break; case 'button': ele.addEventListener('click', () => { // the button may submit a form so record the currentStepId in the session storage sessionStorage.setItem('currentStepId', obj.steps[index].id + 1); tour.next(); }); break; } } } } if (index < len - 1) { if (obj && obj.steps[index].type !== 'interactive' || obj && obj.steps[index].interactive_type === 'text' || obj && obj.steps[index].interactive_type === 'other') { pushNextButton(buttons, obj.steps[index]); } } else { pushCompleteButton(buttons); } addStepToTourButton(tour, obj.steps[index], buttons); prevStep = obj.steps[index]; } tour.start(); } function loadTour(tourId) { if (tourId > 0) { const url = `${Joomla.getOptions('system.paths').rootFull}administrator/index.php?option=com_ajax&plugin=guidedtours&group=system&format=json&id=${tourId}`; fetch(url).then(response => response.json()).then(result => { if (!result.success) { if (result.messages) { Joomla.renderMessages(result.messages); } // Kill all tours if we can't find it emptyStorage(); } startTour(result.data); }).catch(error => { // Kill the tour if there is a problem with selector validation emptyStorage(); const messages = { error: [Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_TOUR_ERROR')] }; Joomla.renderMessages(messages); throw new Error(error); }); } } // Opt-in Start buttons document.querySelector('body').addEventListener('click', event => { // Click somewhere else if (!event.target || !event.target.classList.contains('button-start-guidedtour')) { return; } // Click button but missing data-id if (typeof event.target.getAttribute('data-id') === 'undefined' || event.target.getAttribute('data-id') <= 0) { Joomla.renderMessages({ error: [Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_COULD_NOT_LOAD_THE_TOUR')] }); return; } sessionStorage.setItem('tourToken', String(Joomla.getOptions('com_guidedtours.token'))); loadTour(event.target.getAttribute('data-id')); }); // Start a given tour const tourId = sessionStorage.getItem('tourId'); if (tourId > 0 && sessionStorage.getItem('tourToken') === String(Joomla.getOptions('com_guidedtours.token'))) { loadTour(tourId); } else { emptyStorage(); }