From 5666f71829f3700970fc80f37173ba5c3ee9dcba Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Fri, 8 Jul 2022 08:47:03 -0400 Subject: [PATCH] Bump build --- priv/static/phoenix_live_view.cjs.js | 5 +++++ priv/static/phoenix_live_view.cjs.js.map | 4 ++-- priv/static/phoenix_live_view.esm.js | 5 +++++ priv/static/phoenix_live_view.esm.js.map | 4 ++-- priv/static/phoenix_live_view.js | 5 +++++ priv/static/phoenix_live_view.min.js | 8 ++++---- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/priv/static/phoenix_live_view.cjs.js b/priv/static/phoenix_live_view.cjs.js index 6176536a70..17c8082e8d 100644 --- a/priv/static/phoenix_live_view.cjs.js +++ b/priv/static/phoenix_live_view.cjs.js @@ -3265,6 +3265,11 @@ var LiveSocket = class { clearTimeout(this.reloadWithJitterTimer); this.socket.disconnect(callback); } + replaceTransport(transport) { + clearTimeout(this.reloadWithJitterTimer); + this.socket.replaceTransport(transport); + this.connect(); + } execJS(el, encodedJS, eventType = null) { this.owner(el, (view) => js_default.exec(eventType, encodedJS, view, el)); } diff --git a/priv/static/phoenix_live_view.cjs.js.map b/priv/static/phoenix_live_view.cjs.js.map index 88db5cb4a8..fc47654eae 100644 --- a/priv/static/phoenix_live_view.cjs.js.map +++ b/priv/static/phoenix_live_view.cjs.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../assets/js/phoenix_live_view/index.js", "../../assets/js/phoenix_live_view/constants.js", "../../assets/js/phoenix_live_view/entry_uploader.js", "../../assets/js/phoenix_live_view/utils.js", "../../assets/js/phoenix_live_view/browser.js", "../../assets/js/phoenix_live_view/dom.js", "../../assets/js/phoenix_live_view/upload_entry.js", "../../assets/js/phoenix_live_view/live_uploader.js", "../../assets/js/phoenix_live_view/hooks.js", "../../assets/js/phoenix_live_view/dom_post_morph_restorer.js", "../../assets/node_modules/morphdom/dist/morphdom-esm.js", "../../assets/js/phoenix_live_view/dom_patch.js", "../../assets/js/phoenix_live_view/rendered.js", "../../assets/js/phoenix_live_view/view_hook.js", "../../assets/js/phoenix_live_view/js.js", "../../assets/js/phoenix_live_view/view.js", "../../assets/js/phoenix_live_view/live_socket.js"], - "sourcesContent": ["/*\n================================================================================\nPhoenix LiveView JavaScript Client\n================================================================================\n\nSee the hexdocs at `https://hexdocs.pm/phoenix_live_view` for documentation.\n\n*/\n\nimport LiveSocket from \"./live_socket\"\nexport {\n LiveSocket\n}\n", "\nexport const CONSECUTIVE_RELOADS = \"consecutive-reloads\"\nexport const MAX_RELOADS = 10\nexport const RELOAD_JITTER_MIN = 5000\nexport const RELOAD_JITTER_MAX = 10000\nexport const FAILSAFE_JITTER = 30000\nexport const PHX_EVENT_CLASSES = [\n \"phx-click-loading\", \"phx-change-loading\", \"phx-submit-loading\",\n \"phx-keydown-loading\", \"phx-keyup-loading\", \"phx-blur-loading\", \"phx-focus-loading\"\n]\nexport const PHX_COMPONENT = \"data-phx-component\"\nexport const PHX_LIVE_LINK = \"data-phx-link\"\nexport const PHX_TRACK_STATIC = \"track-static\"\nexport const PHX_LINK_STATE = \"data-phx-link-state\"\nexport const PHX_REF = \"data-phx-ref\"\nexport const PHX_REF_SRC = \"data-phx-ref-src\"\nexport const PHX_TRACK_UPLOADS = \"track-uploads\"\nexport const PHX_UPLOAD_REF = \"data-phx-upload-ref\"\nexport const PHX_PREFLIGHTED_REFS = \"data-phx-preflighted-refs\"\nexport const PHX_DONE_REFS = \"data-phx-done-refs\"\nexport const PHX_DROP_TARGET = \"drop-target\"\nexport const PHX_ACTIVE_ENTRY_REFS = \"data-phx-active-refs\"\nexport const PHX_LIVE_FILE_UPDATED = \"phx:live-file:updated\"\nexport const PHX_SKIP = \"data-phx-skip\"\nexport const PHX_PRUNE = \"data-phx-prune\"\nexport const PHX_PAGE_LOADING = \"page-loading\"\nexport const PHX_CONNECTED_CLASS = \"phx-connected\"\nexport const PHX_DISCONNECTED_CLASS = \"phx-loading\"\nexport const PHX_NO_FEEDBACK_CLASS = \"phx-no-feedback\"\nexport const PHX_ERROR_CLASS = \"phx-error\"\nexport const PHX_PARENT_ID = \"data-phx-parent-id\"\nexport const PHX_MAIN = \"data-phx-main\"\nexport const PHX_ROOT_ID = \"data-phx-root-id\"\nexport const PHX_TRIGGER_ACTION = \"trigger-action\"\nexport const PHX_FEEDBACK_FOR = \"feedback-for\"\nexport const PHX_HAS_FOCUSED = \"phx-has-focused\"\nexport const FOCUSABLE_INPUTS = [\"text\", \"textarea\", \"number\", \"email\", \"password\", \"search\", \"tel\", \"url\", \"date\", \"time\", \"datetime-local\", \"color\", \"range\"]\nexport const CHECKABLE_INPUTS = [\"checkbox\", \"radio\"]\nexport const PHX_HAS_SUBMITTED = \"phx-has-submitted\"\nexport const PHX_SESSION = \"data-phx-session\"\nexport const PHX_VIEW_SELECTOR = `[${PHX_SESSION}]`\nexport const PHX_STICKY = \"data-phx-sticky\"\nexport const PHX_STATIC = \"data-phx-static\"\nexport const PHX_READONLY = \"data-phx-readonly\"\nexport const PHX_DISABLED = \"data-phx-disabled\"\nexport const PHX_DISABLE_WITH = \"disable-with\"\nexport const PHX_DISABLE_WITH_RESTORE = \"data-phx-disable-with-restore\"\nexport const PHX_HOOK = \"hook\"\nexport const PHX_DEBOUNCE = \"debounce\"\nexport const PHX_THROTTLE = \"throttle\"\nexport const PHX_UPDATE = \"update\"\nexport const PHX_KEY = \"key\"\nexport const PHX_PRIVATE = \"phxPrivate\"\nexport const PHX_AUTO_RECOVER = \"auto-recover\"\nexport const PHX_LV_DEBUG = \"phx:live-socket:debug\"\nexport const PHX_LV_PROFILE = \"phx:live-socket:profiling\"\nexport const PHX_LV_LATENCY_SIM = \"phx:live-socket:latency-sim\"\nexport const PHX_PROGRESS = \"progress\"\nexport const LOADER_TIMEOUT = 1\nexport const BEFORE_UNLOAD_LOADER_TIMEOUT = 200\nexport const BINDING_PREFIX = \"phx-\"\nexport const PUSH_TIMEOUT = 30000\nexport const LINK_HEADER = \"x-requested-with\"\nexport const RESPONSE_URL_HEADER = \"x-response-url\"\nexport const DEBOUNCE_TRIGGER = \"debounce-trigger\"\nexport const THROTTLED = \"throttled\"\nexport const DEBOUNCE_PREV_KEY = \"debounce-prev-key\"\nexport const DEFAULTS = {\n debounce: 300,\n throttle: 300\n}\n\n// Rendered\nexport const DYNAMICS = \"d\"\nexport const STATIC = \"s\"\nexport const COMPONENTS = \"c\"\nexport const EVENTS = \"e\"\nexport const REPLY = \"r\"\nexport const TITLE = \"t\"\nexport const TEMPLATES = \"p\"\n", "import {\n logError\n} from \"./utils\"\n\nexport default class EntryUploader {\n constructor(entry, chunkSize, liveSocket){\n this.liveSocket = liveSocket\n this.entry = entry\n this.offset = 0\n this.chunkSize = chunkSize\n this.chunkTimer = null\n this.uploadChannel = liveSocket.channel(`lvu:${entry.ref}`, {token: entry.metadata()})\n }\n\n error(reason){\n clearTimeout(this.chunkTimer)\n this.uploadChannel.leave()\n this.entry.error(reason)\n }\n\n upload(){\n this.uploadChannel.onError(reason => this.error(reason))\n this.uploadChannel.join()\n .receive(\"ok\", _data => this.readNextChunk())\n .receive(\"error\", reason => this.error(reason))\n }\n\n isDone(){ return this.offset >= this.entry.file.size }\n\n readNextChunk(){\n let reader = new window.FileReader()\n let blob = this.entry.file.slice(this.offset, this.chunkSize + this.offset)\n reader.onload = (e) => {\n if(e.target.error === null){\n this.offset += e.target.result.byteLength\n this.pushChunk(e.target.result)\n } else {\n return logError(\"Read error: \" + e.target.error)\n }\n }\n reader.readAsArrayBuffer(blob)\n }\n\n pushChunk(chunk){\n if(!this.uploadChannel.isJoined()){ return }\n this.uploadChannel.push(\"chunk\", chunk)\n .receive(\"ok\", () => {\n this.entry.progress((this.offset / this.entry.file.size) * 100)\n if(!this.isDone()){\n this.chunkTimer = setTimeout(() => this.readNextChunk(), this.liveSocket.getLatencySim() || 0)\n }\n })\n }\n}\n", "import {\n PHX_VIEW_SELECTOR\n} from \"./constants\"\n\nimport EntryUploader from \"./entry_uploader\"\n\nexport let logError = (msg, obj) => console.error && console.error(msg, obj)\n\nexport let isCid = (cid) => {\n let type = typeof(cid)\n return type === \"number\" || (type === \"string\" && /^(0|[1-9]\\d*)$/.test(cid))\n}\n\nexport function detectDuplicateIds(){\n let ids = new Set()\n let elems = document.querySelectorAll(\"*[id]\")\n for(let i = 0, len = elems.length; i < len; i++){\n if(ids.has(elems[i].id)){\n console.error(`Multiple IDs detected: ${elems[i].id}. Ensure unique element ids.`)\n } else {\n ids.add(elems[i].id)\n }\n }\n}\n\nexport let debug = (view, kind, msg, obj) => {\n if(view.liveSocket.isDebugEnabled()){\n console.log(`${view.id} ${kind}: ${msg} - `, obj)\n }\n}\n\n// wraps value in closure or returns closure\nexport let closure = (val) => typeof val === \"function\" ? val : function (){ return val }\n\nexport let clone = (obj) => { return JSON.parse(JSON.stringify(obj)) }\n\nexport let closestPhxBinding = (el, binding, borderEl) => {\n do {\n if(el.matches(`[${binding}]`)){ return el }\n el = el.parentElement || el.parentNode\n } while(el !== null && el.nodeType === 1 && !((borderEl && borderEl.isSameNode(el)) || el.matches(PHX_VIEW_SELECTOR)))\n return null\n}\n\nexport let isObject = (obj) => {\n return obj !== null && typeof obj === \"object\" && !(obj instanceof Array)\n}\n\nexport let isEqualObj = (obj1, obj2) => JSON.stringify(obj1) === JSON.stringify(obj2)\n\nexport let isEmpty = (obj) => {\n for(let x in obj){ return false }\n return true\n}\n\nexport let maybe = (el, callback) => el && callback(el)\n\nexport let channelUploader = function (entries, onError, resp, liveSocket){\n entries.forEach(entry => {\n let entryUploader = new EntryUploader(entry, resp.config.chunk_size, liveSocket)\n entryUploader.upload()\n })\n}\n", "let Browser = {\n canPushState(){ return (typeof (history.pushState) !== \"undefined\") },\n\n dropLocal(localStorage, namespace, subkey){\n return localStorage.removeItem(this.localKey(namespace, subkey))\n },\n\n updateLocal(localStorage, namespace, subkey, initial, func){\n let current = this.getLocal(localStorage, namespace, subkey)\n let key = this.localKey(namespace, subkey)\n let newVal = current === null ? initial : func(current)\n localStorage.setItem(key, JSON.stringify(newVal))\n return newVal\n },\n\n getLocal(localStorage, namespace, subkey){\n return JSON.parse(localStorage.getItem(this.localKey(namespace, subkey)))\n },\n\n updateCurrentState(callback){\n if(!this.canPushState()){ return }\n history.replaceState(callback(history.state || {}), \"\", window.location.href)\n },\n\n pushState(kind, meta, to){\n if(this.canPushState()){\n if(to !== window.location.href){\n if(meta.type == \"redirect\" && meta.scroll){\n // If we're redirecting store the current scrollY for the current history state.\n let currentState = history.state || {}\n currentState.scroll = meta.scroll\n history.replaceState(currentState, \"\", window.location.href)\n }\n\n delete meta.scroll // Only store the scroll in the redirect case.\n history[kind + \"State\"](meta, \"\", to || null) // IE will coerce undefined to string\n let hashEl = this.getHashTargetEl(window.location.hash)\n\n if(hashEl){\n hashEl.scrollIntoView()\n } else if(meta.type === \"redirect\"){\n window.scroll(0, 0)\n }\n }\n } else {\n this.redirect(to)\n }\n },\n\n setCookie(name, value){\n document.cookie = `${name}=${value}`\n },\n\n getCookie(name){\n return document.cookie.replace(new RegExp(`(?:(?:^|.*;\\s*)${name}\\s*\\=\\s*([^;]*).*$)|^.*$`), \"$1\")\n },\n\n redirect(toURL, flash){\n if(flash){ Browser.setCookie(\"__phoenix_flash__\", flash + \"; max-age=60000; path=/\") }\n window.location = toURL\n },\n\n localKey(namespace, subkey){ return `${namespace}-${subkey}` },\n\n getHashTargetEl(maybeHash){\n let hash = maybeHash.toString().substring(1)\n if(hash === \"\"){ return }\n return document.getElementById(hash) || document.querySelector(`a[name=\"${hash}\"]`)\n }\n}\n\nexport default Browser\n", "import {\n CHECKABLE_INPUTS,\n DEBOUNCE_PREV_KEY,\n DEBOUNCE_TRIGGER,\n FOCUSABLE_INPUTS,\n PHX_COMPONENT,\n PHX_EVENT_CLASSES,\n PHX_HAS_FOCUSED,\n PHX_HAS_SUBMITTED,\n PHX_MAIN,\n PHX_NO_FEEDBACK_CLASS,\n PHX_PARENT_ID,\n PHX_PRIVATE,\n PHX_REF,\n PHX_REF_SRC,\n PHX_ROOT_ID,\n PHX_SESSION,\n PHX_STATIC,\n PHX_UPLOAD_REF,\n PHX_VIEW_SELECTOR,\n PHX_STICKY,\n THROTTLED\n} from \"./constants\"\n\nimport {\n logError\n} from \"./utils\"\n\nlet DOM = {\n byId(id){ return document.getElementById(id) || logError(`no id found for ${id}`) },\n\n removeClass(el, className){\n el.classList.remove(className)\n if(el.classList.length === 0){ el.removeAttribute(\"class\") }\n },\n\n all(node, query, callback){\n if(!node){ return [] }\n let array = Array.from(node.querySelectorAll(query))\n return callback ? array.forEach(callback) : array\n },\n\n childNodeLength(html){\n let template = document.createElement(\"template\")\n template.innerHTML = html\n return template.content.childElementCount\n },\n\n isUploadInput(el){ return el.type === \"file\" && el.getAttribute(PHX_UPLOAD_REF) !== null },\n\n findUploadInputs(node){ return this.all(node, `input[type=\"file\"][${PHX_UPLOAD_REF}]`) },\n\n findComponentNodeList(node, cid){\n return this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}=\"${cid}\"]`), node)\n },\n\n isPhxDestroyed(node){\n return node.id && DOM.private(node, \"destroyed\") ? true : false\n },\n\n markPhxChildDestroyed(el){\n if(this.isPhxChild(el)){ el.setAttribute(PHX_SESSION, \"\") }\n this.putPrivate(el, \"destroyed\", true)\n },\n\n findPhxChildrenInFragment(html, parentId){\n let template = document.createElement(\"template\")\n template.innerHTML = html\n return this.findPhxChildren(template.content, parentId)\n },\n\n isIgnored(el, phxUpdate){\n return (el.getAttribute(phxUpdate) || el.getAttribute(\"data-phx-update\")) === \"ignore\"\n },\n\n isPhxUpdate(el, phxUpdate, updateTypes){\n return el.getAttribute && updateTypes.indexOf(el.getAttribute(phxUpdate)) >= 0\n },\n\n findPhxSticky(el){ return this.all(el, `[${PHX_STICKY}]`) },\n\n findPhxChildren(el, parentId){\n return this.all(el, `${PHX_VIEW_SELECTOR}[${PHX_PARENT_ID}=\"${parentId}\"]`)\n },\n\n findParentCIDs(node, cids){\n let initial = new Set(cids)\n return cids.reduce((acc, cid) => {\n let selector = `[${PHX_COMPONENT}=\"${cid}\"] [${PHX_COMPONENT}]`\n\n this.filterWithinSameLiveView(this.all(node, selector), node)\n .map(el => parseInt(el.getAttribute(PHX_COMPONENT)))\n .forEach(childCID => acc.delete(childCID))\n\n return acc\n }, initial)\n },\n\n filterWithinSameLiveView(nodes, parent){\n if(parent.querySelector(PHX_VIEW_SELECTOR)){\n return nodes.filter(el => this.withinSameLiveView(el, parent))\n } else {\n return nodes\n }\n },\n\n withinSameLiveView(node, parent){\n while(node = node.parentNode){\n if(node.isSameNode(parent)){ return true }\n if(node.getAttribute(PHX_SESSION) !== null){ return false }\n }\n },\n\n private(el, key){ return el[PHX_PRIVATE] && el[PHX_PRIVATE][key] },\n\n deletePrivate(el, key){ el[PHX_PRIVATE] && delete (el[PHX_PRIVATE][key]) },\n\n putPrivate(el, key, value){\n if(!el[PHX_PRIVATE]){ el[PHX_PRIVATE] = {} }\n el[PHX_PRIVATE][key] = value\n },\n\n updatePrivate(el, key, defaultVal, updateFunc){\n let existing = this.private(el, key)\n if(existing === undefined){\n this.putPrivate(el, key, updateFunc(defaultVal))\n } else {\n this.putPrivate(el, key, updateFunc(existing))\n }\n },\n\n copyPrivates(target, source){\n if(source[PHX_PRIVATE]){\n target[PHX_PRIVATE] = source[PHX_PRIVATE]\n }\n },\n\n putTitle(str){\n let titleEl = document.querySelector(\"title\")\n let {prefix, suffix} = titleEl.dataset\n document.title = `${prefix || \"\"}${str}${suffix || \"\"}`\n },\n\n debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback){\n let debounce = el.getAttribute(phxDebounce)\n let throttle = el.getAttribute(phxThrottle)\n if(debounce === \"\"){ debounce = defaultDebounce }\n if(throttle === \"\"){ throttle = defaultThrottle }\n let value = debounce || throttle\n switch(value){\n case null: return callback()\n\n case \"blur\":\n if(this.once(el, \"debounce-blur\")){\n el.addEventListener(\"blur\", () => callback())\n }\n return\n\n default:\n let timeout = parseInt(value)\n let trigger = () => throttle ? this.deletePrivate(el, THROTTLED) : callback()\n let currentCycle = this.incCycle(el, DEBOUNCE_TRIGGER, trigger)\n if(isNaN(timeout)){ return logError(`invalid throttle/debounce value: ${value}`) }\n if(throttle){\n let newKeyDown = false\n if(event.type === \"keydown\"){\n let prevKey = this.private(el, DEBOUNCE_PREV_KEY)\n this.putPrivate(el, DEBOUNCE_PREV_KEY, event.key)\n newKeyDown = prevKey !== event.key\n }\n\n if(!newKeyDown && this.private(el, THROTTLED)){\n return false\n } else {\n callback()\n this.putPrivate(el, THROTTLED, true)\n setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER), timeout)\n }\n } else {\n setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle), timeout)\n }\n\n\n let form = el.form\n if(form && this.once(form, \"bind-debounce\")){\n form.addEventListener(\"submit\", () => {\n Array.from((new FormData(form)).entries(), ([name]) => {\n let input = form.querySelector(`[name=\"${name}\"]`)\n this.incCycle(input, DEBOUNCE_TRIGGER)\n this.deletePrivate(input, THROTTLED)\n })\n })\n }\n if(this.once(el, \"bind-debounce\")){\n el.addEventListener(\"blur\", () => this.triggerCycle(el, DEBOUNCE_TRIGGER))\n }\n }\n },\n\n triggerCycle(el, key, currentCycle){\n let [cycle, trigger] = this.private(el, key)\n if(!currentCycle){ currentCycle = cycle }\n if(currentCycle === cycle){\n this.incCycle(el, key)\n trigger()\n }\n },\n\n once(el, key){\n if(this.private(el, key) === true){ return false }\n this.putPrivate(el, key, true)\n return true\n },\n\n incCycle(el, key, trigger = function (){ }){\n let [currentCycle] = this.private(el, key) || [0, trigger]\n currentCycle++\n this.putPrivate(el, key, [currentCycle, trigger])\n return currentCycle\n },\n\n discardError(container, el, phxFeedbackFor){\n let field = el.getAttribute && el.getAttribute(phxFeedbackFor)\n // TODO: Remove id lookup after we update Phoenix to use input_name instead of input_id\n let input = field && container.querySelector(`[id=\"${field}\"], [name=\"${field}\"]`)\n if(!input){ return }\n\n if(!(this.private(input, PHX_HAS_FOCUSED) || this.private(input.form, PHX_HAS_SUBMITTED))){\n el.classList.add(PHX_NO_FEEDBACK_CLASS)\n }\n },\n\n showError(inputEl, phxFeedbackFor){\n if(inputEl.id || inputEl.name){\n this.all(inputEl.form, `[${phxFeedbackFor}=\"${inputEl.id}\"], [${phxFeedbackFor}=\"${inputEl.name}\"]`, (el) => {\n this.removeClass(el, PHX_NO_FEEDBACK_CLASS)\n })\n }\n },\n\n isPhxChild(node){\n return node.getAttribute && node.getAttribute(PHX_PARENT_ID)\n },\n\n isPhxSticky(node){\n return node.getAttribute && node.getAttribute(PHX_STICKY) !== null\n },\n\n firstPhxChild(el){\n return this.isPhxChild(el) ? el : this.all(el, `[${PHX_PARENT_ID}]`)[0]\n },\n\n dispatchEvent(target, name, opts = {}){\n let bubbles = opts.bubbles === undefined ? true : !!opts.bubbles\n let eventOpts = {bubbles: bubbles, cancelable: true, detail: opts.detail || {}}\n let event = name === \"click\" ? new MouseEvent(\"click\", eventOpts) : new CustomEvent(name, eventOpts)\n target.dispatchEvent(event)\n },\n\n cloneNode(node, html){\n if(typeof (html) === \"undefined\"){\n return node.cloneNode(true)\n } else {\n let cloned = node.cloneNode(false)\n cloned.innerHTML = html\n return cloned\n }\n },\n\n mergeAttrs(target, source, opts = {}){\n let exclude = opts.exclude || []\n let isIgnored = opts.isIgnored\n let sourceAttrs = source.attributes\n for(let i = sourceAttrs.length - 1; i >= 0; i--){\n let name = sourceAttrs[i].name\n if(exclude.indexOf(name) < 0){ target.setAttribute(name, source.getAttribute(name)) }\n }\n\n let targetAttrs = target.attributes\n for(let i = targetAttrs.length - 1; i >= 0; i--){\n let name = targetAttrs[i].name\n if(isIgnored){\n if(name.startsWith(\"data-\") && !source.hasAttribute(name)){ target.removeAttribute(name) }\n } else {\n if(!source.hasAttribute(name)){ target.removeAttribute(name) }\n }\n }\n },\n\n mergeFocusedInput(target, source){\n // skip selects because FF will reset highlighted index for any setAttribute\n if(!(target instanceof HTMLSelectElement)){ DOM.mergeAttrs(target, source, {exclude: [\"value\"]}) }\n if(source.readOnly){\n target.setAttribute(\"readonly\", true)\n } else {\n target.removeAttribute(\"readonly\")\n }\n },\n\n hasSelectionRange(el){\n return el.setSelectionRange && (el.type === \"text\" || el.type === \"textarea\")\n },\n\n restoreFocus(focused, selectionStart, selectionEnd){\n if(!DOM.isTextualInput(focused)){ return }\n let wasFocused = focused.matches(\":focus\")\n if(focused.readOnly){ focused.blur() }\n if(!wasFocused){ focused.focus() }\n if(this.hasSelectionRange(focused)){\n focused.setSelectionRange(selectionStart, selectionEnd)\n }\n },\n\n isFormInput(el){ return /^(?:input|select|textarea)$/i.test(el.tagName) && el.type !== \"button\" },\n\n syncAttrsToProps(el){\n if(el instanceof HTMLInputElement && CHECKABLE_INPUTS.indexOf(el.type.toLocaleLowerCase()) >= 0){\n el.checked = el.getAttribute(\"checked\") !== null\n }\n },\n\n isTextualInput(el){ return FOCUSABLE_INPUTS.indexOf(el.type) >= 0 },\n\n isNowTriggerFormExternal(el, phxTriggerExternal){\n return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null\n },\n\n syncPendingRef(fromEl, toEl, disableWith){\n let ref = fromEl.getAttribute(PHX_REF)\n if(ref === null){ return true }\n let refSrc = fromEl.getAttribute(PHX_REF_SRC)\n\n if(DOM.isFormInput(fromEl) || fromEl.getAttribute(disableWith) !== null){\n if(DOM.isUploadInput(fromEl)){ DOM.mergeAttrs(fromEl, toEl, {isIgnored: true}) }\n DOM.putPrivate(fromEl, PHX_REF, toEl)\n return false\n } else {\n PHX_EVENT_CLASSES.forEach(className => {\n fromEl.classList.contains(className) && toEl.classList.add(className)\n })\n toEl.setAttribute(PHX_REF, ref)\n toEl.setAttribute(PHX_REF_SRC, refSrc)\n return true\n }\n },\n\n cleanChildNodes(container, phxUpdate){\n if(DOM.isPhxUpdate(container, phxUpdate, [\"append\", \"prepend\"])){\n let toRemove = []\n container.childNodes.forEach(childNode => {\n if(!childNode.id){\n // Skip warning if it's an empty text node (e.g. a new-line)\n let isEmptyTextNode = childNode.nodeType === Node.TEXT_NODE && childNode.nodeValue.trim() === \"\"\n if(!isEmptyTextNode){\n logError(\"only HTML element tags with an id are allowed inside containers with phx-update.\\n\\n\" +\n `removing illegal node: \"${(childNode.outerHTML || childNode.nodeValue).trim()}\"\\n\\n`)\n }\n toRemove.push(childNode)\n }\n })\n toRemove.forEach(childNode => childNode.remove())\n }\n },\n\n replaceRootContainer(container, tagName, attrs){\n let retainedAttrs = new Set([\"id\", PHX_SESSION, PHX_STATIC, PHX_MAIN, PHX_ROOT_ID])\n if(container.tagName.toLowerCase() === tagName.toLowerCase()){\n Array.from(container.attributes)\n .filter(attr => !retainedAttrs.has(attr.name.toLowerCase()))\n .forEach(attr => container.removeAttribute(attr.name))\n\n Object.keys(attrs)\n .filter(name => !retainedAttrs.has(name.toLowerCase()))\n .forEach(attr => container.setAttribute(attr, attrs[attr]))\n\n return container\n\n } else {\n let newContainer = document.createElement(tagName)\n Object.keys(attrs).forEach(attr => newContainer.setAttribute(attr, attrs[attr]))\n retainedAttrs.forEach(attr => newContainer.setAttribute(attr, container.getAttribute(attr)))\n newContainer.innerHTML = container.innerHTML\n container.replaceWith(newContainer)\n return newContainer\n }\n },\n\n getSticky(el, name, defaultVal){\n let op = (DOM.private(el, \"sticky\") || []).find(([existingName, ]) => name === existingName)\n if(op){\n let [_name, _op, stashedResult] = op\n return stashedResult\n } else {\n return typeof(defaultVal) === \"function\" ? defaultVal() : defaultVal\n }\n },\n\n deleteSticky(el, name){\n this.updatePrivate(el, \"sticky\", [], ops => {\n return ops.filter(([existingName, _]) => existingName !== name)\n })\n },\n\n putSticky(el, name, op){\n let stashedResult = op(el)\n this.updatePrivate(el, \"sticky\", [], ops => {\n let existingIndex = ops.findIndex(([existingName, ]) => name === existingName)\n if(existingIndex >= 0){\n ops[existingIndex] = [name, op, stashedResult]\n } else {\n ops.push([name, op, stashedResult])\n }\n return ops\n })\n },\n\n applyStickyOperations(el){\n let ops = DOM.private(el, \"sticky\")\n if(!ops){ return }\n\n ops.forEach(([name, op, _stashed]) => this.putSticky(el, name, op))\n }\n}\n\nexport default DOM\n", "import {\n PHX_ACTIVE_ENTRY_REFS,\n PHX_LIVE_FILE_UPDATED,\n PHX_PREFLIGHTED_REFS\n} from \"./constants\"\n\nimport {\n channelUploader,\n logError\n} from \"./utils\"\n\nimport LiveUploader from \"./live_uploader\"\n\nexport default class UploadEntry {\n static isActive(fileEl, file){\n let isNew = file._phxRef === undefined\n let activeRefs = fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(\",\")\n let isActive = activeRefs.indexOf(LiveUploader.genFileRef(file)) >= 0\n return file.size > 0 && (isNew || isActive)\n }\n\n static isPreflighted(fileEl, file){\n let preflightedRefs = fileEl.getAttribute(PHX_PREFLIGHTED_REFS).split(\",\")\n let isPreflighted = preflightedRefs.indexOf(LiveUploader.genFileRef(file)) >= 0\n return isPreflighted && this.isActive(fileEl, file)\n }\n\n constructor(fileEl, file, view){\n this.ref = LiveUploader.genFileRef(file)\n this.fileEl = fileEl\n this.file = file\n this.view = view\n this.meta = null\n this._isCancelled = false\n this._isDone = false\n this._progress = 0\n this._lastProgressSent = -1\n this._onDone = function (){ }\n this._onElUpdated = this.onElUpdated.bind(this)\n this.fileEl.addEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)\n }\n\n metadata(){ return this.meta }\n\n progress(progress){\n this._progress = Math.floor(progress)\n if(this._progress > this._lastProgressSent){\n if(this._progress >= 100){\n this._progress = 100\n this._lastProgressSent = 100\n this._isDone = true\n this.view.pushFileProgress(this.fileEl, this.ref, 100, () => {\n LiveUploader.untrackFile(this.fileEl, this.file)\n this._onDone()\n })\n } else {\n this._lastProgressSent = this._progress\n this.view.pushFileProgress(this.fileEl, this.ref, this._progress)\n }\n }\n }\n\n cancel(){\n this._isCancelled = true\n this._isDone = true\n this._onDone()\n }\n\n isDone(){ return this._isDone }\n\n error(reason = \"failed\"){\n this.view.pushFileProgress(this.fileEl, this.ref, {error: reason})\n LiveUploader.clearFiles(this.fileEl)\n }\n\n //private\n\n onDone(callback){\n this._onDone = () => {\n this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)\n callback()\n }\n }\n\n onElUpdated(){\n let activeRefs = this.fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(\",\")\n if(activeRefs.indexOf(this.ref) === -1){ this.cancel() }\n }\n\n toPreflightPayload(){\n return {\n last_modified: this.file.lastModified,\n name: this.file.name,\n size: this.file.size,\n type: this.file.type,\n ref: this.ref\n }\n }\n\n uploader(uploaders){\n if(this.meta.uploader){\n let callback = uploaders[this.meta.uploader] || logError(`no uploader configured for ${this.meta.uploader}`)\n return {name: this.meta.uploader, callback: callback}\n } else {\n return {name: \"channel\", callback: channelUploader}\n }\n }\n\n zipPostFlight(resp){\n this.meta = resp.entries[this.ref]\n if(!this.meta){ logError(`no preflight upload response returned with ref ${this.ref}`, {input: this.fileEl, response: resp}) }\n }\n}\n", "import {\n PHX_DONE_REFS,\n PHX_PREFLIGHTED_REFS,\n PHX_UPLOAD_REF\n} from \"./constants\"\n\nimport {\n} from \"./utils\"\n\nimport DOM from \"./dom\"\nimport UploadEntry from \"./upload_entry\"\n\nlet liveUploaderFileRef = 0\n\nexport default class LiveUploader {\n static genFileRef(file){\n let ref = file._phxRef\n if(ref !== undefined){\n return ref\n } else {\n file._phxRef = (liveUploaderFileRef++).toString()\n return file._phxRef\n }\n }\n\n static getEntryDataURL(inputEl, ref, callback){\n let file = this.activeFiles(inputEl).find(file => this.genFileRef(file) === ref)\n callback(URL.createObjectURL(file))\n }\n\n static hasUploadsInProgress(formEl){\n let active = 0\n DOM.findUploadInputs(formEl).forEach(input => {\n if(input.getAttribute(PHX_PREFLIGHTED_REFS) !== input.getAttribute(PHX_DONE_REFS)){\n active++\n }\n })\n return active > 0\n }\n\n static serializeUploads(inputEl){\n let files = this.activeFiles(inputEl)\n let fileData = {}\n files.forEach(file => {\n let entry = {path: inputEl.name}\n let uploadRef = inputEl.getAttribute(PHX_UPLOAD_REF)\n fileData[uploadRef] = fileData[uploadRef] || []\n entry.ref = this.genFileRef(file)\n entry.name = file.name || entry.ref\n entry.type = file.type\n entry.size = file.size\n fileData[uploadRef].push(entry)\n })\n return fileData\n }\n\n static clearFiles(inputEl){\n inputEl.value = null\n inputEl.removeAttribute(PHX_UPLOAD_REF)\n DOM.putPrivate(inputEl, \"files\", [])\n }\n\n static untrackFile(inputEl, file){\n DOM.putPrivate(inputEl, \"files\", DOM.private(inputEl, \"files\").filter(f => !Object.is(f, file)))\n }\n\n static trackFiles(inputEl, files){\n if(inputEl.getAttribute(\"multiple\") !== null){\n let newFiles = files.filter(file => !this.activeFiles(inputEl).find(f => Object.is(f, file)))\n DOM.putPrivate(inputEl, \"files\", this.activeFiles(inputEl).concat(newFiles))\n inputEl.value = null\n } else {\n DOM.putPrivate(inputEl, \"files\", files)\n }\n }\n\n static activeFileInputs(formEl){\n let fileInputs = DOM.findUploadInputs(formEl)\n return Array.from(fileInputs).filter(el => el.files && this.activeFiles(el).length > 0)\n }\n\n static activeFiles(input){\n return (DOM.private(input, \"files\") || []).filter(f => UploadEntry.isActive(input, f))\n }\n\n static inputsAwaitingPreflight(formEl){\n let fileInputs = DOM.findUploadInputs(formEl)\n return Array.from(fileInputs).filter(input => this.filesAwaitingPreflight(input).length > 0)\n }\n\n static filesAwaitingPreflight(input){\n return this.activeFiles(input).filter(f => !UploadEntry.isPreflighted(input, f))\n }\n\n constructor(inputEl, view, onComplete){\n this.view = view\n this.onComplete = onComplete\n this._entries =\n Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || [])\n .map(file => new UploadEntry(inputEl, file, view))\n\n this.numEntriesInProgress = this._entries.length\n }\n\n entries(){ return this._entries }\n\n initAdapterUpload(resp, onError, liveSocket){\n this._entries =\n this._entries.map(entry => {\n entry.zipPostFlight(resp)\n entry.onDone(() => {\n this.numEntriesInProgress--\n if(this.numEntriesInProgress === 0){ this.onComplete() }\n })\n return entry\n })\n\n let groupedEntries = this._entries.reduce((acc, entry) => {\n let {name, callback} = entry.uploader(liveSocket.uploaders)\n acc[name] = acc[name] || {callback: callback, entries: []}\n acc[name].entries.push(entry)\n return acc\n }, {})\n\n for(let name in groupedEntries){\n let {callback, entries} = groupedEntries[name]\n callback(entries, onError, resp, liveSocket)\n }\n }\n}\n", "import {\n PHX_ACTIVE_ENTRY_REFS,\n PHX_LIVE_FILE_UPDATED,\n PHX_PREFLIGHTED_REFS,\n PHX_UPLOAD_REF\n} from \"./constants\"\n\nimport LiveUploader from \"./live_uploader\"\n\nlet Hooks = {\n LiveFileUpload: {\n activeRefs(){ return this.el.getAttribute(PHX_ACTIVE_ENTRY_REFS) },\n\n preflightedRefs(){ return this.el.getAttribute(PHX_PREFLIGHTED_REFS) },\n\n mounted(){ this.preflightedWas = this.preflightedRefs() },\n\n updated(){\n let newPreflights = this.preflightedRefs()\n if(this.preflightedWas !== newPreflights){\n this.preflightedWas = newPreflights\n if(newPreflights === \"\"){\n this.__view.cancelSubmit(this.el.form)\n }\n }\n\n if(this.activeRefs() === \"\"){ this.el.value = null }\n this.el.dispatchEvent(new CustomEvent(PHX_LIVE_FILE_UPDATED))\n }\n },\n\n LiveImgPreview: {\n mounted(){\n this.ref = this.el.getAttribute(\"data-phx-entry-ref\")\n this.inputEl = document.getElementById(this.el.getAttribute(PHX_UPLOAD_REF))\n LiveUploader.getEntryDataURL(this.inputEl, this.ref, url => {\n this.url = url\n this.el.src = url\n })\n },\n destroyed(){\n URL.revokeObjectURL(this.url)\n }\n }\n}\n\nexport default Hooks\n", "import {\n maybe\n} from \"./utils\"\n\nimport DOM from \"./dom\"\n\nexport default class DOMPostMorphRestorer {\n constructor(containerBefore, containerAfter, updateType){\n let idsBefore = new Set()\n let idsAfter = new Set([...containerAfter.children].map(child => child.id))\n\n let elementsToModify = []\n\n Array.from(containerBefore.children).forEach(child => {\n if(child.id){ // all of our children should be elements with ids\n idsBefore.add(child.id)\n if(idsAfter.has(child.id)){\n let previousElementId = child.previousElementSibling && child.previousElementSibling.id\n elementsToModify.push({elementId: child.id, previousElementId: previousElementId})\n }\n }\n })\n\n this.containerId = containerAfter.id\n this.updateType = updateType\n this.elementsToModify = elementsToModify\n this.elementIdsToAdd = [...idsAfter].filter(id => !idsBefore.has(id))\n }\n\n // We do the following to optimize append/prepend operations:\n // 1) Track ids of modified elements & of new elements\n // 2) All the modified elements are put back in the correct position in the DOM tree\n // by storing the id of their previous sibling\n // 3) New elements are going to be put in the right place by morphdom during append.\n // For prepend, we move them to the first position in the container\n perform(){\n let container = DOM.byId(this.containerId)\n this.elementsToModify.forEach(elementToModify => {\n if(elementToModify.previousElementId){\n maybe(document.getElementById(elementToModify.previousElementId), previousElem => {\n maybe(document.getElementById(elementToModify.elementId), elem => {\n let isInRightPlace = elem.previousElementSibling && elem.previousElementSibling.id == previousElem.id\n if(!isInRightPlace){\n previousElem.insertAdjacentElement(\"afterend\", elem)\n }\n })\n })\n } else {\n // This is the first element in the container\n maybe(document.getElementById(elementToModify.elementId), elem => {\n let isInRightPlace = elem.previousElementSibling == null\n if(!isInRightPlace){\n container.insertAdjacentElement(\"afterbegin\", elem)\n }\n })\n }\n })\n\n if(this.updateType == \"prepend\"){\n this.elementIdsToAdd.reverse().forEach(elemId => {\n maybe(document.getElementById(elemId), elem => container.insertAdjacentElement(\"afterbegin\", elem))\n })\n }\n }\n}\n", "var DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction morphAttrs(fromNode, toNode) {\n var toNodeAttrs = toNode.attributes;\n var attr;\n var attrName;\n var attrNamespaceURI;\n var attrValue;\n var fromValue;\n\n // document-fragments dont have attributes so lets not do anything\n if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {\n return;\n }\n\n // update attributes on original DOM element\n for (var i = toNodeAttrs.length - 1; i >= 0; i--) {\n attr = toNodeAttrs[i];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n attrValue = attr.value;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);\n\n if (fromValue !== attrValue) {\n if (attr.prefix === 'xmlns'){\n attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix\n }\n fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);\n }\n } else {\n fromValue = fromNode.getAttribute(attrName);\n\n if (fromValue !== attrValue) {\n fromNode.setAttribute(attrName, attrValue);\n }\n }\n }\n\n // Remove any extra attributes found on the original DOM element that\n // weren't found on the target element.\n var fromNodeAttrs = fromNode.attributes;\n\n for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {\n attr = fromNodeAttrs[d];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n\n if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {\n fromNode.removeAttributeNS(attrNamespaceURI, attrName);\n }\n } else {\n if (!toNode.hasAttribute(attrName)) {\n fromNode.removeAttribute(attrName);\n }\n }\n }\n}\n\nvar range; // Create a range object for efficently rendering strings to elements.\nvar NS_XHTML = 'http://www.w3.org/1999/xhtml';\n\nvar doc = typeof document === 'undefined' ? undefined : document;\nvar HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');\nvar HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();\n\nfunction createFragmentFromTemplate(str) {\n var template = doc.createElement('template');\n template.innerHTML = str;\n return template.content.childNodes[0];\n}\n\nfunction createFragmentFromRange(str) {\n if (!range) {\n range = doc.createRange();\n range.selectNode(doc.body);\n }\n\n var fragment = range.createContextualFragment(str);\n return fragment.childNodes[0];\n}\n\nfunction createFragmentFromWrap(str) {\n var fragment = doc.createElement('body');\n fragment.innerHTML = str;\n return fragment.childNodes[0];\n}\n\n/**\n * This is about the same\n * var html = new DOMParser().parseFromString(str, 'text/html');\n * return html.body.firstChild;\n *\n * @method toElement\n * @param {String} str\n */\nfunction toElement(str) {\n str = str.trim();\n if (HAS_TEMPLATE_SUPPORT) {\n // avoid restrictions on content for things like `Hi` which\n // createContextualFragment doesn't support\n //