New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

phoenix_live_view

Package Overview
Dependencies
Maintainers
2
Versions
118
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

phoenix_live_view - npm Package Compare versions

Comparing version 1.0.2 to 1.0.3

44

assets/js/phoenix_live_view/dom_patch.js

@@ -21,2 +21,3 @@ import {

detectDuplicateIds,
detectInvalidStreamInserts,
isCid

@@ -30,36 +31,3 @@ } from "./utils"

export default class DOMPatch {
static patchWithClonedTree(container, clonedTree, liveSocket){
let focused = liveSocket.getActiveElement()
let {selectionStart, selectionEnd} = focused && DOM.hasSelectionRange(focused) ? focused : {}
let phxUpdate = liveSocket.binding(PHX_UPDATE)
let externalFormTriggered = null
morphdom(container, clonedTree, {
childrenOnly: false,
onBeforeElUpdated: (fromEl, toEl) => {
DOM.syncPendingAttrs(fromEl, toEl)
// we cannot morph locked children
if(!container.isSameNode(fromEl) && fromEl.hasAttribute(PHX_REF_LOCK)){ return false }
if(DOM.isIgnored(fromEl, phxUpdate)){ return false }
if(focused && focused.isSameNode(fromEl) && DOM.isFormInput(fromEl)){
DOM.mergeFocusedInput(fromEl, toEl)
return false
}
if(DOM.isNowTriggerFormExternal(toEl, liveSocket.binding(PHX_TRIGGER_ACTION))){
externalFormTriggered = toEl
}
}
})
if(externalFormTriggered){
liveSocket.unload()
// use prototype's submit in case there's a form control with name or id of "submit"
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit
Object.getPrototypeOf(externalFormTriggered).submit.call(externalFormTriggered)
}
liveSocket.silenceEvents(() => DOM.restoreFocus(focused, selectionStart, selectionEnd))
}
constructor(view, container, id, html, streams, targetCID){
constructor(view, container, id, html, streams, targetCID, opts={}){
this.view = view

@@ -84,2 +52,4 @@ this.liveSocket = view.liveSocket

}
this.withChildren = opts.withChildren || opts.undoRef || false
this.undoRef = opts.undoRef
}

@@ -121,3 +91,3 @@

function morph(targetContainer, source, withChildren=false){
function morph(targetContainer, source, withChildren=this.withChildren){
let morphCallbacks = {

@@ -254,3 +224,4 @@ // normally, we are running with childrenOnly, as the patch HTML for a LV

let focusedSelectChanged = isFocusedFormEl && this.isChangedSelect(fromEl, toEl)
if(fromEl.hasAttribute(PHX_REF_SRC)){
// only perform the clone step if this is not a patch that unlocks
if(fromEl.hasAttribute(PHX_REF_SRC) && fromEl.getAttribute(PHX_REF_LOCK) != this.undoRef){
if(DOM.isUploadInput(fromEl)){

@@ -349,2 +320,3 @@ DOM.mergeAttrs(fromEl, toEl, {isIgnored: true})

detectDuplicateIds()
detectInvalidStreamInserts(this.streamInserts)
// warn if there are any inputs named "id"

@@ -351,0 +323,0 @@ Array.from(document.querySelectorAll("input[name=id]")).forEach(node => {

7

assets/js/phoenix_live_view/dom.js

@@ -13,2 +13,3 @@ import {

PHX_REF_SRC,
PHX_REF_LOCK,
PHX_PENDING_ATTRS,

@@ -152,3 +153,3 @@ PHX_ROOT_ID,

parentCids.add(cid)
this.all(parent, `[${PHX_COMPONENT}]`)
this.filterWithinSameLiveView(this.all(parent, `[${PHX_COMPONENT}]`), parent)
.map(el => parseInt(el.getAttribute(PHX_COMPONENT)))

@@ -550,2 +551,6 @@ .forEach(childCID => childrenCids.add(childCID))

ops.forEach(([name, op, _stashed]) => this.putSticky(el, name, op))
},
isLocked(el){
return el.hasAttribute && el.hasAttribute(PHX_REF_LOCK)
}

@@ -552,0 +557,0 @@ }

@@ -14,2 +14,11 @@ import {

export default class ElementRef {
static onUnlock(el, callback){
if(!DOM.isLocked(el) && !el.closest(`[${PHX_REF_LOCK}]`)){ return callback() }
const closestLock = el.closest(`[${PHX_REF_LOCK}]`)
const ref = closestLock.closest(`[${PHX_REF_LOCK}]`).getAttribute(PHX_REF_LOCK)
closestLock.addEventListener(`phx:undo-lock:${ref}`, () => {
callback()
}, {once: true})
}
constructor(el){

@@ -16,0 +25,0 @@ this.el = el

@@ -50,4 +50,22 @@ import {

this.focusEnd = this.el.lastElementChild
this.focusStart.addEventListener("focus", () => ARIA.focusLast(this.el))
this.focusEnd.addEventListener("focus", () => ARIA.focusFirst(this.el))
this.focusStart.addEventListener("focus", (e) => {
if(!e.relatedTarget || !this.el.contains(e.relatedTarget)){
// Handle focus entering from outside (e.g. Tab when body is focused)
// https://github.com/phoenixframework/phoenix_live_view/issues/3636
const nextFocus = e.target.nextElementSibling
ARIA.attemptFocus(nextFocus) || ARIA.focusFirst(nextFocus)
} else {
ARIA.focusLast(this.el)
}
})
this.focusEnd.addEventListener("focus", (e) => {
if(!e.relatedTarget || !this.el.contains(e.relatedTarget)){
// Handle focus entering from outside (e.g. Shift+Tab when body is focused)
// https://github.com/phoenixframework/phoenix_live_view/issues/3636
const nextFocus = e.target.previousElementSibling
ARIA.attemptFocus(nextFocus) || ARIA.focusLast(nextFocus)
} else {
ARIA.focusFirst(this.el)
}
})
this.el.addEventListener("phx:show-end", () => this.el.focus())

@@ -54,0 +72,0 @@ if(window.getComputedStyle(this.el).display !== "none"){

@@ -378,3 +378,5 @@ /** Initializes the LiveSocket

let view = this.newRootView(rootEl)
view.setHref(this.getHref())
// stickies cannot be mounted at the router and therefore should not
// get a href set on them
if(!DOM.isPhxSticky(rootEl)){ view.setHref(this.getHref()) }
view.join()

@@ -697,3 +699,3 @@ if(rootEl.hasAttribute(PHX_MAIN)){ this.main = view }

if(!this.registerNewLocation(window.location)){ return }
let {type, backType, id, root, scroll, position} = event.state || {}
let {type, backType, id, scroll, position} = event.state || {}
let href = window.location.href

@@ -712,11 +714,7 @@

this.requestDOMUpdate(() => {
const callback = () => { this.maybeScroll(scroll) }
if(this.main.isConnected() && (type === "patch" && id === this.main.id)){
this.main.pushLinkPatch(event, href, null, () => {
this.maybeScroll(scroll)
})
this.main.pushLinkPatch(event, href, null, callback)
} else {
this.replaceMain(href, null, () => {
if(root){ this.replaceRootHistory() }
this.maybeScroll(scroll)
})
this.replaceMain(href, null, callback)
}

@@ -842,11 +840,2 @@ })

replaceRootHistory(){
Browser.pushState("replace", {
root: true,
type: "patch",
id: this.main.id,
position: this.currentHistoryPosition // Preserve current position
})
}
registerNewLocation(newLocation){

@@ -853,0 +842,0 @@ let {pathname, search} = this.currentLocation

@@ -26,2 +26,13 @@ import {

export function detectInvalidStreamInserts(inserts){
const errors = new Set()
Object.keys(inserts).forEach((id) => {
const streamEl = document.getElementById(id)
if(streamEl && streamEl.parentElement && streamEl.parentElement.getAttribute("phx-update") !== "stream"){
errors.add(`The stream container with id "${streamEl.parentElement.id}" is missing the phx-update="stream" attribute. Ensure it is set for streams to work properly.`)
}
})
errors.forEach(error => console.error(error))
}
export let debug = (view, kind, msg, obj) => {

@@ -28,0 +39,0 @@ if(view.liveSocket.isDebugEnabled()){

@@ -321,4 +321,8 @@ import {

if(this.isMain() && window.history.state === null){
// set initial history entry if this is the first page load
this.liveSocket.replaceRootHistory()
// set initial history entry if this is the first page load (no history)
Browser.pushState("replace", {
type: "patch",
id: this.id,
position: this.liveSocket.currentHistoryPosition
})
}

@@ -701,2 +705,5 @@

// only ever try to add hooks to elements owned by this view
if(el.getAttribute && !this.ownsElement(el)){ return }
if(hookElId && !this.viewHooks[hookElId]){

@@ -715,3 +722,2 @@ // hook created, but not attached (createHook for web component)

let hookName = el.getAttribute(`data-phx-${PHX_HOOK}`) || el.getAttribute(this.binding(PHX_HOOK))
if(hookName && !this.ownsElement(el)){ return }
let callbacks = this.liveSocket.getHookCallbacks(hookName)

@@ -731,5 +737,8 @@

destroyHook(hook){
// __destroyed clears the elementID from the hook, therefore
// we need to get it before calling __destroyed
const hookId = ViewHook.elementID(hook.el)
hook.__destroyed()
hook.__cleanup__()
delete this.viewHooks[ViewHook.elementID(hook.el)]
delete this.viewHooks[hookId]
}

@@ -978,7 +987,8 @@

elRef.maybeUndo(ref, phxEvent, clonedTree => {
let hook = this.triggerBeforeUpdateHook(el, clonedTree)
DOMPatch.patchWithClonedTree(el, clonedTree, this.liveSocket)
// we need to perform a full patch on unlocked elements
// to perform all the necessary logic (like calling updated for hooks, etc.)
let patch = new DOMPatch(this, el, this.id, clonedTree, [], null, {undoRef: ref})
const phxChildrenAdded = this.performPatch(patch, true)
DOM.all(el, `[${PHX_REF_SRC}="${this.refSrc()}"]`, child => this.undoElRef(child, ref, phxEvent))
this.execNewMounted(el)
if(hook){ hook.__updated() }
if(phxChildrenAdded){ this.joinNewChildren() }
})

@@ -1190,11 +1200,16 @@ }

if(DOM.isUploadInput(inputEl) && DOM.isAutoUpload(inputEl)){
if(LiveUploader.filesAwaitingPreflight(inputEl).length > 0){
let [ref, _els] = refGenerator()
this.undoRefs(ref, phxEvent, [inputEl.form])
this.uploadFiles(inputEl.form, phxEvent, targetCtx, ref, cid, (_uploads) => {
callback && callback(resp)
this.triggerAwaitingSubmit(inputEl.form, phxEvent)
this.undoRefs(ref, phxEvent)
})
}
// the element could be inside a locked parent for other unrelated changes;
// we can only start uploads when the tree is unlocked and the
// necessary data attributes are set in the real DOM
ElementRef.onUnlock(inputEl, () => {
if(LiveUploader.filesAwaitingPreflight(inputEl).length > 0){
let [ref, _els] = refGenerator()
this.undoRefs(ref, phxEvent, [inputEl.form])
this.uploadFiles(inputEl.form, phxEvent, targetCtx, ref, cid, (_uploads) => {
callback && callback(resp)
this.triggerAwaitingSubmit(inputEl.form, phxEvent)
this.undoRefs(ref, phxEvent)
})
}
})
} else {

@@ -1201,0 +1216,0 @@ callback && callback(resp)

{
"name": "phoenix_live_view",
"version": "1.0.2",
"version": "1.0.3",
"description": "The Phoenix LiveView JavaScript client.",

@@ -26,13 +26,31 @@ "license": "MIT",

],
"dependencies": {
"morphdom": "2.7.4"
},
"devDependencies": {
"@eslint/js": "^9.10.0",
"@playwright/test": "^1.47.1",
"@babel/cli": "7.26.4",
"@babel/core": "7.26.0",
"@babel/preset-env": "7.26.0",
"@eslint/js": "^9.18.0",
"@playwright/test": "^1.49.1",
"@stylistic/eslint-plugin-js": "^2.12.1",
"css.escape": "^1.5.1",
"eslint": "9.18.0",
"eslint-plugin-jest": "28.10.0",
"eslint-plugin-playwright": "^2.1.0",
"monocart-reporter": "^2.8.0"
"globals": "^15.14.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-monocart-coverage": "^1.1.1",
"monocart-reporter": "^2.9.13",
"phoenix": "1.7.18"
},
"scripts": {
"setup": "mix deps.get && npm install && cd assets && npm install",
"setup": "mix deps.get && npm install",
"e2e:server": "MIX_ENV=e2e mix test --cover --export-coverage e2e test/e2e/test_helper.exs",
"e2e:test": "mix assets.build && cd test/e2e && npx playwright install && npx playwright test",
"js:test": "cd assets && npm install && npm run test",
"js:test": "jest",
"js:test.coverage": "jest --coverage",
"js:test.watch": "jest --watch",
"js:lint": "eslint --fix && cd assets && eslint --fix",
"test": "npm run js:test && npm run e2e:test",

@@ -39,0 +57,0 @@ "cover:merge": "node test/e2e/merge-coverage.mjs",

@@ -197,14 +197,6 @@ # Phoenix LiveView

```bash
$ cd assets
$ npm install
$ npm run test
# to automatically run tests for files that have been changed
$ npm run test.watch
```
or simply:
```bash
$ npm run setup
$ npm run js:test
# to automatically run tests for files that have been changed
$ npm run js:test.watch
```

@@ -211,0 +203,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc