UNPKG

@ditdot-dev/vue-flow-form

Version:

Create conversational conditional-logic forms with Vue.js.

1,705 lines (1,453 loc) 117 kB
import { openBlock, createElementBlock, createElementVNode, createCommentVNode, Fragment, renderList, toDisplayString, normalizeClass, resolveDirective, withDirectives, resolveComponent, createBlock, withKeys, withModifiers, normalizeStyle, vModelText, createVNode, vModelRadio, vModelCheckbox, createTextVNode, resolveDynamicComponent, renderSlot } from 'vue'; /*! Copyright (c) 2020 - present, DITDOT Ltd. - MIT Licence https://github.com/ditdot-dev/vue-flow-form https://www.ditdot.hr/en */ // Language data store var LanguageModel = function LanguageModel(options) { this.enterKey = 'Enter'; this.shiftKey = 'Shift'; this.ok = 'OK'; this.continue = 'Continue'; this.skip = 'Skip'; this.pressEnter = 'Press :enterKey'; this.multipleChoiceHelpText = 'Choose as many as you like'; this.multipleChoiceHelpTextSingle = 'Choose only one answer'; this.otherPrompt = 'Other'; this.placeholder = 'Type your answer here...'; this.submitText = 'Submit'; this.longTextHelpText = ':shiftKey + :enterKey to make a line break.'; this.prev = 'Prev'; this.next = 'Next'; this.percentCompleted = ':percent% completed'; this.invalidPrompt = 'Please fill out the field correctly'; this.thankYouText = 'Thank you!'; this.successText = 'Your submission has been sent.'; this.ariaOk = 'Press to continue'; this.ariaRequired = 'This step is required'; this.ariaPrev = 'Previous step'; this.ariaNext = 'Next step'; this.ariaSubmitText = 'Press to submit'; this.ariaMultipleChoice = 'Press :letter to select'; this.ariaTypeAnswer = 'Type your answer here'; this.errorAllowedFileTypes = 'Invalid file type. Allowed file types: :fileTypes.'; this.errorMaxFileSize = 'File(s) too large. Maximum allowed file size: :size.'; this.errorMinFiles = 'Too few files added. Minimum allowed files: :min.'; this.errorMaxFiles = 'Too many files added. Maximum allowed files: :max.'; Object.assign(this, options || {}); }; /** * Inserts a new CSS class into the language model string to format the :string * Use it in a component's v-html directive: v-html="language.formatString(language.languageString)" */ LanguageModel.prototype.formatString = function formatString (string, replacements) { var this$1$1 = this; return string.replace(/:(\w+)/g, function (match, word) { if (this$1$1[word]) { return '<span class="f-string-em">' + this$1$1[word] + '</span>' } else if (replacements && replacements[word]) { return replacements[word] } return match }) }; LanguageModel.prototype.formatFileSize = function formatFileSize (bytes) { var units = ['B', 'kB', 'MB', 'GB', 'TB'], i = bytes > 0 ? Math.floor(Math.log(bytes) / Math.log(1024)) : 0; return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + units[i]; }; /* Copyright (c) 2020 - present, DITDOT Ltd. - MIT Licence https://github.com/ditdot-dev/vue-flow-form https://www.ditdot.hr/en */ // Global data store var QuestionType = Object.freeze({ Date: 'FlowFormDateType', Dropdown: 'FlowFormDropdownType', Email: 'FlowFormEmailType', File: 'FlowFormFileType', LongText: 'FlowFormLongTextType', MultipleChoice: 'FlowFormMultipleChoiceType', MultiplePictureChoice: 'FlowFormMultiplePictureChoiceType', Number: 'FlowFormNumberType', Password: 'FlowFormPasswordType', Phone: 'FlowFormPhoneType', SectionBreak: 'FlowFormSectionBreakType', Text: 'FlowFormTextType', Url: 'FlowFormUrlType', Matrix: 'FlowFormMatrixType', OpinionScale: 'FlowFormOpinionScaleType', IconRate: 'FlowFormIconRateType', }); Object.freeze({ label: '', value: '', disabled: true }); var MaskPresets = Object.freeze({ Date: '##/##/####', DateIso: '####-##-##', PhoneUs: '(###) ###-####' }); var ChoiceOption = function ChoiceOption(options) { this.label = ''; this.value = null; this.selected = false; this.imageSrc = null; this.imageAlt = null; Object.assign(this, options); }; ChoiceOption.prototype.choiceLabel = function choiceLabel () { return this.label || this.value }; ChoiceOption.prototype.choiceValue = function choiceValue () { // Returns the value if it's anything other than the default (null). if (this.value !== null) { return this.value } // Returns any other non-empty property if the value has not been set. return this.label || this.imageAlt || this.imageSrc }; ChoiceOption.prototype.toggle = function toggle () { this.selected = !this.selected; }; var LinkOption = function LinkOption(options) { this.url = ''; this.text = ''; this.target = '_blank'; Object.assign(this, options); }; var MatrixColumn = function MatrixColumn(options) { this.value = ''; this.label = ''; Object.assign(this, options); }; var MatrixRow = function MatrixRow(options) { this.id = ''; this.label = ''; Object.assign(this, options); }; var QuestionModel = function QuestionModel(options) { // Make sure the options variable is an object options = options || {}; this.id = null; this.answer = null; this.answered = false; this.index = 0; this.options = []; this.description = ''; this.className = ''; this.type = null; this.html = null; this.required = false; this.jump = null; this.placeholder = null; this.mask = ''; this.multiple = false; this.allowOther = false; this.other = null; this.language = null; this.tagline = null; this.title = null; this.subtitle = null; this.content = null; this.inline = false; this.helpText = null; this.helpTextShow = true; this.descriptionLink = []; this.min = null; this.max = null; this.maxLength = null; this.nextStepOnAnswer = false; this.accept = null; this.maxSize = null; this.rows = []; this.columns = []; this.labelLeft = null; this.labelRight = null; Object.assign(this, options); // Sets default mask and placeholder value on PhoneType question if (this.type === QuestionType.Phone) { if (!this.mask) { this.mask = MaskPresets.Phone; } if (!this.placeholder) { this.placeholder = this.mask; } } if (this.type === QuestionType.Url) { this.mask = null; } if (this.type === QuestionType.Date && !this.placeholder) { this.placeholder = 'yyyy-mm-dd'; } if (this.type !== QuestionType.Matrix && this.multiple && !Array.isArray(this.answer)) { this.answer = this.answer ? [this.answer] : []; } // Check if we have an answer already (when we have a pre-filled form) // and set the answered property accordingly if (!this.required && typeof options.answer !== 'undefined') { this.answered = true; } else if (this.answer && (!this.multiple || this.answer.length)) { this.answered = true; } this.resetOptions(); }; QuestionModel.prototype.getJumpId = function getJumpId () { var nextId = null; if (typeof this.jump === 'function') { nextId = this.jump.call(this); } else if (this.jump[this.answer]) { nextId = this.jump[this.answer]; } else if (this.jump['_other']) { nextId = this.jump['_other']; } return nextId }; QuestionModel.prototype.setAnswer = function setAnswer (answer) { if (this.type === QuestionType.Number && answer !== '' && !isNaN(+answer)) { answer = +answer; } this.answer = answer; }; QuestionModel.prototype.setIndex = function setIndex (index) { if (!this.id) { this.id = 'q_' + index; } this.index = index; }; QuestionModel.prototype.resetOptions = function resetOptions () { var this$1$1 = this; if (this.options) { var isArray = Array.isArray(this.answer); var numSelected = 0; this.options.forEach(function (o) { var optionValue = o.choiceValue(); if (this$1$1.answer === optionValue || (isArray && this$1$1.answer.indexOf(optionValue) !== -1)) { o.selected = true; ++numSelected; } else { o.selected = false; } }); if (this.allowOther) { var otherAnswer = null; if (isArray) { if (this.answer.length && this.answer.length !== numSelected) { otherAnswer = this.answer[this.answer.length - 1]; } } else if (this.options.map(function (o) { return o.choiceValue(); }).indexOf(this.answer) === -1) { otherAnswer = this.answer; } if (otherAnswer !== null) { this.other = otherAnswer; } } } }; QuestionModel.prototype.resetAnswer = function resetAnswer () { this.answered = false; this.answer = this.multiple ? [] : null; this.other = null; this.resetOptions(); }; QuestionModel.prototype.isMultipleChoiceType = function isMultipleChoiceType () { return [QuestionType.MultipleChoice, QuestionType.MultiplePictureChoice].includes(this.type) }; /* Copyright (c) 2020 - present, DITDOT Ltd. - MIT Licence https://github.com/ditdot-dev/vue-flow-form https://www.ditdot.hr/en */ var isIos = false, isMobile = false; if (typeof navigator !== 'undefined' && typeof document !== 'undefined') { // Simple mobile device/tablet detection isIos = navigator.userAgent.match(/iphone|ipad|ipod/i) || (navigator.userAgent.indexOf('Mac') !== -1 && 'ontouchend' in document); isMobile = isIos || navigator.userAgent.match(/android/i); } // Mixin that adds `isMobile` and `isIos` data variables var IsMobile = { data: function data() { return { isIos: isIos, isMobile: isMobile } } }; var script$l = { name: 'FlowFormBaseType', props: { language: LanguageModel, question: QuestionModel, active: Boolean, disabled: Boolean, modelValue: [String, Array, Boolean, Number, Object] }, mixins: [ IsMobile ], data: function data() { return { dirty: false, dataValue: null, answer: null, enterPressed: false, allowedChars: null, alwaysAllowedKeys: ['ArrowLeft', 'ArrowRight', 'Delete', 'Backspace'], focused: false, canReceiveFocus: false, errorMessage: null } }, mounted: function mounted() { if (this.question.answer) { this.dataValue = this.answer = this.question.answer; } else if (this.question.multiple) { this.dataValue = []; } }, methods: { /** * This method can be overriden in custom components to * change the answer before going through validation. */ fixAnswer: function fixAnswer(answer) { return answer }, getElement: function getElement() { var el = this.$refs.input; // Sometimes the input is nested so we need to find it while (el && el.$el) { el = el.$el; } return el }, setFocus: function setFocus() { this.focused = true; }, // eslint-disable-next-line no-unused-vars unsetFocus: function unsetFocus($event) { this.focused = false; }, focus: function focus() { if (!this.focused) { var el = this.getElement(); el && el.focus(); } }, blur: function blur() { var el = this.getElement(); el && el.blur(); }, onKeyDown: function onKeyDown($event) { this.enterPressed = false; clearTimeout(this.timeoutId); if ($event) { if ($event.key === 'Enter' && !$event.shiftKey) { this.unsetFocus(); } if (this.allowedChars !== null) { // Check if the entered character is allowed. // We always allow keys from the alwaysAllowedKeys array. if (this.alwaysAllowedKeys.indexOf($event.key) === -1 && this.allowedChars.indexOf($event.key) === -1) { $event.preventDefault(); } } } }, onChange: function onChange($event) { this.dirty = true; this.dataValue = $event.target.value; this.onKeyDown(); this.setAnswer(this.dataValue); }, onEnter: function onEnter() { this._onEnter(); }, _onEnter: function _onEnter() { this.enterPressed = true; this.dataValue = this.fixAnswer(this.dataValue); this.setAnswer(this.dataValue); this.isValid() ? this.blur() : this.focus(); }, setAnswer: function setAnswer(answer) { this.question.setAnswer(answer); this.answer = this.question.answer; this.question.answered = this.isValid(); this.$emit('update:modelValue', this.answer); }, showInvalid: function showInvalid() { return this.dirty && this.enterPressed && !this.isValid() }, isValid: function isValid() { if (!this.question.required && !this.hasValue && this.dirty) { return true } if (this.validate()) { return true } return false }, /** * This method validates the input and is meant to be overriden * in custom question types. */ validate: function validate() { return !this.question.required || this.hasValue } }, computed: { placeholder: function placeholder() { return this.question.placeholder || this.language.placeholder }, hasValue: function hasValue() { if (this.dataValue !== null) { var v = this.dataValue; if (v.trim) { // Don't allow empty strings return v.trim().length > 0 } if (Array.isArray(v)) { // Don't allow empty arrays return v.length > 0 } // All other non-null values are allowed to pass through return true } return false } } }; script$l.__file = "src/components/QuestionTypes/BaseType.vue"; var script$k = { extends: script$l, name: QuestionType.Dropdown, computed: { answerLabel: function answerLabel() { for (var i = 0; i < this.question.options.length; i++) { var option = this.question.options[i]; if (option.choiceValue() === this.dataValue) { return option.choiceLabel() } } return this.question.placeholder } }, methods: { onKeyDownListener: function onKeyDownListener($event) { if ($event.key === 'ArrowDown' || $event.key === 'ArrowUp') { this.setAnswer(this.dataValue); } else if ($event.key === 'Enter' && this.hasValue) { this.focused = false; this.blur(); } }, onKeyUpListener: function onKeyUpListener($event) { if ($event.key === 'Enter' && this.isValid() && !this.disabled) { $event.stopPropagation(); this._onEnter(); this.$emit('next'); } } } }; var _hoisted_1$9 = { class: "faux-form" }; var _hoisted_2$7 = ["value", "required"]; var _hoisted_3$5 = { key: 0, label: " ", value: "", disabled: "", selected: "", hidden: "" }; var _hoisted_4$5 = ["disabled", "value"]; var _hoisted_5$5 = /*#__PURE__*/createElementVNode("span", { class: "f-arrow-down" }, [ /*#__PURE__*/createElementVNode("svg", { version: "1.1", id: "Capa_1", xmlns: "http://www.w3.org/2000/svg", "xmlns:xlink": "http://www.w3.org/1999/xlink", x: "0px", y: "0px", viewBox: "-163 254.1 284.9 284.9", style: "", "xml:space": "preserve" }, [ /*#__PURE__*/createElementVNode("g", null, [ /*#__PURE__*/createElementVNode("path", { d: "M119.1,330.6l-14.3-14.3c-1.9-1.9-4.1-2.9-6.6-2.9c-2.5,0-4.7,1-6.6,2.9L-20.5,428.5l-112.2-112.2c-1.9-1.9-4.1-2.9-6.6-2.9c-2.5,0-4.7,0.9-6.6,2.9l-14.3,14.3c-1.9,1.9-2.9,4.1-2.9,6.6c0,2.5,1,4.7,2.9,6.6l133,133c1.9,1.9,4.1,2.9,6.6,2.9s4.7-1,6.6-2.9l133.1-133c1.9-1.9,2.8-4.1,2.8-6.6C121.9,334.7,121,332.5,119.1,330.6z" }) ]) ]) ], -1 /* HOISTED */); function render$b(_ctx, _cache, $props, $setup, $data, $options) { return (openBlock(), createElementBlock("span", _hoisted_1$9, [ createElementVNode("select", { ref: "input", class: "", value: _ctx.dataValue, onChange: _cache[0] || (_cache[0] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return (_ctx.onChange && _ctx.onChange.apply(_ctx, args)); }), onKeydown: _cache[1] || (_cache[1] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return ($options.onKeyDownListener && $options.onKeyDownListener.apply($options, args)); }), onKeyup: _cache[2] || (_cache[2] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return ($options.onKeyUpListener && $options.onKeyUpListener.apply($options, args)); }), required: _ctx.question.required }, [ (_ctx.question.required) ? (openBlock(), createElementBlock("option", _hoisted_3$5, " ")) : createCommentVNode("v-if", true), (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.question.options, function (option, index) { return (openBlock(), createElementBlock("option", { disabled: option.disabled, value: option.choiceValue(), key: 'o' + index }, toDisplayString(option.choiceLabel()), 9 /* TEXT, PROPS */, _hoisted_4$5)) }), 128 /* KEYED_FRAGMENT */)) ], 40 /* PROPS, HYDRATE_EVENTS */, _hoisted_2$7), createElementVNode("span", null, [ createElementVNode("span", { class: normalizeClass(["f-empty", {'f-answered': this.question.answer && this.question.answered}]) }, toDisplayString($options.answerLabel), 3 /* TEXT, CLASS */), _hoisted_5$5 ]) ])) } script$k.render = render$b; script$k.__file = "src/components/QuestionTypes/DropdownType.vue"; function maskit (value, mask, masked, tokens) { if ( masked === void 0 ) masked = true; value = value || ''; mask = mask || ''; var iMask = 0; var iValue = 0; var output = ''; while (iMask < mask.length && iValue < value.length) { var cMask = mask[iMask]; var masker = tokens[cMask]; var cValue = value[iValue]; if (masker && !masker.escape) { if (masker.pattern.test(cValue)) { output += masker.transform ? masker.transform(cValue) : cValue; iMask++; } iValue++; } else { if (masker && masker.escape) { iMask++; // take the next mask char and treat it as char cMask = mask[iMask]; } if (masked) { output += cMask; } if (cValue === cMask) { iValue++; } // user typed the same char iMask++; } } // fix mask that ends with a char: (#) var restOutput = ''; while (iMask < mask.length && masked) { var cMask = mask[iMask]; if (tokens[cMask]) { restOutput = ''; break } restOutput += cMask; iMask++; } return output + restOutput } function dynamicMask (maskit, masks, tokens) { masks = masks.sort(function (a, b) { return a.length - b.length; }); return function (value, mask, masked) { if ( masked === void 0 ) masked = true; var i = 0; while (i < masks.length) { var currentMask = masks[i]; i++; var nextMask = masks[i]; if (! (nextMask && maskit(value, nextMask, true, tokens).length > currentMask.length) ) { return maskit(value, currentMask, masked, tokens) } } return '' // empty masks } } // Facade to maskit/dynamicMask when mask is String or Array function masker (value, mask, masked, tokens) { if ( masked === void 0 ) masked = true; return Array.isArray(mask) ? dynamicMask(maskit, mask, tokens)(value, mask, masked, tokens) : maskit(value, mask, masked, tokens) } var tokens = { '#': {pattern: /\d/}, 'X': {pattern: /[0-9a-zA-Z]/}, 'S': {pattern: /[a-zA-Z]/}, 'A': {pattern: /[a-zA-Z]/, transform: function (v) { return v.toLocaleUpperCase(); }}, 'a': {pattern: /[a-zA-Z]/, transform: function (v) { return v.toLocaleLowerCase(); }}, '!': {escape: true} }; // https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events#The_old-fashioned_way function event (name) { var evt = document.createEvent('Event'); evt.initEvent(name, true, true); return evt } function mask (el, binding) { var config = binding.value; if (Array.isArray(config) || typeof config === 'string') { config = { mask: config, tokens: tokens }; } if (el.tagName.toLocaleUpperCase() !== 'INPUT') { var els = el.getElementsByTagName('input'); if (els.length !== 1) { throw new Error("v-mask directive requires 1 input, found " + els.length) } else { el = els[0]; } } el.oninput = function (evt) { if (!evt.isTrusted) { return } // avoid infinite loop /* other properties to try to diferentiate InputEvent of Event (custom) InputEvent (native) cancelable: false isTrusted: true composed: true isComposing: false which: 0 Event (custom) cancelable: true isTrusted: false */ // by default, keep cursor at same position as before the mask var position = el.selectionEnd; // save the character just inserted var digit = el.value[position-1]; el.value = masker(el.value, config.mask, true, config.tokens); // if the digit was changed, increment position until find the digit again while (position < el.value.length && el.value.charAt(position-1) !== digit) { position++; } if (el === document.activeElement) { el.setSelectionRange(position, position); setTimeout(function () { el.setSelectionRange(position, position); }, 0); } el.dispatchEvent(event('input')); }; var newDisplay = masker(el.value, config.mask, true, config.tokens); if (newDisplay !== el.value) { el.value = newDisplay; el.dispatchEvent(event('input')); } } var script$j = { name: 'TheMask', props: { value: [String, Number], mask: { type: [String, Array], required: true }, masked: { // by default emits the value unformatted, change to true to format with the mask type: Boolean, default: false // raw }, tokens: { type: Object, default: function () { return tokens; } } }, directives: {mask: mask}, data: function data () { return { lastValue: null, // avoid unecessary emit when has no change display: this.value } }, watch : { value: function value (newValue) { if (newValue !== this.lastValue) { this.display = newValue; } }, masked: function masked () { this.refresh(this.display); } }, computed: { config: function config () { return { mask: this.mask, tokens: this.tokens, masked: this.masked } } }, methods: { onInput: function onInput (e) { if (e.isTrusted) { return } // ignore native event this.refresh(e.target.value); }, refresh: function refresh (value) { this.display = value; var value = masker(value, this.mask, this.masked, this.tokens); if (value !== this.lastValue) { this.lastValue = value; this.$emit('input', value); } } } }; var _hoisted_1$8 = ["value"]; function render$a(_ctx, _cache, $props, $setup, $data, $options) { var _directive_mask = resolveDirective("mask"); return withDirectives((openBlock(), createElementBlock("input", { type: "text", value: $data.display, onInput: _cache[0] || (_cache[0] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return ($options.onInput && $options.onInput.apply($options, args)); }) }, null, 40 /* PROPS, HYDRATE_EVENTS */, _hoisted_1$8)), [ [_directive_mask, $options.config] ]) } script$j.render = render$a; script$j.__file = "node_modules/vue-the-mask/src/component.vue"; var script$i = { extends: script$l, name: QuestionType.Text, components: { TheMask: script$j }, data: function data() { return { inputType: 'text', canReceiveFocus: true } }, methods: { validate: function validate() { if (this.question.mask && this.hasValue) { return this.validateMask() } return !this.question.required || this.hasValue }, validateMask: function validateMask() { var this$1$1 = this; if (Array.isArray(this.question.mask)) { return this.question.mask.some(function (mask) { return mask.length === this$1$1.dataValue.length; }) } return this.dataValue.length === this.question.mask.length } } }; var _hoisted_1$7 = ["data-placeholder"]; var _hoisted_2$6 = ["type", "value", "required", "min", "max", "placeholder", "maxlength"]; function render$9(_ctx, _cache, $props, $setup, $data, $options) { var _component_the_mask = resolveComponent("the-mask"); return (openBlock(), createElementBlock("span", { "data-placeholder": $data.inputType === 'date' ? _ctx.placeholder : null }, [ (_ctx.question.mask) ? (openBlock(), createBlock(_component_the_mask, { key: 0, ref: "input", mask: _ctx.question.mask, masked: false, type: $data.inputType, value: _ctx.modelValue, required: _ctx.question.required, onKeydown: _ctx.onKeyDown, onKeyup: [ _ctx.onChange, withKeys(withModifiers(_ctx.onEnter, ["prevent"]), ["enter"]), withKeys(withModifiers(_ctx.onEnter, ["prevent"]), ["tab"]) ], onFocus: _ctx.setFocus, onBlur: _ctx.unsetFocus, placeholder: _ctx.placeholder, min: _ctx.question.min, max: _ctx.question.max, onChange: _ctx.onChange }, null, 8 /* PROPS */, ["mask", "type", "value", "required", "onKeydown", "onKeyup", "onFocus", "onBlur", "placeholder", "min", "max", "onChange"])) : (openBlock(), createElementBlock("input", { key: 1, ref: "input", type: $data.inputType, value: _ctx.modelValue, required: _ctx.question.required, onKeydown: _cache[0] || (_cache[0] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return (_ctx.onKeyDown && _ctx.onKeyDown.apply(_ctx, args)); }), onKeyup: [ _cache[1] || (_cache[1] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return (_ctx.onChange && _ctx.onChange.apply(_ctx, args)); }), _cache[2] || (_cache[2] = withKeys(withModifiers(function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return (_ctx.onEnter && _ctx.onEnter.apply(_ctx, args)); }, ["prevent"]), ["enter"])), _cache[3] || (_cache[3] = withKeys(withModifiers(function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return (_ctx.onEnter && _ctx.onEnter.apply(_ctx, args)); }, ["prevent"]), ["tab"])) ], onFocus: _cache[4] || (_cache[4] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return (_ctx.setFocus && _ctx.setFocus.apply(_ctx, args)); }), onBlur: _cache[5] || (_cache[5] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return (_ctx.unsetFocus && _ctx.unsetFocus.apply(_ctx, args)); }), min: _ctx.question.min, max: _ctx.question.max, onChange: _cache[6] || (_cache[6] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return (_ctx.onChange && _ctx.onChange.apply(_ctx, args)); }), placeholder: _ctx.placeholder, maxlength: _ctx.question.maxLength }, null, 40 /* PROPS, HYDRATE_EVENTS */, _hoisted_2$6)) ], 8 /* PROPS */, _hoisted_1$7)) } script$i.render = render$9; script$i.__file = "src/components/QuestionTypes/TextType.vue"; var script$h = { extends: script$i, name: QuestionType.Email, data: function data() { return { inputType: 'email' } }, methods: { validate: function validate() { if (this.hasValue) { return /^[^@]+@.+[^.]$/.test(this.dataValue) } return !this.question.required } } }; script$h.__file = "src/components/QuestionTypes/EmailType.vue"; var script$g = { name: 'TextareaAutosize', props: { value: { type: [String, Number], default: '' }, autosize: { type: Boolean, default: true }, minHeight: { type: [Number], 'default': null }, maxHeight: { type: [Number], 'default': null }, /* * Force !important for style properties */ important: { type: [Boolean, Array], default: false } }, data: function data () { return { // data property for v-model binding with real textarea tag val: null, // works when content height becomes more then value of the maxHeight property maxHeightScroll: false, height: 'auto' } }, computed: { computedStyles: function computedStyles () { if (!this.autosize) { return {} } return { resize: !this.isResizeImportant ? 'none' : 'none !important', height: this.height, overflow: this.maxHeightScroll ? 'auto' : (!this.isOverflowImportant ? 'hidden' : 'hidden !important') } }, isResizeImportant: function isResizeImportant () { var imp = this.important; return imp === true || (Array.isArray(imp) && imp.includes('resize')) }, isOverflowImportant: function isOverflowImportant () { var imp = this.important; return imp === true || (Array.isArray(imp) && imp.includes('overflow')) }, isHeightImportant: function isHeightImportant () { var imp = this.important; return imp === true || (Array.isArray(imp) && imp.includes('height')) } }, watch: { value: function value (val) { this.val = val; }, val: function val (val$1) { this.$nextTick(this.resize); this.$emit('input', val$1); }, minHeight: function minHeight () { this.$nextTick(this.resize); }, maxHeight: function maxHeight () { this.$nextTick(this.resize); }, autosize: function autosize (val) { if (val) { this.resize(); } } }, methods: { resize: function resize () { var this$1$1 = this; var important = this.isHeightImportant ? 'important' : ''; this.height = "auto" + (important ? ' !important' : ''); this.$nextTick(function () { var contentHeight = this$1$1.$el.scrollHeight + 1; if (this$1$1.minHeight) { contentHeight = contentHeight < this$1$1.minHeight ? this$1$1.minHeight : contentHeight; } if (this$1$1.maxHeight) { if (contentHeight > this$1$1.maxHeight) { contentHeight = this$1$1.maxHeight; this$1$1.maxHeightScroll = true; } else { this$1$1.maxHeightScroll = false; } } var heightVal = contentHeight + 'px'; this$1$1.height = "" + heightVal + (important ? ' !important' : ''); }); return this } }, created: function created () { this.val = this.value; }, mounted: function mounted () { this.resize(); } }; function render$8(_ctx, _cache, $props, $setup, $data, $options) { return withDirectives((openBlock(), createElementBlock("textarea", { style: normalizeStyle($options.computedStyles), "onUpdate:modelValue": _cache[0] || (_cache[0] = function ($event) { return (($data.val) = $event); }), onFocus: _cache[1] || (_cache[1] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return ($options.resize && $options.resize.apply($options, args)); }) }, null, 36 /* STYLE, HYDRATE_EVENTS */)), [ [vModelText, $data.val] ]) } script$g.render = render$8; script$g.__file = "node_modules/vue-textarea-autosize/src/components/TextareaAutosize.vue"; var script$f = { extends: script$l, name: QuestionType.LongText, components: { TextareaAutosize: script$g }, data: function data () { return { canReceiveFocus: true } }, mounted: function mounted() { window.addEventListener('resize', this.onResizeListener); }, beforeUnmount: function beforeUnmount() { window.removeEventListener('resize', this.onResizeListener); }, methods: { onResizeListener: function onResizeListener() { this.$refs.input.resize(); }, unsetFocus: function unsetFocus($event) { if ($event || !this.isMobile) { this.focused = false; } }, onEnterDown: function onEnterDown($event) { if (!this.isMobile) { $event.preventDefault(); } }, onEnter: function onEnter() { this._onEnter(); if (this.isMobile) { this.focus(); } } } }; function render$7(_ctx, _cache, $props, $setup, $data, $options) { var _component_textarea_autosize = resolveComponent("textarea-autosize"); return (openBlock(), createElementBlock("span", null, [ createVNode(_component_textarea_autosize, { ref: "input", rows: "1", value: _ctx.modelValue, required: _ctx.question.required, onKeydown: [ _ctx.onKeyDown, withKeys(withModifiers($options.onEnterDown, ["exact"]), ["enter"]) ], onKeyup: [ _ctx.onChange, withKeys(withModifiers($options.onEnter, ["exact","prevent"]), ["enter"]), withKeys(withModifiers($options.onEnter, ["prevent"]), ["tab"]) ], onFocus: _ctx.setFocus, onBlur: $options.unsetFocus, placeholder: _ctx.placeholder, maxlength: _ctx.question.maxLength }, null, 8 /* PROPS */, ["value", "required", "onKeydown", "onKeyup", "onFocus", "onBlur", "placeholder", "maxlength"]) ])) } script$f.render = render$7; script$f.__file = "src/components/QuestionTypes/LongTextType.vue"; var script$e = { extends: script$l, name: QuestionType.MultipleChoice, data: function data() { return { editingOther: false, hasImages: false } }, mounted: function mounted() { this.addKeyListener(); }, beforeUnmount: function beforeUnmount() { this.removeKeyListener(); }, watch: { active: function active(value) { if (value) { this.addKeyListener(); if (this.question.multiple && this.question.answered) { this.enterPressed = false; } } else { this.removeKeyListener(); } } }, methods: { addKeyListener: function addKeyListener() { this.removeKeyListener(); document.addEventListener('keyup', this.onKeyListener); }, removeKeyListener: function removeKeyListener() { document.removeEventListener('keyup', this.onKeyListener); }, /** * Listens for keyboard events (A, B, C, ...) */ onKeyListener: function onKeyListener($event) { if (this.active && !this.editingOther && $event.key && $event.key.length === 1) { var keyCode = $event.key.toUpperCase().charCodeAt(0); if (keyCode >= 65 && keyCode <= 90) { var index = keyCode - 65; if (index > -1) { var option = this.question.options[index]; if (option) { this.toggleAnswer(option); } else if (this.question.allowOther && index === this.question.options.length) { this.startEditOther(); } } } } }, getLabel: function getLabel(index) { return this.language.ariaMultipleChoice.replace(':letter', this.getToggleKey(index)) }, getToggleKey: function getToggleKey(index) { var key = 65 + index; if (key <= 90) { return String.fromCharCode(key) } return '' }, toggleAnswer: function toggleAnswer(option) { if (!this.question.multiple) { if (this.question.allowOther) { this.question.other = this.dataValue = null; this.setAnswer(this.dataValue); } for (var i = 0; i < this.question.options.length; i++) { var o = this.question.options[i]; if (o.selected) { this._toggleAnswer(o); } } } this._toggleAnswer(option); }, _toggleAnswer: function _toggleAnswer(option) { var optionValue = option.choiceValue(); option.toggle(); if (this.question.multiple) { this.enterPressed = false; if (!option.selected) { this._removeAnswer(optionValue); } else if (this.dataValue.indexOf(optionValue) === -1) { this.dataValue.push(optionValue); } } else { this.dataValue = option.selected ? optionValue : null; } if (this.isValid() && this.question.nextStepOnAnswer && !this.question.multiple && !this.disabled) { this.$emit('next'); } this.setAnswer(this.dataValue); }, _removeAnswer: function _removeAnswer(value) { var index = this.dataValue.indexOf(value); if (index !== -1) { this.dataValue.splice(index, 1); } }, startEditOther: function startEditOther() { var this$1$1 = this; this.editingOther = true; this.enterPressed = false; this.$nextTick(function () { this$1$1.$refs.otherInput.focus(); }); }, onChangeOther: function onChangeOther() { if (this.editingOther) { var value = [], self = this; this.question.options.forEach(function(option) { if (option.selected) { if (self.question.multiple) { value.push(option.choiceValue()); } else { option.toggle(); } } }); if (this.question.other && this.question.multiple) { value.push(this.question.other); } else if (!this.question.multiple) { value = this.question.other; } this.dataValue = value; this.setAnswer(this.dataValue); } }, stopEditOther: function stopEditOther() { this.editingOther = false; } }, computed: { hasValue: function hasValue() { if (this.question.options.filter(function (o) { return o.selected; }).length) { return true } if (this.question.allowOther) { return this.question.other && this.question.other.trim().length > 0 } return false } } }; var _hoisted_1$6 = { class: "f-radios-wrap" }; var _hoisted_2$5 = ["onClick", "aria-label"]; var _hoisted_3$4 = { key: 0, class: "f-image" }; var _hoisted_4$4 = ["src", "alt"]; var _hoisted_5$4 = { class: "f-label-wrap" }; var _hoisted_6$4 = { class: "f-key" }; var _hoisted_7$4 = { key: 0, class: "f-label" }; var _hoisted_8$4 = ["aria-label"]; var _hoisted_9$4 = { class: "f-label-wrap" }; var _hoisted_10$4 = { key: 0, class: "f-key" }; var _hoisted_11$4 = { key: 2, class: "f-selected" }; var _hoisted_12$4 = { class: "f-label" }; var _hoisted_13$4 = { key: 3, class: "f-label" }; function render$6(_ctx, _cache, $props, $setup, $data, $options) { return (openBlock(), createElementBlock("div", _hoisted_1$6, [ createElementVNode("ul", { class: normalizeClass(["f-radios", {'f-multiple': _ctx.question.multiple}]), role: "listbox" }, [ (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.question.options, function (option, index) { return (openBlock(), createElementBlock("li", { onClick: withModifiers(function ($event) { return ($options.toggleAnswer(option)); }, ["prevent"]), class: normalizeClass({'f-selected': option.selected}), key: 'm' + index, "aria-label": $options.getLabel(index), role: "option" }, [ ($data.hasImages && option.imageSrc) ? (openBlock(), createElementBlock("span", _hoisted_3$4, [ createElementVNode("img", { src: option.imageSrc, alt: option.imageAlt }, null, 8 /* PROPS */, _hoisted_4$4) ])) : createCommentVNode("v-if", true), createElementVNode("div", _hoisted_5$4, [ createElementVNode("span", _hoisted_6$4, toDisplayString($options.getToggleKey(index)), 1 /* TEXT */), (option.choiceLabel()) ? (openBlock(), createElementBlock("span", _hoisted_7$4, toDisplayString(option.choiceLabel()), 1 /* TEXT */)) : createCommentVNode("v-if", true) ]) ], 10 /* CLASS, PROPS */, _hoisted_2$5)) }), 128 /* KEYED_FRAGMENT */)), (!$data.hasImages && _ctx.question.allowOther) ? (openBlock(), createElementBlock("li", { key: 0, class: normalizeClass(["f-other", {'f-selected': _ctx.question.other, 'f-focus': $data.editingOther}]), onClick: _cache[5] || (_cache[5] = withModifiers(function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return ($options.startEditOther && $options.startEditOther.apply($options, args)); }, ["prevent"])), "aria-label": _ctx.language.ariaTypeAnswer, role: "option" }, [ createElementVNode("div", _hoisted_9$4, [ (!$data.editingOther) ? (openBlock(), createElementBlock("span", _hoisted_10$4, toDisplayString($options.getToggleKey(_ctx.question.options.length)), 1 /* TEXT */)) : createCommentVNode("v-if", true), ($data.editingOther) ? withDirectives((openBlock(), createElementBlock("input", { key: 1, "onUpdate:modelValue": _cache[0] || (_cache[0] = function ($event) { return ((_ctx.question.other) = $event); }), type: "text", ref: "otherInput", onBlur: _cache[1] || (_cache[1] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return ($options.stopEditOther && $options.stopEditOther.apply($options, args)); }), onKeyup: [ _cache[2] || (_cache[2] = withKeys(withModifiers(function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return ($options.stopEditOther && $options.stopEditOther.apply($options, args)); }, ["prevent"]), ["enter"])), _cache[3] || (_cache[3] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return ($options.onChangeOther && $options.onChangeOther.apply($options, args)); }) ], onChange: _cache[4] || (_cache[4] = function () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; return ($options.onChangeOther && $options.onChangeOther.apply($options, args)); }), maxlength: "256" }, null, 544 /* HYDRATE_EVENTS, NEED_PATCH */)), [ [vModelText, _ctx.question.other] ]) : (_ctx.question.other) ? (openBlock(), createElementBlock("span", _hoisted_11$4, [ createElementVNode("span", _hoisted_12$4, toDisplayString(_ctx.question.other), 1 /* TEXT */) ])) : (openBlock(), createElementBlock("span", _hoisted_13$4, toDisplayString(_ctx.language.otherPrompt), 1 /* TEXT */)) ]) ], 10 /* CLASS, PROPS */, _hoisted_8$4)) : createCommentVNode("v-if", true) ], 2 /* CLASS */) ])) } script$e.render = render$6; script$e.__file = "src/components/QuestionTypes/MultipleChoiceType.vue"; var script$d = { extends: script$e, name: QuestionType.MultiplePictureChoice, data: function data() { return { hasImages: true } } }; script$d.__file = "src/components/QuestionTypes/MultiplePictureChoiceType.vue"; var script$c = { extends: script$i, name: QuestionType.Number, data: function data() { return { inputType: 'tel', allowedChars: '-0123456789.' } }, methods: { validate: function validate() { if (this.question.min !== null && !isNaN(this.question.min) && +this.dataValue < +this.question.min) { return false } if (this.question.max !== null && !isNaN(this.question.max) && +this.dataValue > +this.question.max) { return false } if (this.hasValue) { if (this.question.mask) { return this.validateMask() } return !isNaN(+this.dataValue) } return !this.question.required || this.hasValue } } }; script$c.__file = "src/components/QuestionTypes/NumberType.vue"; var script$b = { extends: script$i, name: QuestionType.Password, data: function data() { return { inputType: 'password' } } }; script$b.__file = "src/components/QuestionTypes/PasswordType.vue"; var script$a = { extends: script$i, name: QuestionType.Phone, data: function data() { return { inputType: 'tel', canReceiveFocus: true } } }; script$a.__file = "src/components/QuestionTypes/PhoneType.vue"; var script$9 = { extends: script$l, name: QuestionType.SectionBreak, methods: { onEnter: function onEnter() { this.dirty = true; this._onEnter(); }, isValid: function isValid() { return true } } }; var _hoisted_1$5 = { key: 0, class: "f-content" }; var _hoisted_2$4 = { class: "f-section-text" }; function render$5(_ctx, _cache, $props, $setup, $data, $options) { return (_ctx.question.content) ? (openBlock(), createElementBlock("div", _hoisted_1$5, [ createElementVNode("span", _hoisted_2$4, toDisplayString(_ctx.question.content), 1 /* TEXT */) ])) : createCommentVNode("v-if", true) } script$9.render = render$5; script$9.__file = "src/components/QuestionTypes/SectionBreakType.vue"; var script$8 = { extends: script$i, name: QuestionType.Url, data: function data() { return { inputType: 'url' } }, methods: { fixAnswer: function fixAnswer(answer) { if (answer && answer.indexOf('://') === -1) { // Insert https protocol to make it easier to enter // correct URLs answer = 'https://' + answer; } return answer }, validate: function validate() { if (this.hasValue) { try { new URL(this.fixAnswer(this.dataValue)); return true } catch(_) { // Invalid URL return false } } return !this.question.required } } }; script$8.__file = "src/components/QuestionTypes/UrlType.vue"; var script$7 = { extends: script$i, name: QuestionType.Date, data: function data() { return { inputType: 'date' } }, methods: { validate: function validate() { if (this.question.min && this.dataValue < this.question.min) { return false } if (this.question.max && this.dataValue > this.question.max) { return false } return !this.question.required || this.hasValue } }